This commit is contained in:
Louis Lam 2023-12-24 22:01:17 +08:00
parent 37f261480a
commit 7d91c8d037
9 changed files with 153 additions and 14 deletions

View file

@ -2,7 +2,9 @@ import { DockgeSocket } from "./util-server";
import { io, Socket as SocketClient } from "socket.io-client"; import { io, Socket as SocketClient } from "socket.io-client";
import { log } from "./log"; import { log } from "./log";
import { Agent } from "./models/agent"; 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 * Dockge Instance Manager
@ -16,6 +18,74 @@ export class AgentManager {
this.socket = socket; this.socket = socket;
} }
test(url : string, username : string, password : string) : Promise<void> {
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<Agent> {
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) { connect(url : string, username : string, password : string) {
let obj = new URL(url); let obj = new URL(url);
let endpoint = obj.host; let endpoint = obj.host;
@ -48,7 +118,7 @@ export class AgentManager {
client.emit("login", { client.emit("login", {
username: username, username: username,
password: password, password: password,
}, (res) => { }, (res : LooseObject) => {
if (res.ok) { if (res.ok) {
log.info("agent-manager", "Logged in to the socket server: " + endpoint); log.info("agent-manager", "Logged in to the socket server: " + endpoint);
this.socket.emit("agentStatus", { 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", "Error from the socket server: " + endpoint);
log.error("agent-manager", err); log.debug("agent-manager", err);
this.socket.emit("agentStatus", { this.socket.emit("agentStatus", {
endpoint: endpoint, endpoint: endpoint,
status: "offline", status: "offline",
@ -87,6 +157,20 @@ export class AgentManager {
this.socket.emit("agent", ...args); 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; this.instanceSocketList[endpoint] = client;
} }

View file

@ -251,7 +251,7 @@ export class DockgeServer {
let obj2 = obj as LooseObject; let obj2 = obj as LooseObject;
obj2.endpoint = dockgeSocket.endpoint; 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") { if (typeof(socket.request.headers.endpoint) === "string") {
@ -601,7 +601,7 @@ export class DockgeServer {
map.set(stackName, stack.toSimpleJSON(dockgeSocket.endpoint)); 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", { dockgeSocket.emitAgent("stackList", {
ok: true, ok: true,
stackList: Object.fromEntries(map), stackList: Object.fromEntries(map),

View file

@ -1,13 +1,46 @@
import { SocketHandler } from "../socket-handler.js"; import { SocketHandler } from "../socket-handler.js";
import { DockgeServer } from "../dockge-server"; import { DockgeServer } from "../dockge-server";
import { log } from "../log"; import { log } from "../log";
import { checkLogin, DockgeSocket } from "../util-server"; import { callbackError, checkLogin, DockgeSocket } from "../util-server";
import { AgentSocket } from "../../common/agent-socket";
import { ALL_ENDPOINTS } from "../../common/util-common";
export class ManageAgentSocketHandler extends SocketHandler { export class ManageAgentSocketHandler extends SocketHandler {
create(socket : DockgeSocket, server : DockgeServer) { 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);
}
});
} }
} }

View file

@ -60,12 +60,14 @@ export function callbackError(error : unknown, callback : unknown) {
callback({ callback({
ok: false, ok: false,
msg: error.message, msg: error.message,
msgi18n: true,
}); });
} else if (error instanceof ValidationError) { } else if (error instanceof ValidationError) {
callback({ callback({
ok: false, ok: false,
type: ERROR_TYPE_VALIDATION, type: ERROR_TYPE_VALIDATION,
msg: error.message, msg: error.message,
msgi18n: true,
}); });
} else { } else {
log.debug("console", "Unknown error: " + error); log.debug("console", "Unknown error: " + error);

View file

@ -107,5 +107,6 @@
"agentOffline": "Offline", "agentOffline": "Offline",
"connecting": "Connecting", "connecting": "Connecting",
"connect": "Connect", "connect": "Connect",
"addAgent": "Add Agent" "addAgent": "Add Agent",
"agentAddedSuccessfully": "Agent added successfully."
} }

View file

@ -259,6 +259,10 @@ export default defineComponent({
socket.on("agentStatus", (res) => { socket.on("agentStatus", (res) => {
this.agentStatusList[res.endpoint] = res.status; this.agentStatusList[res.endpoint] = res.status;
if (res.msg) {
this.toastError(res.msg);
}
}); });
socket.on("agentList", (res) => { socket.on("agentList", (res) => {

View file

@ -52,6 +52,7 @@
<span v-if="endpoint === ''">{{ $t("currentEndpoint") }}</span> <span v-if="endpoint === ''">{{ $t("currentEndpoint") }}</span>
<span v-else>{{ endpoint }}</span> <span v-else>{{ endpoint }}</span>
<!-- Remove Button -->
<font-awesome-icon v-if="endpoint !== ''" class="ms-2 remove-agent" icon="trash" @click="removeAgent(agent.url)" /> <font-awesome-icon v-if="endpoint !== ''" class="ms-2 remove-agent" icon="trash" @click="removeAgent(agent.url)" />
</div> </div>
@ -74,7 +75,10 @@
<input id="password" v-model="agent.password" type="password" class="form-control" required autocomplete="new-password"> <input id="password" v-model="agent.password" type="password" class="form-control" required autocomplete="new-password">
</div> </div>
<button type="submit" class="btn btn-normal">{{ $t("connect") }}</button> <button type="submit" class="btn btn-primary" :disabled="connectingAgent">
<template v-if="connectingAgent">{{ $t("connecting") }}</template>
<template v-else>{{ $t("connect") }}</template>
</button>
</form> </form>
</div> </div>
</div> </div>
@ -110,6 +114,7 @@ export default {
displayedRecords: [], displayedRecords: [],
dockerRunCommand: "", dockerRunCommand: "",
showAgentForm: false, showAgentForm: false,
connectingAgent: false,
agent: { agent: {
url: "http://", url: "http://",
username: "", username: "",
@ -156,14 +161,16 @@ export default {
methods: { methods: {
addAgent() { addAgent() {
this.connectingAgent = true;
this.$root.getSocket().emit("addAgent", this.agent, (res) => { this.$root.getSocket().emit("addAgent", this.agent, (res) => {
if (res.ok) {
this.$root.toastRes(res); this.$root.toastRes(res);
if (res.ok) {
this.showAgentForm = false; this.showAgentForm = false;
} }
});
this.showAgentForm = false; this.connectingAgent = false;
});
}, },
removeAgent(url) { removeAgent(url) {

View file

@ -46,6 +46,7 @@
"mysql2": "~3.6.5", "mysql2": "~3.6.5",
"promisify-child-process": "~4.1.2", "promisify-child-process": "~4.1.2",
"redbean-node": "~0.3.3", "redbean-node": "~0.3.3",
"semver": "^7.5.4",
"socket.io": "~4.7.2", "socket.io": "~4.7.2",
"socket.io-client": "~4.7.2", "socket.io-client": "~4.7.2",
"timezones-list": "~3.0.2", "timezones-list": "~3.0.2",
@ -66,6 +67,7 @@
"@types/command-exists": "~1.2.3", "@types/command-exists": "~1.2.3",
"@types/express": "~4.17.21", "@types/express": "~4.17.21",
"@types/jsonwebtoken": "~9.0.5", "@types/jsonwebtoken": "~9.0.5",
"@types/semver": "^7.5.6",
"@typescript-eslint/eslint-plugin": "~6.8.0", "@typescript-eslint/eslint-plugin": "~6.8.0",
"@typescript-eslint/parser": "~6.8.0", "@typescript-eslint/parser": "~6.8.0",
"@vitejs/plugin-vue": "~4.5.2", "@vitejs/plugin-vue": "~4.5.2",

View file

@ -68,6 +68,9 @@ dependencies:
redbean-node: redbean-node:
specifier: ~0.3.3 specifier: ~0.3.3
version: 0.3.3(mysql2@3.6.5) version: 0.3.3(mysql2@3.6.5)
semver:
specifier: ^7.5.4
version: 7.5.4
socket.io: socket.io:
specifier: ~4.7.2 specifier: ~4.7.2
version: 4.7.2 version: 4.7.2
@ -124,6 +127,9 @@ devDependencies:
'@types/jsonwebtoken': '@types/jsonwebtoken':
specifier: ~9.0.5 specifier: ~9.0.5
version: 9.0.5 version: 9.0.5
'@types/semver':
specifier: ^7.5.6
version: 7.5.6
'@typescript-eslint/eslint-plugin': '@typescript-eslint/eslint-plugin':
specifier: ~6.8.0 specifier: ~6.8.0
version: 6.8.0(@typescript-eslint/parser@6.8.0)(eslint@8.50.0)(typescript@5.2.2) version: 6.8.0(@typescript-eslint/parser@6.8.0)(eslint@8.50.0)(typescript@5.2.2)