mirror of
https://github.com/louislam/dockge.git
synced 2026-05-21 14:02:17 +00:00
WIP
This commit is contained in:
151
backend/agent-manager.ts
Normal file
151
backend/agent-manager.ts
Normal file
@@ -0,0 +1,151 @@
|
||||
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";
|
||||
|
||||
/**
|
||||
* Dockge Instance Manager
|
||||
*/
|
||||
export class AgentManager {
|
||||
|
||||
protected socket : DockgeSocket;
|
||||
protected instanceSocketList : Record<string, SocketClient> = {};
|
||||
|
||||
constructor(socket: DockgeSocket) {
|
||||
this.socket = socket;
|
||||
}
|
||||
|
||||
connect(url : string, username : string, password : string) {
|
||||
let obj = new URL(url);
|
||||
let endpoint = obj.host;
|
||||
|
||||
this.socket.emit("agentStatus", {
|
||||
endpoint: endpoint,
|
||||
status: "connecting",
|
||||
});
|
||||
|
||||
if (!endpoint) {
|
||||
log.error("agent-manager", "Invalid endpoint: " + endpoint + " URL: " + url);
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.instanceSocketList[endpoint]) {
|
||||
log.debug("agent-manager", "Already connected to the socket server: " + endpoint);
|
||||
return;
|
||||
}
|
||||
|
||||
log.info("agent-manager", "Connecting to the socket server: " + endpoint);
|
||||
let client = io(url, {
|
||||
extraHeaders: {
|
||||
endpoint,
|
||||
}
|
||||
});
|
||||
|
||||
client.on("connect", () => {
|
||||
log.info("agent-manager", "Connected to the socket server: " + endpoint);
|
||||
|
||||
client.emit("login", {
|
||||
username: username,
|
||||
password: password,
|
||||
}, (res) => {
|
||||
if (res.ok) {
|
||||
log.info("agent-manager", "Logged in to the socket server: " + endpoint);
|
||||
this.socket.emit("agentStatus", {
|
||||
endpoint: endpoint,
|
||||
status: "online",
|
||||
});
|
||||
} else {
|
||||
log.error("agent-manager", "Failed to login to the socket server: " + endpoint);
|
||||
this.socket.emit("agentStatus", {
|
||||
endpoint: endpoint,
|
||||
status: "offline",
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
client.on("error", (err) => {
|
||||
log.error("agent-manager", "Error from the socket server: " + endpoint);
|
||||
log.error("agent-manager", err);
|
||||
this.socket.emit("agentStatus", {
|
||||
endpoint: endpoint,
|
||||
status: "offline",
|
||||
});
|
||||
});
|
||||
|
||||
client.on("disconnect", () => {
|
||||
log.info("agent-manager", "Disconnected from the socket server: " + endpoint);
|
||||
this.socket.emit("agentStatus", {
|
||||
endpoint: endpoint,
|
||||
status: "offline",
|
||||
});
|
||||
});
|
||||
|
||||
client.on("agent", (...args : unknown[]) => {
|
||||
log.debug("agent-manager", "Forward event");
|
||||
this.socket.emit("agent", ...args);
|
||||
});
|
||||
|
||||
this.instanceSocketList[endpoint] = client;
|
||||
}
|
||||
|
||||
disconnect(endpoint : string) {
|
||||
let client = this.instanceSocketList[endpoint];
|
||||
client?.disconnect();
|
||||
}
|
||||
|
||||
async connectAll() {
|
||||
if (this.socket.endpoint) {
|
||||
log.info("agent-manager", "This connection is connected as an agent, skip connectAll()");
|
||||
return;
|
||||
}
|
||||
|
||||
let list : Record<string, Agent> = await Agent.getAgentList();
|
||||
|
||||
if (Object.keys(list).length !== 0) {
|
||||
log.info("agent-manager", "Connecting to all instance socket server(s)...");
|
||||
}
|
||||
|
||||
for (let endpoint in list) {
|
||||
let agent = list[endpoint];
|
||||
this.connect(agent.url, agent.username, agent.password);
|
||||
}
|
||||
}
|
||||
|
||||
disconnectAll() {
|
||||
for (let endpoint in this.instanceSocketList) {
|
||||
this.disconnect(endpoint);
|
||||
}
|
||||
}
|
||||
|
||||
emitToEndpoint(endpoint: string, eventName: string, ...args : unknown[]) {
|
||||
log.debug("agent-manager", "Emitting event to endpoint: " + endpoint);
|
||||
let client = this.instanceSocketList[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);
|
||||
}
|
||||
|
||||
emitToAllEndpoints(eventName: string, ...args : unknown[]) {
|
||||
log.debug("agent-manager", "Emitting event to all endpoints");
|
||||
for (let endpoint in this.instanceSocketList) {
|
||||
this.emitToEndpoint(endpoint, eventName, ...args);
|
||||
}
|
||||
}
|
||||
|
||||
async sendAgentList() {
|
||||
let list = await Agent.getAgentList();
|
||||
let result : Record<string, LooseObject> = {};
|
||||
for (let endpoint in list) {
|
||||
let agent = list[endpoint];
|
||||
result[endpoint] = agent.toJSON();
|
||||
}
|
||||
this.socket.emit("agentList", {
|
||||
ok: true,
|
||||
agentList: result,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,121 +0,0 @@
|
||||
import { DockgeSocket } from "./util-server";
|
||||
import { io, Socket as SocketClient } from "socket.io-client";
|
||||
import { log } from "./log";
|
||||
|
||||
/**
|
||||
* Dockge Instance Manager
|
||||
*/
|
||||
export class DockgeInstanceManager {
|
||||
|
||||
protected socket : DockgeSocket;
|
||||
protected instanceSocketList : Record<string, SocketClient> = {};
|
||||
|
||||
constructor(socket: DockgeSocket) {
|
||||
this.socket = socket;
|
||||
}
|
||||
|
||||
connect(endpoint : string, tls : boolean, username : string, password : string) {
|
||||
if (this.instanceSocketList[endpoint]) {
|
||||
log.debug("INSTANCEMANAGER", "Already connected to the socket server: " + endpoint);
|
||||
return;
|
||||
}
|
||||
|
||||
let url = ((tls) ? "wss://" : "ws://") + endpoint;
|
||||
|
||||
log.info("INSTANCEMANAGER", "Connecting to the socket server: " + endpoint);
|
||||
let client = io(url, {
|
||||
transports: [ "websocket", "polling" ],
|
||||
extraHeaders: {
|
||||
endpoint,
|
||||
}
|
||||
});
|
||||
|
||||
client.on("connect", () => {
|
||||
log.info("INSTANCEMANAGER", "Connected to the socket server: " + endpoint);
|
||||
|
||||
client.emit("login", {
|
||||
username: username,
|
||||
password: password,
|
||||
}, (res) => {
|
||||
if (res.ok) {
|
||||
log.info("INSTANCEMANAGER", "Logged in to the socket server: " + endpoint);
|
||||
} else {
|
||||
log.error("INSTANCEMANAGER", "Failed to login to the socket server: " + endpoint);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
client.on("error", (err) => {
|
||||
log.error("INSTANCEMANAGER", "Error from the socket server: " + endpoint);
|
||||
log.error("INSTANCEMANAGER", err);
|
||||
});
|
||||
|
||||
client.on("disconnect", () => {
|
||||
log.info("INSTANCEMANAGER", "Disconnected from the socket server: " + endpoint);
|
||||
});
|
||||
|
||||
client.on("agent", (...args : unknown[]) => {
|
||||
log.debug("INSTANCEMANAGER", "Forward event");
|
||||
this.socket.emit("agent", ...args);
|
||||
});
|
||||
|
||||
this.instanceSocketList[endpoint] = client;
|
||||
}
|
||||
|
||||
disconnect(endpoint : string) {
|
||||
let client = this.instanceSocketList[endpoint];
|
||||
client?.disconnect();
|
||||
}
|
||||
|
||||
connectAll() {
|
||||
if (this.socket.endpoint) {
|
||||
log.info("INSTANCEMANAGER", "This connection is connected as an agent, skip connectAll()");
|
||||
return;
|
||||
}
|
||||
|
||||
let list : Record<string, {tls : boolean, username : string, password : string}> = {
|
||||
|
||||
};
|
||||
|
||||
if (process.env.DOCKGE_TEST_REMOTE_HOST) {
|
||||
list[process.env.DOCKGE_TEST_REMOTE_HOST] = {
|
||||
tls: false,
|
||||
username: "admin",
|
||||
password: process.env.DOCKGE_TEST_REMOTE_PW || "",
|
||||
};
|
||||
}
|
||||
|
||||
if (Object.keys(list).length !== 0) {
|
||||
log.info("INSTANCEMANAGER", "Connecting to all instance socket server(s)...");
|
||||
}
|
||||
|
||||
for (let endpoint in list) {
|
||||
let item = list[endpoint];
|
||||
this.connect(endpoint, item.tls, item.username, item.password);
|
||||
}
|
||||
}
|
||||
|
||||
disconnectAll() {
|
||||
for (let endpoint in this.instanceSocketList) {
|
||||
this.disconnect(endpoint);
|
||||
}
|
||||
}
|
||||
|
||||
emitToEndpoint(endpoint: string, eventName: string, ...args : unknown[]) {
|
||||
log.debug("INSTANCEMANAGER", "Emitting event to endpoint: " + endpoint);
|
||||
let client = this.instanceSocketList[endpoint];
|
||||
if (!client) {
|
||||
log.error("INSTANCEMANAGER", "Socket client not found for endpoint: " + endpoint);
|
||||
throw new Error("Socket client not found for endpoint: " + endpoint);
|
||||
}
|
||||
client?.emit("agent", endpoint, eventName, ...args);
|
||||
}
|
||||
|
||||
emitToAllEndpoints(eventName: string, ...args : unknown[]) {
|
||||
log.debug("INSTANCEMANAGER", "Emitting event to all endpoints");
|
||||
for (let endpoint in this.instanceSocketList) {
|
||||
this.emitToEndpoint(endpoint, eventName, ...args);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -31,10 +31,11 @@ import { Cron } from "croner";
|
||||
import gracefulShutdown from "http-graceful-shutdown";
|
||||
import User from "./models/user";
|
||||
import childProcessAsync from "promisify-child-process";
|
||||
import { DockgeInstanceManager } from "./dockge-instance-manager";
|
||||
import { AgentManager } from "./agent-manager";
|
||||
import { AgentProxySocketHandler } from "./socket-handlers/agent-proxy-socket-handler";
|
||||
import { AgentSocketHandler } from "./agent-socket-handler";
|
||||
import { AgentSocket } from "../common/agent-socket";
|
||||
import { ManageAgentSocketHandler } from "./socket-handlers/manage-agent-socket-handler";
|
||||
|
||||
export class DockgeServer {
|
||||
app : Express;
|
||||
@@ -56,6 +57,7 @@ export class DockgeServer {
|
||||
*/
|
||||
socketHandlerList : SocketHandler[] = [
|
||||
new MainSocketHandler(),
|
||||
new ManageAgentSocketHandler(),
|
||||
];
|
||||
|
||||
agentProxySocketHandler = new AgentProxySocketHandler();
|
||||
@@ -206,7 +208,7 @@ export class DockgeServer {
|
||||
cors,
|
||||
allowRequest: (req, callback) => {
|
||||
let isOriginValid = true;
|
||||
const bypass = isDev;
|
||||
const bypass = isDev || process.env.UPTIME_KUMA_WS_ORIGIN_CHECK === "bypass";
|
||||
|
||||
if (!bypass) {
|
||||
let host = req.headers.host;
|
||||
@@ -241,14 +243,14 @@ export class DockgeServer {
|
||||
|
||||
this.io.on("connection", async (socket: Socket) => {
|
||||
let dockgeSocket = socket as DockgeSocket;
|
||||
dockgeSocket.instanceManager = new DockgeInstanceManager(dockgeSocket);
|
||||
dockgeSocket.instanceManager = new AgentManager(dockgeSocket);
|
||||
dockgeSocket.emitAgent = (event : string, ...args : unknown[]) => {
|
||||
let obj = args[0];
|
||||
if (typeof(obj) === "object") {
|
||||
let obj2 = obj as LooseObject;
|
||||
obj2.endpoint = dockgeSocket.endpoint;
|
||||
}
|
||||
dockgeSocket.emit("agent", event, ...args);
|
||||
this.io.to(dockgeSocket.userID + "").emit("agent", event, ...args);
|
||||
};
|
||||
|
||||
if (typeof(socket.request.headers.endpoint) === "string") {
|
||||
@@ -330,6 +332,8 @@ export class DockgeServer {
|
||||
log.error("server", e);
|
||||
}
|
||||
|
||||
socket.instanceManager.sendAgentList();
|
||||
|
||||
// Also connect to other dockge instances
|
||||
socket.instanceManager.connectAll();
|
||||
}
|
||||
@@ -605,25 +609,6 @@ export class DockgeServer {
|
||||
}
|
||||
}
|
||||
|
||||
async sendStackStatusList() {
|
||||
let statusList = await Stack.getStatusList();
|
||||
|
||||
let roomList = this.io.sockets.adapter.rooms.keys();
|
||||
|
||||
for (let room of roomList) {
|
||||
// Check if the room is a number (user id)
|
||||
if (Number(room)) {
|
||||
log.debug("server", "Send stack status list to room " + room);
|
||||
this.io.to(room).emit("stackStatusList", {
|
||||
ok: true,
|
||||
stackStatusList: Object.fromEntries(statusList),
|
||||
});
|
||||
} else {
|
||||
log.debug("server", "Skip sending stack status list to room " + room);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async getDockerNetworkList() : Promise<string[]> {
|
||||
let res = await childProcessAsync.spawn("docker", [ "network", "ls", "--format", "{{.Name}}" ], {
|
||||
encoding: "utf-8",
|
||||
|
||||
16
backend/migrations/2023-12-20-2117-agent-table.ts
Normal file
16
backend/migrations/2023-12-20-2117-agent-table.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { Knex } from "knex";
|
||||
|
||||
export async function up(knex: Knex): Promise<void> {
|
||||
// Create the user table
|
||||
return knex.schema.createTable("agent", (table) => {
|
||||
table.increments("id");
|
||||
table.string("url", 255).notNullable().unique();
|
||||
table.string("username", 255).notNullable();
|
||||
table.string("password", 255).notNullable();
|
||||
table.boolean("active").notNullable().defaultTo(true);
|
||||
});
|
||||
}
|
||||
|
||||
export async function down(knex: Knex): Promise<void> {
|
||||
return knex.schema.dropTable("agent");
|
||||
}
|
||||
33
backend/models/agent.ts
Normal file
33
backend/models/agent.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { BeanModel } from "redbean-node/dist/bean-model";
|
||||
import { R } from "redbean-node";
|
||||
import { LooseObject } from "../../common/util-common";
|
||||
import User from "./user";
|
||||
|
||||
export class Agent extends BeanModel {
|
||||
|
||||
static async getAgentList() : Promise<Record<string, Agent>> {
|
||||
let list = await R.findAll("agent") as Agent[];
|
||||
let result : Record<string, Agent> = {};
|
||||
for (let agent of list) {
|
||||
result[agent.endpoint] = agent;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
get endpoint() : string {
|
||||
let obj = new URL(this.url);
|
||||
return obj.host;
|
||||
}
|
||||
|
||||
toJSON() : LooseObject {
|
||||
return {
|
||||
url: this.url,
|
||||
username: this.username,
|
||||
password: this.password,
|
||||
endpoint: this.endpoint,
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default Agent;
|
||||
13
backend/socket-handlers/manage-agent-socket-handler.ts
Normal file
13
backend/socket-handlers/manage-agent-socket-handler.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
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";
|
||||
|
||||
export class ManageAgentSocketHandler extends SocketHandler {
|
||||
|
||||
create(socket : DockgeSocket, server : DockgeServer) {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@ import { ERROR_TYPE_VALIDATION } from "../common/util-common";
|
||||
import { R } from "redbean-node";
|
||||
import { verifyPassword } from "./password-hash";
|
||||
import fs from "fs";
|
||||
import { DockgeInstanceManager } from "./dockge-instance-manager";
|
||||
import { AgentManager } from "./agent-manager";
|
||||
|
||||
export interface JWTDecoded {
|
||||
username : string;
|
||||
@@ -16,7 +16,7 @@ export interface JWTDecoded {
|
||||
export interface DockgeSocket extends Socket {
|
||||
userID: number;
|
||||
consoleTerminal? : Terminal;
|
||||
instanceManager : DockgeInstanceManager;
|
||||
instanceManager : AgentManager;
|
||||
endpoint : string;
|
||||
emitAgent : (eventName : string, ...args : unknown[]) => void;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user