From 17f9ee63f7f1aee89375bae1ff9b99849120aadc Mon Sep 17 00:00:00 2001 From: Louis Lam Date: Mon, 25 Dec 2023 18:02:46 +0800 Subject: [PATCH] WIP --- backend/agent-manager.ts | 11 ++++-- .../docker-socket-handler.ts | 26 ++++++------- .../terminal-socket-handler.ts | 35 +++++++---------- backend/stack.ts | 21 +++++++++- backend/terminal.ts | 19 +++++---- backend/util-server.ts | 8 ++++ common/util-common.ts | 6 +-- frontend/src/components/Container.vue | 39 ++++++++++++++----- frontend/src/components/Terminal.vue | 13 +++---- frontend/src/pages/Compose.vue | 20 +++++++--- frontend/src/pages/Console.vue | 7 +++- frontend/src/pages/ContainerTerminal.vue | 4 +- frontend/src/router.ts | 4 ++ 13 files changed, 137 insertions(+), 76 deletions(-) diff --git a/backend/agent-manager.ts b/backend/agent-manager.ts index fd86781..819c9c5 100644 --- a/backend/agent-manager.ts +++ b/backend/agent-manager.ts @@ -147,7 +147,6 @@ export class AgentManager { client.on("connect_error", (err) => { log.error("agent-manager", "Error from the socket server: " + endpoint); - log.debug("agent-manager", err); this.socket.emit("agentStatus", { endpoint: endpoint, status: "offline", @@ -163,7 +162,6 @@ export class AgentManager { }); client.on("agent", (...args : unknown[]) => { - log.debug("agent-manager", "Forward event"); this.socket.emit("agent", ...args); }); @@ -216,11 +214,18 @@ export class AgentManager { emitToEndpoint(endpoint: string, eventName: string, ...args : unknown[]) { log.debug("agent-manager", "Emitting event to endpoint: " + endpoint); let client = this.agentSocketList[endpoint]; + if (!client) { log.error("agent-manager", "Socket client not found for endpoint: " + endpoint); throw new Error("Socket client not found for endpoint: " + endpoint); } - client?.emit("agent", endpoint, eventName, ...args); + + if (!client.connected) { + log.error("agent-manager", "Socket client not connected for endpoint: " + endpoint); + throw new Error("Socket client not connected for endpoint: " + endpoint); + } + + client.emit("agent", endpoint, eventName, ...args); } emitToAllEndpoints(eventName: string, ...args : unknown[]) { diff --git a/backend/agent-socket-handlers/docker-socket-handler.ts b/backend/agent-socket-handlers/docker-socket-handler.ts index f7f3646..32aea87 100644 --- a/backend/agent-socket-handlers/docker-socket-handler.ts +++ b/backend/agent-socket-handlers/docker-socket-handler.ts @@ -1,6 +1,6 @@ import { AgentSocketHandler } from "../agent-socket-handler"; import { DockgeServer } from "../dockge-server"; -import { callbackError, checkLogin, DockgeSocket, ValidationError } from "../util-server"; +import { callbackError, callbackResult, checkLogin, DockgeSocket, ValidationError } from "../util-server"; import { Stack } from "../stack"; import { AgentSocket } from "../../common/agent-socket"; @@ -14,10 +14,10 @@ export class DockerSocketHandler extends AgentSocketHandler { const stack = await this.saveStack(server, name, composeYAML, composeENV, isAdd); await stack.deploy(socket); server.sendStackList(); - callback({ + callbackResult({ ok: true, msg: "Deployed", - }); + }, callback); stack.joinCombinedTerminal(socket); } catch (e) { callbackError(e, callback); @@ -27,11 +27,11 @@ export class DockerSocketHandler extends AgentSocketHandler { agentSocket.on("saveStack", async (name : unknown, composeYAML : unknown, composeENV : unknown, isAdd : unknown, callback) => { try { checkLogin(socket); - this.saveStack(server, name, composeYAML, composeENV, isAdd); - callback({ + await this.saveStack(server, name, composeYAML, composeENV, isAdd); + callbackResult({ ok: true, "msg": "Saved" - }); + }, callback); server.sendStackList(); } catch (e) { callbackError(e, callback); @@ -54,10 +54,10 @@ export class DockerSocketHandler extends AgentSocketHandler { } server.sendStackList(); - callback({ + callbackResult({ ok: true, msg: "Deleted" - }); + }, callback); } catch (e) { callbackError(e, callback); @@ -78,10 +78,10 @@ export class DockerSocketHandler extends AgentSocketHandler { stack.joinCombinedTerminal(socket); } - callback({ + callbackResult({ ok: true, - stack: stack.toJSON(socket.endpoint), - }); + stack: await stack.toJSON(socket.endpoint), + }, callback); } catch (e) { callbackError(e, callback); } @@ -92,10 +92,10 @@ export class DockerSocketHandler extends AgentSocketHandler { try { checkLogin(socket); server.sendStackList(); - callback({ + callbackResult({ ok: true, msg: "Updated" - }); + }, callback); } catch (e) { callbackError(e, callback); } diff --git a/backend/agent-socket-handlers/terminal-socket-handler.ts b/backend/agent-socket-handlers/terminal-socket-handler.ts index f09aa61..70f58d4 100644 --- a/backend/agent-socket-handlers/terminal-socket-handler.ts +++ b/backend/agent-socket-handlers/terminal-socket-handler.ts @@ -1,5 +1,5 @@ import { DockgeServer } from "../dockge-server"; -import { callbackError, checkLogin, DockgeSocket, ValidationError } from "../util-server"; +import { callbackError, callbackResult, checkLogin, DockgeSocket, ValidationError } from "../util-server"; import { log } from "../log"; import { InteractiveTerminal, MainTerminal, Terminal } from "../terminal"; import { Stack } from "../stack"; @@ -9,7 +9,7 @@ import { AgentSocket } from "../../common/agent-socket"; export class TerminalSocketHandler extends AgentSocketHandler { create(s : DockgeSocket, server : DockgeServer, agentSocket : AgentSocket) { - agentSocket.on("terminalInput", async (terminalName : unknown, cmd : unknown, errorCallback) => { + agentSocket.on("terminalInput", async (terminalName : unknown, cmd : unknown, callback) => { try { checkLogin(s); @@ -29,12 +29,7 @@ export class TerminalSocketHandler extends AgentSocketHandler { throw new Error("Terminal not found or it is not a Interactive Terminal."); } } catch (e) { - if (e instanceof Error) { - errorCallback({ - ok: false, - msg: e.message, - }); - } + callbackError(e, callback); } }); @@ -50,22 +45,22 @@ export class TerminalSocketHandler extends AgentSocketHandler { throw new ValidationError("Terminal name must be a string."); } - log.debug("deployStack", "Terminal name: " + terminalName); + log.debug("mainTerminal", "Terminal name: " + terminalName); let terminal = Terminal.getTerminal(terminalName); if (!terminal) { terminal = new MainTerminal(server, terminalName); terminal.rows = 50; - log.debug("deployStack", "Terminal created"); + log.debug("mainTerminal", "Terminal created"); } - terminal.join(socket); + terminal.join(s); terminal.start(); - callback({ + callbackResult({ ok: true, - }); + }, callback); } catch (e) { callbackError(e, callback); } @@ -74,7 +69,7 @@ export class TerminalSocketHandler extends AgentSocketHandler { // Interactive Terminal for containers agentSocket.on("interactiveTerminal", async (stackName : unknown, serviceName : unknown, shell : unknown, callback) => { try { - checkLogin(socket); + checkLogin(s); if (typeof(stackName) !== "string") { throw new ValidationError("Stack name must be a string."); @@ -93,11 +88,11 @@ export class TerminalSocketHandler extends AgentSocketHandler { // Get stack const stack = await Stack.getStack(server, stackName); - stack.joinContainerTerminal(socket, serviceName, shell); + stack.joinContainerTerminal(s, serviceName, shell); - callback({ + callbackResult({ ok: true, - }); + }, callback); } catch (e) { callbackError(e, callback); } @@ -145,9 +140,9 @@ export class TerminalSocketHandler extends AgentSocketHandler { const stack = await Stack.getStack(server, stackName); await stack.leaveCombinedTerminal(s); - callback({ + callbackResult({ ok: true, - }); + }, callback); } catch (e) { callbackError(e, callback); } @@ -157,7 +152,7 @@ export class TerminalSocketHandler extends AgentSocketHandler { agentSocket.on("terminalResize", async (terminalName: unknown, rows: unknown, cols: unknown) => { log.info("terminalResize", `Terminal: ${terminalName}`); try { - checkLogin(socket); + checkLogin(s); if (typeof terminalName !== "string") { throw new Error("Terminal name must be a string."); } diff --git a/backend/stack.ts b/backend/stack.ts index 743ccd8..fbce500 100644 --- a/backend/stack.ts +++ b/backend/stack.ts @@ -18,6 +18,7 @@ import { } from "../common/util-common"; import { InteractiveTerminal, Terminal } from "./terminal"; import childProcessAsync from "promisify-child-process"; +import { Settings } from "./settings"; export class Stack { @@ -50,12 +51,30 @@ export class Stack { } } - toJSON(endpoint : string) : object { + async toJSON(endpoint : string) : Promise { + + // Since we have multiple agents now, embed primary hostname in the stack object too. + let primaryHostname = await Settings.get("primaryHostname"); + if (!primaryHostname) { + if (!endpoint) { + primaryHostname = "localhost"; + } else { + // Use the endpoint as the primary hostname + try { + primaryHostname = (new URL("https://" + endpoint).hostname); + } catch (e) { + // Just in case if the endpoint is in a incorrect format + primaryHostname = "localhost"; + } + } + } + let obj = this.toSimpleJSON(endpoint); return { ...obj, composeYAML: this.composeYAML, composeENV: this.composeENV, + primaryHostname, }; } diff --git a/backend/terminal.ts b/backend/terminal.ts index f742267..d2220ad 100644 --- a/backend/terminal.ts +++ b/backend/terminal.ts @@ -35,7 +35,7 @@ export class Terminal { public enableKeepAlive : boolean = false; protected keepAliveInterval? : NodeJS.Timeout; - protected socketList : DockgeSocket[] = []; + protected socketList : Record = {}; constructor(server : DockgeServer, name : string, file : string, args : string | string[], cwd : string) { this.server = server; @@ -89,7 +89,7 @@ export class Terminal { // Close if there is no clients this.keepAliveInterval = setInterval(() => { - const numClients = this.socketList.length; + const numClients = Object.keys(this.socketList).length; if (numClients === 0) { log.debug("Terminal", "Terminal " + this.name + " has no client, closing..."); @@ -114,7 +114,8 @@ export class Terminal { this._ptyProcess.onData((data) => { this.buffer.pushItem(data); - for (const socket of this.socketList) { + for (const socketID in this.socketList) { + const socket = this.socketList[socketID]; socket.emitAgent("terminalWrite", this.name, data); } }); @@ -139,12 +140,13 @@ export class Terminal { * @param res */ protected exit = (res : {exitCode: number, signal?: number | undefined}) => { - for (const socket of this.socketList) { + for (const socketID in this.socketList) { + const socket = this.socketList[socketID]; socket.emitAgent("terminalExit", this.name, res.exitCode); } // Remove all clients - this.socketList = []; + this.socketList = {}; Terminal.terminalMap.delete(this.name); log.debug("Terminal", "Terminal " + this.name + " exited with code " + res.exitCode); @@ -161,14 +163,11 @@ export class Terminal { } public join(socket : DockgeSocket) { - this.socketList.push(socket); + this.socketList[socket.id] = socket; } public leave(socket : DockgeSocket) { - const index = this.socketList.indexOf(socket); - if (index !== -1) { - this.socketList.splice(index, 1); - } + delete this.socketList[socket.id]; } public get ptyProcess() { diff --git a/backend/util-server.ts b/backend/util-server.ts index 73a31e0..227ece0 100644 --- a/backend/util-server.ts +++ b/backend/util-server.ts @@ -74,6 +74,14 @@ export function callbackError(error : unknown, callback : unknown) { } } +export function callbackResult(result : unknown, callback : unknown) { + if (typeof(callback) !== "function") { + log.error("console", "Callback is not a function"); + return; + } + callback(result); +} + export async function doubleCheckPassword(socket : DockgeSocket, currentPassword : unknown) { if (typeof currentPassword !== "string") { throw new Error("Wrong data type?"); diff --git a/common/util-common.ts b/common/util-common.ts index cc24ef6..c85b96f 100644 --- a/common/util-common.ts +++ b/common/util-common.ts @@ -291,10 +291,9 @@ function copyYAMLCommentsItems(items : any, srcItems : any) { * - "127.0.0.1:5000-5010:5000-5010" * - "6060:6060/udp" * @param input - * @param defaultHostname + * @param hostname */ -export function parseDockerPort(input : string, defaultHostname : string = "localhost") { - let hostname = defaultHostname; +export function parseDockerPort(input : string, hostname) { let port; let display; @@ -407,3 +406,4 @@ function traverseYAML(pair : Pair, env : DotenvParseOutput) : void { pair.value.value = envsubst(pair.value.value, env); } } + diff --git a/frontend/src/components/Container.vue b/frontend/src/components/Container.vue index b18c06c..8741d14 100644 --- a/frontend/src/components/Container.vue +++ b/frontend/src/components/Container.vue @@ -189,14 +189,34 @@ export default defineComponent({ }, terminalRouteLink() { - return { - name: "containerTerminal", - params: { - stackName: this.stackName, - serviceName: this.name, - type: "bash", - }, - }; + if (this.endpoint) { + return { + name: "containerTerminalEndpoint", + params: { + endpoint: this.endpoint, + stackName: this.stackName, + serviceName: this.name, + type: "bash", + }, + }; + } else { + return { + name: "containerTerminal", + params: { + stackName: this.stackName, + serviceName: this.name, + type: "bash", + }, + }; + } + }, + + endpoint() { + return this.$parent.$parent.endpoint; + }, + + stack() { + return this.$parent.$parent.stack; }, stackName() { @@ -254,8 +274,7 @@ export default defineComponent({ }, methods: { parsePort(port) { - let hostname = this.$root.info.primaryHostname || location.hostname; - return parseDockerPort(port, hostname); + return parseDockerPort(port, this.stack.primaryHostname); }, remove() { delete this.jsonObject.services[this.name]; diff --git a/frontend/src/components/Terminal.vue b/frontend/src/components/Terminal.vue index 1772ef0..5452738 100644 --- a/frontend/src/components/Terminal.vue +++ b/frontend/src/components/Terminal.vue @@ -7,7 +7,6 @@