diff --git a/backend/agent-manager.ts b/backend/agent-manager.ts index 44dedc1..1293f35 100644 --- a/backend/agent-manager.ts +++ b/backend/agent-manager.ts @@ -2,7 +2,9 @@ import { DockgeSocket } from "./util-server"; import { io, Socket as SocketClient } from "socket.io-client"; import { log } from "./log"; import { Agent } from "./models/agent"; -import { LooseObject } from "../common/util-common"; +import { isDev, LooseObject } from "../common/util-common"; +import semver from "semver"; +import { R } from "redbean-node"; /** * Dockge Instance Manager @@ -16,6 +18,74 @@ export class AgentManager { this.socket = socket; } + test(url : string, username : string, password : string) : Promise { + return new Promise((resolve, reject) => { + let obj = new URL(url); + let endpoint = obj.host; + + if (!endpoint) { + reject(new Error("Invalid Dockge URL")); + } + + if (this.instanceSocketList[endpoint]) { + reject(new Error("The Dockge URL already exists")); + } + + let client = io(url, { + reconnection: false, + extraHeaders: { + endpoint, + } + }); + + client.on("connect", () => { + client.emit("login", { + username: username, + password: password, + }, (res : LooseObject) => { + if (res.ok) { + resolve(); + } else { + reject(new Error(res.msg)); + } + client.disconnect(); + }); + }); + + client.on("connect_error", (err) => { + if (err.message === "xhr poll error") { + reject(new Error("Unable to connect to the Dockge instance")); + } else { + reject(err); + } + client.disconnect(); + }); + }); + } + + /** + * + * @param url + * @param username + * @param password + */ + async add(url : string, username : string, password : string) : Promise { + let bean = R.dispense("agent") as Agent; + bean.url = url; + bean.username = username; + bean.password = password; + await R.store(bean); + return bean; + } + + /** + * + * @param endpoint + */ + remove(endpoint : string) { + + } + connect(url : string, username : string, password : string) { let obj = new URL(url); let endpoint = obj.host; @@ -48,7 +118,7 @@ export class AgentManager { client.emit("login", { username: username, password: password, - }, (res) => { + }, (res : LooseObject) => { if (res.ok) { log.info("agent-manager", "Logged in to the socket server: " + endpoint); this.socket.emit("agentStatus", { @@ -65,9 +135,9 @@ export class AgentManager { }); }); - client.on("error", (err) => { + client.on("connect_error", (err) => { log.error("agent-manager", "Error from the socket server: " + endpoint); - log.error("agent-manager", err); + log.debug("agent-manager", err); this.socket.emit("agentStatus", { endpoint: endpoint, status: "offline", @@ -87,6 +157,20 @@ export class AgentManager { this.socket.emit("agent", ...args); }); + client.on("info", (res) => { + log.debug("agent-manager", res); + + // Disconnect if the version is lower than 1.4.0 + if (!isDev && semver.satisfies(res.version, "< 1.4.0")) { + this.socket.emit("agentStatus", { + endpoint: endpoint, + status: "offline", + msg: `${endpoint}: Unsupported version: ` + res.version, + }); + client.disconnect(); + } + }); + this.instanceSocketList[endpoint] = client; } diff --git a/backend/dockge-server.ts b/backend/dockge-server.ts index 447fca3..806ab41 100644 --- a/backend/dockge-server.ts +++ b/backend/dockge-server.ts @@ -251,7 +251,7 @@ export class DockgeServer { let obj2 = obj as LooseObject; obj2.endpoint = dockgeSocket.endpoint; } - this.io.to(dockgeSocket.userID + "").emit("agent", event, ...args); + dockgeSocket.emit("agent", event, ...args); }; if (typeof(socket.request.headers.endpoint) === "string") { @@ -601,7 +601,7 @@ export class DockgeServer { map.set(stackName, stack.toSimpleJSON(dockgeSocket.endpoint)); } - log.debug("server", "Send stack list"); + log.debug("server", "Send stack list to user: " + dockgeSocket.id + " (" + dockgeSocket.endpoint + ")"); dockgeSocket.emitAgent("stackList", { ok: true, stackList: Object.fromEntries(map), diff --git a/backend/socket-handlers/manage-agent-socket-handler.ts b/backend/socket-handlers/manage-agent-socket-handler.ts index b96e812..a43465c 100644 --- a/backend/socket-handlers/manage-agent-socket-handler.ts +++ b/backend/socket-handlers/manage-agent-socket-handler.ts @@ -1,13 +1,46 @@ import { SocketHandler } from "../socket-handler.js"; import { DockgeServer } from "../dockge-server"; import { log } from "../log"; -import { checkLogin, DockgeSocket } from "../util-server"; -import { AgentSocket } from "../../common/agent-socket"; -import { ALL_ENDPOINTS } from "../../common/util-common"; +import { callbackError, checkLogin, DockgeSocket } from "../util-server"; export class ManageAgentSocketHandler extends SocketHandler { create(socket : DockgeSocket, server : DockgeServer) { + // addAgent + socket.on("addAgent", async (data : unknown, callback : unknown) => { + try { + log.debug("manage-agent-socket-handler", "addAgent"); + checkLogin(socket); + let manager = socket.instanceManager; + await manager.test(data.url, data.username, data.password); + await manager.add(data.url, data.username, data.password); + + // connect to the agent + manager.connect(data.url, data.username, data.password); + + manager.sendAgentList(); + + callback({ + ok: true, + msg: "agentAddedSuccessfully", + msgi18n: true, + }); + + } catch (e) { + callbackError(e, callback); + } + }); + + // removeAgent + socket.on("removeAgent", async (data : unknown, callback : unknown) => { + try { + log.debug("manage-agent-socket-handler", "removeAgent"); + checkLogin(socket); + await socket.instanceManager.remove(data.endpoint); + } catch (e) { + callbackError(e, callback); + } + }); } } diff --git a/backend/util-server.ts b/backend/util-server.ts index 24c3ffe..73a31e0 100644 --- a/backend/util-server.ts +++ b/backend/util-server.ts @@ -60,12 +60,14 @@ export function callbackError(error : unknown, callback : unknown) { callback({ ok: false, msg: error.message, + msgi18n: true, }); } else if (error instanceof ValidationError) { callback({ ok: false, type: ERROR_TYPE_VALIDATION, msg: error.message, + msgi18n: true, }); } else { log.debug("console", "Unknown error: " + error); diff --git a/frontend/src/lang/en.json b/frontend/src/lang/en.json index 8169bf8..d3b6717 100644 --- a/frontend/src/lang/en.json +++ b/frontend/src/lang/en.json @@ -107,5 +107,6 @@ "agentOffline": "Offline", "connecting": "Connecting", "connect": "Connect", - "addAgent": "Add Agent" + "addAgent": "Add Agent", + "agentAddedSuccessfully": "Agent added successfully." } diff --git a/frontend/src/mixins/socket.ts b/frontend/src/mixins/socket.ts index 39949b1..87c5586 100644 --- a/frontend/src/mixins/socket.ts +++ b/frontend/src/mixins/socket.ts @@ -259,6 +259,10 @@ export default defineComponent({ socket.on("agentStatus", (res) => { this.agentStatusList[res.endpoint] = res.status; + + if (res.msg) { + this.toastError(res.msg); + } }); socket.on("agentList", (res) => { diff --git a/frontend/src/pages/DashboardHome.vue b/frontend/src/pages/DashboardHome.vue index aa402fa..4067450 100644 --- a/frontend/src/pages/DashboardHome.vue +++ b/frontend/src/pages/DashboardHome.vue @@ -52,6 +52,7 @@ {{ $t("currentEndpoint") }} {{ endpoint }} + @@ -74,7 +75,10 @@ - + @@ -110,6 +114,7 @@ export default { displayedRecords: [], dockerRunCommand: "", showAgentForm: false, + connectingAgent: false, agent: { url: "http://", username: "", @@ -156,14 +161,16 @@ export default { methods: { addAgent() { + this.connectingAgent = true; this.$root.getSocket().emit("addAgent", this.agent, (res) => { + this.$root.toastRes(res); + if (res.ok) { - this.$root.toastRes(res); this.showAgentForm = false; } - }); - this.showAgentForm = false; + this.connectingAgent = false; + }); }, removeAgent(url) { diff --git a/package.json b/package.json index 1cd8923..50e469b 100644 --- a/package.json +++ b/package.json @@ -46,6 +46,7 @@ "mysql2": "~3.6.5", "promisify-child-process": "~4.1.2", "redbean-node": "~0.3.3", + "semver": "^7.5.4", "socket.io": "~4.7.2", "socket.io-client": "~4.7.2", "timezones-list": "~3.0.2", @@ -66,6 +67,7 @@ "@types/command-exists": "~1.2.3", "@types/express": "~4.17.21", "@types/jsonwebtoken": "~9.0.5", + "@types/semver": "^7.5.6", "@typescript-eslint/eslint-plugin": "~6.8.0", "@typescript-eslint/parser": "~6.8.0", "@vitejs/plugin-vue": "~4.5.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5d266b3..b67903d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -68,6 +68,9 @@ dependencies: redbean-node: specifier: ~0.3.3 version: 0.3.3(mysql2@3.6.5) + semver: + specifier: ^7.5.4 + version: 7.5.4 socket.io: specifier: ~4.7.2 version: 4.7.2 @@ -124,6 +127,9 @@ devDependencies: '@types/jsonwebtoken': specifier: ~9.0.5 version: 9.0.5 + '@types/semver': + specifier: ^7.5.6 + version: 7.5.6 '@typescript-eslint/eslint-plugin': specifier: ~6.8.0 version: 6.8.0(@typescript-eslint/parser@6.8.0)(eslint@8.50.0)(typescript@5.2.2)