mirror of
https://github.com/louislam/dockge.git
synced 2024-11-27 21:24:02 +00:00
Redesign
This commit is contained in:
parent
80e885e85d
commit
0f79b46769
28 changed files with 471 additions and 135 deletions
7
backend/agent-socket-handler.ts
Normal file
7
backend/agent-socket-handler.ts
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
import { DockgeServer } from "./dockge-server";
|
||||||
|
import { AgentSocket } from "../../common/agent-socket";
|
||||||
|
import { DockgeSocket } from "./util-server";
|
||||||
|
|
||||||
|
export abstract class AgentSocketHandler {
|
||||||
|
abstract create(socket : DockgeSocket, server : DockgeServer, agentSocket : AgentSocket): void;
|
||||||
|
}
|
|
@ -1,17 +1,16 @@
|
||||||
import { SocketHandler } from "../socket-handler.js";
|
import { AgentSocketHandler } from "../agent-socket-handler";
|
||||||
import { DockgeServer } from "../dockge-server";
|
import { DockgeServer } from "../dockge-server";
|
||||||
import { callbackError, checkLogin, DockgeSocket, ValidationError } from "../util-server";
|
import { callbackError, checkLogin, DockgeSocket, ValidationError } from "../util-server";
|
||||||
import { Stack } from "../stack";
|
import { Stack } from "../stack";
|
||||||
|
import { AgentSocket } from "../../common/agent-socket";
|
||||||
|
|
||||||
// @ts-ignore
|
export class DockerSocketHandler extends AgentSocketHandler {
|
||||||
import composerize from "composerize";
|
create(s : DockgeSocket, server : DockgeServer, agentSocket : AgentSocket) {
|
||||||
|
// Do not call super.create()
|
||||||
|
|
||||||
export class DockerSocketHandler extends SocketHandler {
|
agentSocket.on("deployStack", async (name : unknown, composeYAML : unknown, composeENV : unknown, isAdd : unknown, callback) => {
|
||||||
create(socket : DockgeSocket, server : DockgeServer) {
|
|
||||||
|
|
||||||
socket.on("deployStack", async (name : unknown, composeYAML : unknown, composeENV : unknown, isAdd : unknown, callback) => {
|
|
||||||
try {
|
try {
|
||||||
checkLogin(socket);
|
checkLogin(s);
|
||||||
const stack = await this.saveStack(socket, server, name, composeYAML, composeENV, isAdd);
|
const stack = await this.saveStack(socket, server, name, composeYAML, composeENV, isAdd);
|
||||||
await stack.deploy(socket);
|
await stack.deploy(socket);
|
||||||
server.sendStackList();
|
server.sendStackList();
|
||||||
|
@ -25,7 +24,7 @@ export class DockerSocketHandler extends SocketHandler {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on("saveStack", async (name : unknown, composeYAML : unknown, composeENV : unknown, isAdd : unknown, callback) => {
|
agentSocket.on("saveStack", async (name : unknown, composeYAML : unknown, composeENV : unknown, isAdd : unknown, callback) => {
|
||||||
try {
|
try {
|
||||||
checkLogin(socket);
|
checkLogin(socket);
|
||||||
this.saveStack(socket, server, name, composeYAML, composeENV, isAdd);
|
this.saveStack(socket, server, name, composeYAML, composeENV, isAdd);
|
||||||
|
@ -39,7 +38,7 @@ export class DockerSocketHandler extends SocketHandler {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on("deleteStack", async (name : unknown, callback) => {
|
agentSocket.on("deleteStack", async (name : unknown, callback) => {
|
||||||
try {
|
try {
|
||||||
checkLogin(socket);
|
checkLogin(socket);
|
||||||
if (typeof(name) !== "string") {
|
if (typeof(name) !== "string") {
|
||||||
|
@ -65,9 +64,9 @@ export class DockerSocketHandler extends SocketHandler {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on("getStack", async (stackName : unknown, callback) => {
|
agentSocket.on("getStack", async (stackName : unknown, callback) => {
|
||||||
try {
|
try {
|
||||||
checkLogin(socket);
|
checkLogin(s);
|
||||||
|
|
||||||
if (typeof(stackName) !== "string") {
|
if (typeof(stackName) !== "string") {
|
||||||
throw new ValidationError("Stack name must be a string");
|
throw new ValidationError("Stack name must be a string");
|
||||||
|
@ -76,7 +75,7 @@ export class DockerSocketHandler extends SocketHandler {
|
||||||
const stack = await Stack.getStack(server, stackName);
|
const stack = await Stack.getStack(server, stackName);
|
||||||
|
|
||||||
if (stack.isManagedByDockge) {
|
if (stack.isManagedByDockge) {
|
||||||
stack.joinCombinedTerminal(socket);
|
stack.joinCombinedTerminal(s);
|
||||||
}
|
}
|
||||||
|
|
||||||
callback({
|
callback({
|
||||||
|
@ -89,7 +88,7 @@ export class DockerSocketHandler extends SocketHandler {
|
||||||
});
|
});
|
||||||
|
|
||||||
// requestStackList
|
// requestStackList
|
||||||
socket.on("requestStackList", async (callback) => {
|
agentSocket.on("requestStackList", async (callback) => {
|
||||||
try {
|
try {
|
||||||
checkLogin(socket);
|
checkLogin(socket);
|
||||||
server.sendStackList();
|
server.sendStackList();
|
||||||
|
@ -103,7 +102,7 @@ export class DockerSocketHandler extends SocketHandler {
|
||||||
});
|
});
|
||||||
|
|
||||||
// startStack
|
// startStack
|
||||||
socket.on("startStack", async (stackName : unknown, callback) => {
|
agentSocket.on("startStack", async (stackName : unknown, callback) => {
|
||||||
try {
|
try {
|
||||||
checkLogin(socket);
|
checkLogin(socket);
|
||||||
|
|
||||||
|
@ -127,7 +126,7 @@ export class DockerSocketHandler extends SocketHandler {
|
||||||
});
|
});
|
||||||
|
|
||||||
// stopStack
|
// stopStack
|
||||||
socket.on("stopStack", async (stackName : unknown, callback) => {
|
agentSocket.on("stopStack", async (stackName : unknown, callback) => {
|
||||||
try {
|
try {
|
||||||
checkLogin(socket);
|
checkLogin(socket);
|
||||||
|
|
||||||
|
@ -148,16 +147,16 @@ export class DockerSocketHandler extends SocketHandler {
|
||||||
});
|
});
|
||||||
|
|
||||||
// restartStack
|
// restartStack
|
||||||
socket.on("restartStack", async (stackName : unknown, callback) => {
|
agentSocket.on("restartStack", async (stackName : unknown, callback) => {
|
||||||
try {
|
try {
|
||||||
checkLogin(socket);
|
checkLogin(s);
|
||||||
|
|
||||||
if (typeof(stackName) !== "string") {
|
if (typeof(stackName) !== "string") {
|
||||||
throw new ValidationError("Stack name must be a string");
|
throw new ValidationError("Stack name must be a string");
|
||||||
}
|
}
|
||||||
|
|
||||||
const stack = await Stack.getStack(server, stackName);
|
const stack = await Stack.getStack(server, stackName);
|
||||||
await stack.restart(socket);
|
await stack.restart(s);
|
||||||
callback({
|
callback({
|
||||||
ok: true,
|
ok: true,
|
||||||
msg: "Restarted"
|
msg: "Restarted"
|
||||||
|
@ -169,7 +168,7 @@ export class DockerSocketHandler extends SocketHandler {
|
||||||
});
|
});
|
||||||
|
|
||||||
// updateStack
|
// updateStack
|
||||||
socket.on("updateStack", async (stackName : unknown, callback) => {
|
agentSocket.on("updateStack", async (stackName : unknown, callback) => {
|
||||||
try {
|
try {
|
||||||
checkLogin(socket);
|
checkLogin(socket);
|
||||||
|
|
||||||
|
@ -190,7 +189,7 @@ export class DockerSocketHandler extends SocketHandler {
|
||||||
});
|
});
|
||||||
|
|
||||||
// down stack
|
// down stack
|
||||||
socket.on("downStack", async (stackName : unknown, callback) => {
|
agentSocket.on("downStack", async (stackName : unknown, callback) => {
|
||||||
try {
|
try {
|
||||||
checkLogin(socket);
|
checkLogin(socket);
|
||||||
|
|
||||||
|
@ -211,9 +210,9 @@ export class DockerSocketHandler extends SocketHandler {
|
||||||
});
|
});
|
||||||
|
|
||||||
// Services status
|
// Services status
|
||||||
socket.on("serviceStatusList", async (stackName : unknown, callback) => {
|
agentSocket.on("serviceStatusList", async (stackName : unknown, callback) => {
|
||||||
try {
|
try {
|
||||||
checkLogin(socket);
|
checkLogin(s);
|
||||||
|
|
||||||
if (typeof(stackName) !== "string") {
|
if (typeof(stackName) !== "string") {
|
||||||
throw new ValidationError("Stack name must be a string");
|
throw new ValidationError("Stack name must be a string");
|
||||||
|
@ -231,7 +230,7 @@ export class DockerSocketHandler extends SocketHandler {
|
||||||
});
|
});
|
||||||
|
|
||||||
// getExternalNetworkList
|
// getExternalNetworkList
|
||||||
socket.on("getDockerNetworkList", async (callback) => {
|
agentSocket.on("getDockerNetworkList", async (callback) => {
|
||||||
try {
|
try {
|
||||||
checkLogin(socket);
|
checkLogin(socket);
|
||||||
const dockerNetworkList = await server.getDockerNetworkList();
|
const dockerNetworkList = await server.getDockerNetworkList();
|
||||||
|
@ -243,25 +242,6 @@ export class DockerSocketHandler extends SocketHandler {
|
||||||
callbackError(e, callback);
|
callbackError(e, callback);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// composerize
|
|
||||||
socket.on("composerize", async (dockerRunCommand : unknown, callback) => {
|
|
||||||
try {
|
|
||||||
checkLogin(socket);
|
|
||||||
|
|
||||||
if (typeof(dockerRunCommand) !== "string") {
|
|
||||||
throw new ValidationError("dockerRunCommand must be a string");
|
|
||||||
}
|
|
||||||
|
|
||||||
const composeTemplate = composerize(dockerRunCommand);
|
|
||||||
callback({
|
|
||||||
ok: true,
|
|
||||||
composeTemplate,
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
callbackError(e, callback);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async saveStack(socket : DockgeSocket, server : DockgeServer, name : unknown, composeYAML : unknown, composeENV : unknown, isAdd : unknown) : Promise<Stack> {
|
async saveStack(socket : DockgeSocket, server : DockgeServer, name : unknown, composeYAML : unknown, composeENV : unknown, isAdd : unknown) : Promise<Stack> {
|
|
@ -9,7 +9,7 @@ import knex from "knex";
|
||||||
import Dialect from "knex/lib/dialects/sqlite3/index.js";
|
import Dialect from "knex/lib/dialects/sqlite3/index.js";
|
||||||
|
|
||||||
import sqlite from "@louislam/sqlite3";
|
import sqlite from "@louislam/sqlite3";
|
||||||
import { sleep } from "./util-common";
|
import { sleep } from "../common/util-common";
|
||||||
|
|
||||||
interface DBConfig {
|
interface DBConfig {
|
||||||
type?: "sqlite" | "mysql";
|
type?: "sqlite" | "mysql";
|
||||||
|
|
114
backend/dockge-instance-manager.ts
Normal file
114
backend/dockge-instance-manager.ts
Normal file
|
@ -0,0 +1,114 @@
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,3 +1,4 @@
|
||||||
|
import "dotenv/config";
|
||||||
import { MainRouter } from "./routers/main-router";
|
import { MainRouter } from "./routers/main-router";
|
||||||
import * as fs from "node:fs";
|
import * as fs from "node:fs";
|
||||||
import { PackageJson } from "type-fest";
|
import { PackageJson } from "type-fest";
|
||||||
|
@ -17,11 +18,11 @@ import { Settings } from "./settings";
|
||||||
import checkVersion from "./check-version";
|
import checkVersion from "./check-version";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import { R } from "redbean-node";
|
import { R } from "redbean-node";
|
||||||
import { genSecret, isDev } from "./util-common";
|
import { genSecret, isDev, LooseObject } from "../common/util-common";
|
||||||
import { generatePasswordHash } from "./password-hash";
|
import { generatePasswordHash } from "./password-hash";
|
||||||
import { Bean } from "redbean-node/dist/bean";
|
import { Bean } from "redbean-node/dist/bean";
|
||||||
import { Arguments, Config, DockgeSocket } from "./util-server";
|
import { Arguments, Config, DockgeSocket } from "./util-server";
|
||||||
import { DockerSocketHandler } from "./socket-handlers/docker-socket-handler";
|
import { DockerSocketHandler } from "./agent-socket-handlers/docker-socket-handler";
|
||||||
import expressStaticGzip from "express-static-gzip";
|
import expressStaticGzip from "express-static-gzip";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import { TerminalSocketHandler } from "./socket-handlers/terminal-socket-handler";
|
import { TerminalSocketHandler } from "./socket-handlers/terminal-socket-handler";
|
||||||
|
@ -30,9 +31,10 @@ import { Cron } from "croner";
|
||||||
import gracefulShutdown from "http-graceful-shutdown";
|
import gracefulShutdown from "http-graceful-shutdown";
|
||||||
import User from "./models/user";
|
import User from "./models/user";
|
||||||
import childProcessAsync from "promisify-child-process";
|
import childProcessAsync from "promisify-child-process";
|
||||||
import { Terminal } from "./terminal";
|
import { DockgeInstanceManager } from "./dockge-instance-manager";
|
||||||
|
import { AgentProxySocketHandler } from "./socket-handlers/agent-proxy-socket-handler";
|
||||||
import "dotenv/config";
|
import { AgentSocketHandler } from "./agent-socket-handler";
|
||||||
|
import { AgentSocket } from "../common/agent-socket";
|
||||||
|
|
||||||
export class DockgeServer {
|
export class DockgeServer {
|
||||||
app : Express;
|
app : Express;
|
||||||
|
@ -54,10 +56,15 @@ export class DockgeServer {
|
||||||
*/
|
*/
|
||||||
socketHandlerList : SocketHandler[] = [
|
socketHandlerList : SocketHandler[] = [
|
||||||
new MainSocketHandler(),
|
new MainSocketHandler(),
|
||||||
new DockerSocketHandler(),
|
|
||||||
new TerminalSocketHandler(),
|
new TerminalSocketHandler(),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
agentProxySocketHandler = new AgentProxySocketHandler();
|
||||||
|
|
||||||
|
agentSocketHandlerList : AgentSocketHandler[] = [
|
||||||
|
new DockerSocketHandler(),
|
||||||
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Show Setup Page
|
* Show Setup Page
|
||||||
*/
|
*/
|
||||||
|
@ -230,20 +237,52 @@ export class DockgeServer {
|
||||||
});
|
});
|
||||||
|
|
||||||
this.io.on("connection", async (socket: Socket) => {
|
this.io.on("connection", async (socket: Socket) => {
|
||||||
log.info("server", "Socket connected!");
|
let dockgeSocket = socket as DockgeSocket;
|
||||||
|
dockgeSocket.instanceManager = new DockgeInstanceManager(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.sendInfo(socket, true);
|
if (typeof(socket.request.headers.endpoint) === "string") {
|
||||||
|
dockgeSocket.endpoint = socket.request.headers.endpoint;
|
||||||
|
} else {
|
||||||
|
dockgeSocket.endpoint = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dockgeSocket.endpoint) {
|
||||||
|
log.info("server", "Socket connected (agent), as endpoint " + dockgeSocket.endpoint);
|
||||||
|
} else {
|
||||||
|
log.info("server", "Socket connected (direct)");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.sendInfo(dockgeSocket, true);
|
||||||
|
|
||||||
if (this.needSetup) {
|
if (this.needSetup) {
|
||||||
log.info("server", "Redirect to setup page");
|
log.info("server", "Redirect to setup page");
|
||||||
socket.emit("setup");
|
dockgeSocket.emit("setup");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create socket handlers
|
// Create socket handlers (original, no agent support)
|
||||||
for (const socketHandler of this.socketHandlerList) {
|
for (const socketHandler of this.socketHandlerList) {
|
||||||
socketHandler.create(socket as DockgeSocket, this);
|
socketHandler.create(dockgeSocket, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create Agent Socket
|
||||||
|
let agentSocket = new AgentSocket();
|
||||||
|
|
||||||
|
// Create agent socket handlers
|
||||||
|
for (const socketHandler of this.agentSocketHandlerList) {
|
||||||
|
socketHandler.create(dockgeSocket, this, agentSocket);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create agent proxy socket handlers
|
||||||
|
this.agentProxySocketHandler.create2(dockgeSocket, this, agentSocket);
|
||||||
|
|
||||||
// ***************************
|
// ***************************
|
||||||
// Better do anything after added all socket handlers here
|
// Better do anything after added all socket handlers here
|
||||||
// ***************************
|
// ***************************
|
||||||
|
@ -251,12 +290,18 @@ export class DockgeServer {
|
||||||
log.debug("auth", "check auto login");
|
log.debug("auth", "check auto login");
|
||||||
if (await Settings.get("disableAuth")) {
|
if (await Settings.get("disableAuth")) {
|
||||||
log.info("auth", "Disabled Auth: auto login to admin");
|
log.info("auth", "Disabled Auth: auto login to admin");
|
||||||
this.afterLogin(socket as DockgeSocket, await R.findOne("user") as User);
|
this.afterLogin(dockgeSocket, await R.findOne("user") as User);
|
||||||
socket.emit("autoLogin");
|
dockgeSocket.emit("autoLogin");
|
||||||
} else {
|
} else {
|
||||||
log.debug("auth", "need auth");
|
log.debug("auth", "need auth");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Socket disconnect
|
||||||
|
dockgeSocket.on("disconnect", () => {
|
||||||
|
log.info("server", "Socket disconnected!");
|
||||||
|
dockgeSocket.instanceManager.disconnectAll();
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
this.io.on("disconnect", () => {
|
this.io.on("disconnect", () => {
|
||||||
|
@ -265,7 +310,7 @@ export class DockgeServer {
|
||||||
|
|
||||||
if (isDev) {
|
if (isDev) {
|
||||||
setInterval(() => {
|
setInterval(() => {
|
||||||
log.debug("terminal", "Terminal count: " + Terminal.getTerminalCount());
|
//log.debug("terminal", "Terminal count: " + Terminal.getTerminalCount());
|
||||||
}, 5000);
|
}, 5000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -281,6 +326,9 @@ export class DockgeServer {
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log.error("server", e);
|
log.error("server", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Also connect to other dockge instances
|
||||||
|
socket.instanceManager.connectAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -519,26 +567,34 @@ export class DockgeServer {
|
||||||
return jwtSecretBean;
|
return jwtSecretBean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send stack list to all connected sockets
|
||||||
|
* @param useCache
|
||||||
|
*/
|
||||||
async sendStackList(useCache = false) {
|
async sendStackList(useCache = false) {
|
||||||
let roomList = this.io.sockets.adapter.rooms.keys();
|
let socketList = this.io.sockets.sockets.values();
|
||||||
let map : Map<string, object> | undefined;
|
|
||||||
|
let stackList;
|
||||||
|
|
||||||
|
for (let socket of socketList) {
|
||||||
|
let dockgeSocket = socket as DockgeSocket;
|
||||||
|
|
||||||
for (let room of roomList) {
|
|
||||||
// Check if the room is a number (user id)
|
// Check if the room is a number (user id)
|
||||||
if (Number(room)) {
|
if (dockgeSocket.userID) {
|
||||||
|
|
||||||
// Get the list only if there is a room
|
// Get the list only if there is a logged in user
|
||||||
if (!map) {
|
if (!stackList) {
|
||||||
map = new Map();
|
stackList = await Stack.getStackList(this, useCache);
|
||||||
let stackList = await Stack.getStackList(this, useCache);
|
|
||||||
|
|
||||||
for (let [ stackName, stack ] of stackList) {
|
|
||||||
map.set(stackName, stack.toSimpleJSON());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
log.debug("server", "Send stack list to room " + room);
|
let map : Map<string, object> = new Map();
|
||||||
this.io.to(room).emit("stackList", {
|
|
||||||
|
for (let [ stackName, stack ] of stackList) {
|
||||||
|
map.set(stackName, stack.toSimpleJSON(dockgeSocket.endpoint));
|
||||||
|
}
|
||||||
|
|
||||||
|
log.debug("server", "Send stack list");
|
||||||
|
dockgeSocket.emitAgent("stackList", {
|
||||||
ok: true,
|
ok: true,
|
||||||
stackList: Object.fromEntries(map),
|
stackList: Object.fromEntries(map),
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
// Console colors
|
// Console colors
|
||||||
// https://stackoverflow.com/questions/9781218/how-to-change-node-jss-console-font-color
|
// https://stackoverflow.com/questions/9781218/how-to-change-node-jss-console-font-color
|
||||||
import { intHash, isDev } from "./util-common";
|
import { intHash, isDev } from "../common/util-common";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
|
|
||||||
export const CONSOLE_STYLE_Reset = "\x1b[0m";
|
export const CONSOLE_STYLE_Reset = "\x1b[0m";
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { R } from "redbean-node";
|
import { R } from "redbean-node";
|
||||||
import { log } from "./log";
|
import { log } from "./log";
|
||||||
import { LooseObject } from "./util-common";
|
import { LooseObject } from "../common/util-common";
|
||||||
|
|
||||||
export class Settings {
|
export class Settings {
|
||||||
|
|
||||||
|
|
43
backend/socket-handlers/agent-proxy-socket-handler.ts
Normal file
43
backend/socket-handlers/agent-proxy-socket-handler.ts
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
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";
|
||||||
|
|
||||||
|
export class AgentProxySocketHandler extends SocketHandler {
|
||||||
|
|
||||||
|
create2(socket : DockgeSocket, server : DockgeServer, agentSocket : AgentSocket) {
|
||||||
|
// Agent - proxying requests if needed
|
||||||
|
socket.on("agent", async (endpoint : unknown, eventName : unknown, ...args : unknown[]) => {
|
||||||
|
try {
|
||||||
|
checkLogin(socket);
|
||||||
|
|
||||||
|
// Check Type
|
||||||
|
if (typeof(endpoint) !== "string") {
|
||||||
|
throw new Error("Endpoint must be a string");
|
||||||
|
}
|
||||||
|
if (typeof(eventName) !== "string") {
|
||||||
|
throw new Error("Event name must be a string");
|
||||||
|
}
|
||||||
|
|
||||||
|
log.debug("agent", "Proxying request to " + endpoint + " for " + eventName);
|
||||||
|
|
||||||
|
// Direct connection or matching endpoint
|
||||||
|
if (!endpoint || endpoint === socket.endpoint) {
|
||||||
|
log.debug("agent", "Direct connection");
|
||||||
|
agentSocket.call(eventName, ...args);
|
||||||
|
} else {
|
||||||
|
socket.instanceManager.emitToEndpoint(endpoint, eventName, ...args);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
if (e instanceof Error) {
|
||||||
|
log.warn("agent", e.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
create(socket : DockgeSocket, server : DockgeServer) {
|
||||||
|
throw new Error("Method not implemented. Please use create2 instead.");
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,3 +1,5 @@
|
||||||
|
// @ts-ignore
|
||||||
|
import composerize from "composerize";
|
||||||
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";
|
||||||
|
@ -5,7 +7,14 @@ import { R } from "redbean-node";
|
||||||
import { loginRateLimiter, twoFaRateLimiter } from "../rate-limiter";
|
import { loginRateLimiter, twoFaRateLimiter } from "../rate-limiter";
|
||||||
import { generatePasswordHash, needRehashPassword, shake256, SHAKE256_LENGTH, verifyPassword } from "../password-hash";
|
import { generatePasswordHash, needRehashPassword, shake256, SHAKE256_LENGTH, verifyPassword } from "../password-hash";
|
||||||
import { User } from "../models/user";
|
import { User } from "../models/user";
|
||||||
import { checkLogin, DockgeSocket, doubleCheckPassword, JWTDecoded } from "../util-server";
|
import {
|
||||||
|
callbackError,
|
||||||
|
checkLogin,
|
||||||
|
DockgeSocket,
|
||||||
|
doubleCheckPassword,
|
||||||
|
JWTDecoded,
|
||||||
|
ValidationError
|
||||||
|
} from "../util-server";
|
||||||
import { passwordStrength } from "check-password-strength";
|
import { passwordStrength } from "check-password-strength";
|
||||||
import jwt from "jsonwebtoken";
|
import jwt from "jsonwebtoken";
|
||||||
import { Settings } from "../settings";
|
import { Settings } from "../settings";
|
||||||
|
@ -294,6 +303,25 @@ export class MainSocketHandler extends SocketHandler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// composerize
|
||||||
|
socket.on("composerize", async (dockerRunCommand : unknown, callback) => {
|
||||||
|
try {
|
||||||
|
checkLogin(socket);
|
||||||
|
|
||||||
|
if (typeof(dockerRunCommand) !== "string") {
|
||||||
|
throw new ValidationError("dockerRunCommand must be a string");
|
||||||
|
}
|
||||||
|
|
||||||
|
const composeTemplate = composerize(dockerRunCommand);
|
||||||
|
callback({
|
||||||
|
ok: true,
|
||||||
|
composeTemplate,
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
callbackError(e, callback);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async login(username : string, password : string) : Promise<User | null> {
|
async login(username : string, password : string) : Promise<User | null> {
|
||||||
|
|
|
@ -11,7 +11,7 @@ import {
|
||||||
getComposeTerminalName, getContainerExecTerminalName,
|
getComposeTerminalName, getContainerExecTerminalName,
|
||||||
isDev,
|
isDev,
|
||||||
PROGRESS_TERMINAL_ROWS
|
PROGRESS_TERMINAL_ROWS
|
||||||
} from "../util-common";
|
} from "../../common/util-common";
|
||||||
import { InteractiveTerminal, MainTerminal, Terminal } from "../terminal";
|
import { InteractiveTerminal, MainTerminal, Terminal } from "../terminal";
|
||||||
import { Stack } from "../stack";
|
import { Stack } from "../stack";
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,7 @@ import {
|
||||||
PROGRESS_TERMINAL_ROWS,
|
PROGRESS_TERMINAL_ROWS,
|
||||||
RUNNING, TERMINAL_ROWS,
|
RUNNING, TERMINAL_ROWS,
|
||||||
UNKNOWN
|
UNKNOWN
|
||||||
} from "./util-common";
|
} from "../common/util-common";
|
||||||
import { InteractiveTerminal, Terminal } from "./terminal";
|
import { InteractiveTerminal, Terminal } from "./terminal";
|
||||||
import childProcessAsync from "promisify-child-process";
|
import childProcessAsync from "promisify-child-process";
|
||||||
|
|
||||||
|
@ -50,8 +50,8 @@ export class Stack {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
toJSON() : object {
|
toJSON(endpoint : string) : object {
|
||||||
let obj = this.toSimpleJSON();
|
let obj = this.toSimpleJSON(endpoint);
|
||||||
return {
|
return {
|
||||||
...obj,
|
...obj,
|
||||||
composeYAML: this.composeYAML,
|
composeYAML: this.composeYAML,
|
||||||
|
@ -59,13 +59,14 @@ export class Stack {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
toSimpleJSON() : object {
|
toSimpleJSON(endpoint : string) : object {
|
||||||
return {
|
return {
|
||||||
name: this.name,
|
name: this.name,
|
||||||
status: this._status,
|
status: this._status,
|
||||||
tags: [],
|
tags: [],
|
||||||
isManagedByDockge: this.isManagedByDockge,
|
isManagedByDockge: this.isManagedByDockge,
|
||||||
composeFileName: this._composeFileName,
|
composeFileName: this._composeFileName,
|
||||||
|
endpoint,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -186,8 +187,8 @@ export class Stack {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async deploy(socket? : DockgeSocket) : Promise<number> {
|
async deploy(socket : DockgeSocket) : Promise<number> {
|
||||||
const terminalName = getComposeTerminalName(this.name);
|
const terminalName = getComposeTerminalName(socket.endpoint, this.name);
|
||||||
let exitCode = await Terminal.exec(this.server, socket, terminalName, "docker", [ "compose", "up", "-d", "--remove-orphans" ], this.path);
|
let exitCode = await Terminal.exec(this.server, socket, terminalName, "docker", [ "compose", "up", "-d", "--remove-orphans" ], this.path);
|
||||||
if (exitCode !== 0) {
|
if (exitCode !== 0) {
|
||||||
throw new Error("Failed to deploy, please check the terminal output for more information.");
|
throw new Error("Failed to deploy, please check the terminal output for more information.");
|
||||||
|
@ -195,8 +196,8 @@ export class Stack {
|
||||||
return exitCode;
|
return exitCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
async delete(socket?: DockgeSocket) : Promise<number> {
|
async delete(socket: DockgeSocket) : Promise<number> {
|
||||||
const terminalName = getComposeTerminalName(this.name);
|
const terminalName = getComposeTerminalName(socket.endpoint, this.name);
|
||||||
let exitCode = await Terminal.exec(this.server, socket, terminalName, "docker", [ "compose", "down", "--remove-orphans" ], this.path);
|
let exitCode = await Terminal.exec(this.server, socket, terminalName, "docker", [ "compose", "down", "--remove-orphans" ], this.path);
|
||||||
if (exitCode !== 0) {
|
if (exitCode !== 0) {
|
||||||
throw new Error("Failed to delete, please check the terminal output for more information.");
|
throw new Error("Failed to delete, please check the terminal output for more information.");
|
||||||
|
@ -388,7 +389,7 @@ export class Stack {
|
||||||
}
|
}
|
||||||
|
|
||||||
async start(socket: DockgeSocket) {
|
async start(socket: DockgeSocket) {
|
||||||
const terminalName = getComposeTerminalName(this.name);
|
const terminalName = getComposeTerminalName(socket.endpoint, this.name);
|
||||||
let exitCode = await Terminal.exec(this.server, socket, terminalName, "docker", [ "compose", "up", "-d", "--remove-orphans" ], this.path);
|
let exitCode = await Terminal.exec(this.server, socket, terminalName, "docker", [ "compose", "up", "-d", "--remove-orphans" ], this.path);
|
||||||
if (exitCode !== 0) {
|
if (exitCode !== 0) {
|
||||||
throw new Error("Failed to start, please check the terminal output for more information.");
|
throw new Error("Failed to start, please check the terminal output for more information.");
|
||||||
|
@ -397,7 +398,7 @@ export class Stack {
|
||||||
}
|
}
|
||||||
|
|
||||||
async stop(socket: DockgeSocket) : Promise<number> {
|
async stop(socket: DockgeSocket) : Promise<number> {
|
||||||
const terminalName = getComposeTerminalName(this.name);
|
const terminalName = getComposeTerminalName(socket.endpoint, this.name);
|
||||||
let exitCode = await Terminal.exec(this.server, socket, terminalName, "docker", [ "compose", "stop" ], this.path);
|
let exitCode = await Terminal.exec(this.server, socket, terminalName, "docker", [ "compose", "stop" ], this.path);
|
||||||
if (exitCode !== 0) {
|
if (exitCode !== 0) {
|
||||||
throw new Error("Failed to stop, please check the terminal output for more information.");
|
throw new Error("Failed to stop, please check the terminal output for more information.");
|
||||||
|
@ -406,7 +407,7 @@ export class Stack {
|
||||||
}
|
}
|
||||||
|
|
||||||
async restart(socket: DockgeSocket) : Promise<number> {
|
async restart(socket: DockgeSocket) : Promise<number> {
|
||||||
const terminalName = getComposeTerminalName(this.name);
|
const terminalName = getComposeTerminalName(socket.endpoint, this.name);
|
||||||
let exitCode = await Terminal.exec(this.server, socket, terminalName, "docker", [ "compose", "restart" ], this.path);
|
let exitCode = await Terminal.exec(this.server, socket, terminalName, "docker", [ "compose", "restart" ], this.path);
|
||||||
if (exitCode !== 0) {
|
if (exitCode !== 0) {
|
||||||
throw new Error("Failed to restart, please check the terminal output for more information.");
|
throw new Error("Failed to restart, please check the terminal output for more information.");
|
||||||
|
@ -415,7 +416,7 @@ export class Stack {
|
||||||
}
|
}
|
||||||
|
|
||||||
async down(socket: DockgeSocket) : Promise<number> {
|
async down(socket: DockgeSocket) : Promise<number> {
|
||||||
const terminalName = getComposeTerminalName(this.name);
|
const terminalName = getComposeTerminalName(socket.endpoint, this.name);
|
||||||
let exitCode = await Terminal.exec(this.server, socket, terminalName, "docker", [ "compose", "down" ], this.path);
|
let exitCode = await Terminal.exec(this.server, socket, terminalName, "docker", [ "compose", "down" ], this.path);
|
||||||
if (exitCode !== 0) {
|
if (exitCode !== 0) {
|
||||||
throw new Error("Failed to down, please check the terminal output for more information.");
|
throw new Error("Failed to down, please check the terminal output for more information.");
|
||||||
|
@ -424,7 +425,7 @@ export class Stack {
|
||||||
}
|
}
|
||||||
|
|
||||||
async update(socket: DockgeSocket) {
|
async update(socket: DockgeSocket) {
|
||||||
const terminalName = getComposeTerminalName(this.name);
|
const terminalName = getComposeTerminalName(socket.endpoint, this.name);
|
||||||
let exitCode = await Terminal.exec(this.server, socket, terminalName, "docker", [ "compose", "pull" ], this.path);
|
let exitCode = await Terminal.exec(this.server, socket, terminalName, "docker", [ "compose", "pull" ], this.path);
|
||||||
if (exitCode !== 0) {
|
if (exitCode !== 0) {
|
||||||
throw new Error("Failed to pull, please check the terminal output for more information.");
|
throw new Error("Failed to pull, please check the terminal output for more information.");
|
||||||
|
@ -445,7 +446,7 @@ export class Stack {
|
||||||
}
|
}
|
||||||
|
|
||||||
async joinCombinedTerminal(socket: DockgeSocket) {
|
async joinCombinedTerminal(socket: DockgeSocket) {
|
||||||
const terminalName = getCombinedTerminalName(this.name);
|
const terminalName = getCombinedTerminalName(socket.endpoint, this.name);
|
||||||
const terminal = Terminal.getOrCreateTerminal(this.server, terminalName, "docker", [ "compose", "logs", "-f", "--tail", "100" ], this.path);
|
const terminal = Terminal.getOrCreateTerminal(this.server, terminalName, "docker", [ "compose", "logs", "-f", "--tail", "100" ], this.path);
|
||||||
terminal.enableKeepAlive = true;
|
terminal.enableKeepAlive = true;
|
||||||
terminal.rows = COMBINED_TERMINAL_ROWS;
|
terminal.rows = COMBINED_TERMINAL_ROWS;
|
||||||
|
@ -455,7 +456,7 @@ export class Stack {
|
||||||
}
|
}
|
||||||
|
|
||||||
async leaveCombinedTerminal(socket: DockgeSocket) {
|
async leaveCombinedTerminal(socket: DockgeSocket) {
|
||||||
const terminalName = getCombinedTerminalName(this.name);
|
const terminalName = getCombinedTerminalName(socket.endpoint, this.name);
|
||||||
const terminal = Terminal.getTerminal(terminalName);
|
const terminal = Terminal.getTerminal(terminalName);
|
||||||
if (terminal) {
|
if (terminal) {
|
||||||
terminal.leave(socket);
|
terminal.leave(socket);
|
||||||
|
@ -463,7 +464,7 @@ export class Stack {
|
||||||
}
|
}
|
||||||
|
|
||||||
async joinContainerTerminal(socket: DockgeSocket, serviceName: string, shell : string = "sh", index: number = 0) {
|
async joinContainerTerminal(socket: DockgeSocket, serviceName: string, shell : string = "sh", index: number = 0) {
|
||||||
const terminalName = getContainerExecTerminalName(this.name, serviceName, index);
|
const terminalName = getContainerExecTerminalName(socket.endpoint, this.name, serviceName, index);
|
||||||
let terminal = Terminal.getTerminal(terminalName);
|
let terminal = Terminal.getTerminal(terminalName);
|
||||||
|
|
||||||
if (!terminal) {
|
if (!terminal) {
|
||||||
|
|
|
@ -8,7 +8,7 @@ import {
|
||||||
PROGRESS_TERMINAL_ROWS,
|
PROGRESS_TERMINAL_ROWS,
|
||||||
TERMINAL_COLS,
|
TERMINAL_COLS,
|
||||||
TERMINAL_ROWS
|
TERMINAL_ROWS
|
||||||
} from "./util-common";
|
} from "../common/util-common";
|
||||||
import { sync as commandExistsSync } from "command-exists";
|
import { sync as commandExistsSync } from "command-exists";
|
||||||
import { log } from "./log";
|
import { log } from "./log";
|
||||||
|
|
||||||
|
|
|
@ -2,10 +2,11 @@ import { Socket } from "socket.io";
|
||||||
import { Terminal } from "./terminal";
|
import { Terminal } from "./terminal";
|
||||||
import { randomBytes } from "crypto";
|
import { randomBytes } from "crypto";
|
||||||
import { log } from "./log";
|
import { log } from "./log";
|
||||||
import { ERROR_TYPE_VALIDATION } from "./util-common";
|
import { ERROR_TYPE_VALIDATION } from "../common/util-common";
|
||||||
import { R } from "redbean-node";
|
import { R } from "redbean-node";
|
||||||
import { verifyPassword } from "./password-hash";
|
import { verifyPassword } from "./password-hash";
|
||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
|
import { DockgeInstanceManager } from "./dockge-instance-manager";
|
||||||
|
|
||||||
export interface JWTDecoded {
|
export interface JWTDecoded {
|
||||||
username : string;
|
username : string;
|
||||||
|
@ -15,6 +16,9 @@ export interface JWTDecoded {
|
||||||
export interface DockgeSocket extends Socket {
|
export interface DockgeSocket extends Socket {
|
||||||
userID: number;
|
userID: number;
|
||||||
consoleTerminal? : Terminal;
|
consoleTerminal? : Terminal;
|
||||||
|
instanceManager : DockgeInstanceManager;
|
||||||
|
endpoint : string;
|
||||||
|
emitAgent : (eventName : string, ...args : unknown[]) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
// For command line arguments, so they are nullable
|
// For command line arguments, so they are nullable
|
||||||
|
|
15
common/agent-socket.ts
Normal file
15
common/agent-socket.ts
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
export class AgentSocket {
|
||||||
|
|
||||||
|
eventList : Map<string, (...args : unknown[]) => void> = new Map();
|
||||||
|
|
||||||
|
on(event : string, callback : (...args : unknown[]) => void) {
|
||||||
|
this.eventList.set(event, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
call(eventName : string, ...args : unknown[]) {
|
||||||
|
const callback = this.eventList.get(eventName);
|
||||||
|
if (callback) {
|
||||||
|
callback(...args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -206,20 +206,20 @@ export function getCryptoRandomInt(min: number, max: number):number {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getComposeTerminalName(stack : string) {
|
export function getComposeTerminalName(endpoint : string, stack : string) {
|
||||||
return "compose-" + stack;
|
return "compose-" + endpoint + "-" + stack;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getCombinedTerminalName(stack : string) {
|
export function getCombinedTerminalName(endpoint : string, stack : string) {
|
||||||
return "combined-" + stack;
|
return "combined-" + endpoint + "-" + stack;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getContainerTerminalName(container : string) {
|
export function getContainerTerminalName(endpoint : string, container : string) {
|
||||||
return "container-" + container;
|
return "container-" + endpoint + "-" + container;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getContainerExecTerminalName(stackName : string, container : string, index : number) {
|
export function getContainerExecTerminalName(endpoint : string, stackName : string, container : string, index : number) {
|
||||||
return "container-exec-" + stackName + "-" + container + "-" + index;
|
return "container-exec-" + endpoint + "-" + stackName + "-" + container + "-" + index;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function copyYAMLComments(doc : Document, src : Document) {
|
export function copyYAMLComments(doc : Document, src : Document) {
|
|
@ -5,7 +5,7 @@ import { User } from "../backend/models/user";
|
||||||
import { DockgeServer } from "../backend/dockge-server";
|
import { DockgeServer } from "../backend/dockge-server";
|
||||||
import { log } from "../backend/log";
|
import { log } from "../backend/log";
|
||||||
import { io } from "socket.io-client";
|
import { io } from "socket.io-client";
|
||||||
import { BaseRes } from "../backend/util-common";
|
import { BaseRes } from "../common/util-common";
|
||||||
|
|
||||||
console.log("== Dockge Reset Password Tool ==");
|
console.log("== Dockge Reset Password Tool ==");
|
||||||
|
|
||||||
|
|
|
@ -137,7 +137,7 @@
|
||||||
<script>
|
<script>
|
||||||
import { defineComponent } from "vue";
|
import { defineComponent } from "vue";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
|
||||||
import { parseDockerPort } from "../../../backend/util-common";
|
import { parseDockerPort } from "../../../common/util-common";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: {
|
components: {
|
||||||
|
|
|
@ -67,7 +67,7 @@
|
||||||
<script>
|
<script>
|
||||||
import Confirm from "../components/Confirm.vue";
|
import Confirm from "../components/Confirm.vue";
|
||||||
import StackListItem from "../components/StackListItem.vue";
|
import StackListItem from "../components/StackListItem.vue";
|
||||||
import { CREATED_FILE, CREATED_STACK, EXITED, RUNNING, UNKNOWN } from "../../../backend/util-common";
|
import { CREATED_FILE, CREATED_STACK, EXITED, RUNNING, UNKNOWN } from "../../../common/util-common";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
|
@ -120,7 +120,7 @@ export default {
|
||||||
* @returns {Array} The sorted list of stacks.
|
* @returns {Array} The sorted list of stacks.
|
||||||
*/
|
*/
|
||||||
sortedStackList() {
|
sortedStackList() {
|
||||||
let result = Object.values(this.$root.stackList);
|
let result = Object.values(this.$root.completeStackList);
|
||||||
|
|
||||||
result = result.filter(stack => {
|
result = result.filter(stack => {
|
||||||
// filter by search text
|
// filter by search text
|
||||||
|
@ -160,6 +160,7 @@ export default {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// sort by status
|
||||||
if (m1.status !== m2.status) {
|
if (m1.status !== m2.status) {
|
||||||
if (m2.status === RUNNING) {
|
if (m2.status === RUNNING) {
|
||||||
return 1;
|
return 1;
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
<template>
|
<template>
|
||||||
<router-link :to="`/compose/${stack.name}`" :class="{ 'dim' : !stack.isManagedByDockge }" class="item">
|
<router-link :to="url" :class="{ 'dim' : !stack.isManagedByDockge }" class="item">
|
||||||
<Uptime :stack="stack" :fixed-width="true" class="me-2" />
|
<Uptime :stack="stack" :fixed-width="true" class="me-2" />
|
||||||
<span class="title">{{ stackName }}</span>
|
<div class="title">
|
||||||
|
<span>{{ stackName }}</span>
|
||||||
|
<div class="endpoint">{{ endpointDisplay }}</div>
|
||||||
|
</div>
|
||||||
</router-link>
|
</router-link>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -51,6 +54,20 @@ export default {
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
endpointDisplay() {
|
||||||
|
if (this.stack.endpoint) {
|
||||||
|
return this.stack.endpoint;
|
||||||
|
} else {
|
||||||
|
return "Default";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
url() {
|
||||||
|
if (this.stack.endpoint) {
|
||||||
|
return `/compose/${this.stack.name}/${this.stack.endpoint}`;
|
||||||
|
} else {
|
||||||
|
return `/compose/${this.stack.name}`;
|
||||||
|
}
|
||||||
|
},
|
||||||
depthMargin() {
|
depthMargin() {
|
||||||
return {
|
return {
|
||||||
marginLeft: `${31 * this.depth}px`,
|
marginLeft: `${31 * this.depth}px`,
|
||||||
|
@ -117,16 +134,31 @@ export default {
|
||||||
padding-right: 2px !important;
|
padding-right: 2px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
// .stack-item {
|
.item {
|
||||||
// width: 100%;
|
text-decoration: none;
|
||||||
// }
|
|
||||||
|
|
||||||
.tags {
|
|
||||||
margin-top: 4px;
|
|
||||||
padding-left: 67px;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
align-items: center;
|
||||||
gap: 0;
|
min-height: 52px;
|
||||||
|
border-radius: 10px;
|
||||||
|
transition: all ease-in-out 0.15s;
|
||||||
|
width: 100%;
|
||||||
|
padding: 5px 8px;
|
||||||
|
&.disabled {
|
||||||
|
opacity: 0.3;
|
||||||
|
}
|
||||||
|
&:hover {
|
||||||
|
background-color: $highlight-white;
|
||||||
|
}
|
||||||
|
&.active {
|
||||||
|
background-color: #cdf8f4;
|
||||||
|
}
|
||||||
|
.title {
|
||||||
|
margin-top: -4px;
|
||||||
|
}
|
||||||
|
.endpoint {
|
||||||
|
font-size: 12px;
|
||||||
|
color: $dark-font-color3;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.collapsed {
|
.collapsed {
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
import { Terminal } from "@xterm/xterm";
|
import { Terminal } from "@xterm/xterm";
|
||||||
import { FitAddon } from "@xterm/addon-fit";
|
import { FitAddon } from "@xterm/addon-fit";
|
||||||
import { WebLinksAddon } from "xterm-addon-web-links";
|
import { WebLinksAddon } from "xterm-addon-web-links";
|
||||||
import { TERMINAL_COLS, TERMINAL_ROWS } from "../../../backend/util-common";
|
import { TERMINAL_COLS, TERMINAL_ROWS } from "../../../common/util-common";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { statusColor, statusNameShort } from "../../../backend/util-common";
|
import { statusColor, statusNameShort } from "../../../common/util-common";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// Dayjs init inside this, so it has to be the first import
|
// Dayjs init inside this, so it has to be the first import
|
||||||
import "../../backend/util-common";
|
import "../../common/util-common";
|
||||||
|
|
||||||
import { createApp, defineComponent, h } from "vue";
|
import { createApp, defineComponent, h } from "vue";
|
||||||
import App from "./App.vue";
|
import App from "./App.vue";
|
||||||
|
|
|
@ -3,6 +3,7 @@ import { Socket } from "socket.io-client";
|
||||||
import { defineComponent } from "vue";
|
import { defineComponent } from "vue";
|
||||||
import jwtDecode from "jwt-decode";
|
import jwtDecode from "jwt-decode";
|
||||||
import { Terminal } from "@xterm/xterm";
|
import { Terminal } from "@xterm/xterm";
|
||||||
|
import { AgentSocket } from "../../../common/agent-socket";
|
||||||
|
|
||||||
let socket : Socket;
|
let socket : Socket;
|
||||||
|
|
||||||
|
@ -28,16 +29,29 @@ export default defineComponent({
|
||||||
loggedIn: false,
|
loggedIn: false,
|
||||||
allowLoginDialog: false,
|
allowLoginDialog: false,
|
||||||
username: null,
|
username: null,
|
||||||
|
instanceList: {} as Record<string, any>,
|
||||||
stackList: {},
|
stackList: {},
|
||||||
composeTemplate: "",
|
composeTemplate: "",
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
|
||||||
|
completeStackList() {
|
||||||
|
let list : Record<string, any> = this.stackList;
|
||||||
|
for (let endpoint in this.instanceList) {
|
||||||
|
let instance = this.instanceList[endpoint];
|
||||||
|
for (let stackName in instance.stackList) {
|
||||||
|
list[stackName + "_" + endpoint] = instance.stackList[stackName];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
},
|
||||||
|
|
||||||
usernameFirstChar() {
|
usernameFirstChar() {
|
||||||
if (typeof this.username == "string" && this.username.length >= 1) {
|
if (typeof this.username == "string" && this.username.length >= 1) {
|
||||||
return this.username.charAt(0).toUpperCase();
|
return this.username.charAt(0).toUpperCase();
|
||||||
} else {
|
} else {
|
||||||
return "🐻";
|
return "🐬";
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -112,6 +126,12 @@ export default defineComponent({
|
||||||
transports: [ "websocket", "polling" ]
|
transports: [ "websocket", "polling" ]
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Handling events from agents
|
||||||
|
let agentSocket = new AgentSocket();
|
||||||
|
socket.on("agent", (eventName : unknown, ...args : unknown[]) => {
|
||||||
|
agentSocket.call(eventName, ...args);
|
||||||
|
});
|
||||||
|
|
||||||
socket.on("connect", () => {
|
socket.on("connect", () => {
|
||||||
console.log("Connected to the socket server");
|
console.log("Connected to the socket server");
|
||||||
|
|
||||||
|
@ -186,9 +206,20 @@ export default defineComponent({
|
||||||
terminal.write(data);
|
terminal.write(data);
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on("stackList", (res) => {
|
agentSocket.on("stackList", (res) => {
|
||||||
|
console.log(res);
|
||||||
|
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
this.stackList = res.stackList;
|
if (!res.endpoint) {
|
||||||
|
this.stackList = res.stackList;
|
||||||
|
} else {
|
||||||
|
if (!this.instanceList[res.endpoint]) {
|
||||||
|
this.instanceList[res.endpoint] = {
|
||||||
|
stackList: {},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
this.instanceList[res.endpoint].stackList = res.stackList;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -220,6 +251,10 @@ export default defineComponent({
|
||||||
return socket;
|
return socket;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
emitAgent(endpoint : string, eventName : string, ...args : unknown[]) {
|
||||||
|
this.getSocket().emit("agent", endpoint, eventName, ...args);
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get payload of JWT cookie
|
* Get payload of JWT cookie
|
||||||
* @returns {(object | undefined)} JWT payload
|
* @returns {(object | undefined)} JWT payload
|
||||||
|
|
|
@ -236,7 +236,7 @@ import {
|
||||||
getComposeTerminalName,
|
getComposeTerminalName,
|
||||||
PROGRESS_TERMINAL_ROWS,
|
PROGRESS_TERMINAL_ROWS,
|
||||||
RUNNING
|
RUNNING
|
||||||
} from "../../../backend/util-common";
|
} from "../../../common/util-common";
|
||||||
import { BModal } from "bootstrap-vue-next";
|
import { BModal } from "bootstrap-vue-next";
|
||||||
import NetworkInput from "../components/NetworkInput.vue";
|
import NetworkInput from "../components/NetworkInput.vue";
|
||||||
import dotenv from "dotenv";
|
import dotenv from "dotenv";
|
||||||
|
@ -349,18 +349,22 @@ export default {
|
||||||
if (!this.stack.name) {
|
if (!this.stack.name) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
return getComposeTerminalName(this.stack.name);
|
return getComposeTerminalName(this.endpoint, this.stack.name);
|
||||||
},
|
},
|
||||||
|
|
||||||
combinedTerminalName() {
|
combinedTerminalName() {
|
||||||
if (!this.stack.name) {
|
if (!this.stack.name) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
return getCombinedTerminalName(this.stack.name);
|
return getCombinedTerminalName(this.endpoint, this.stack.name);
|
||||||
},
|
},
|
||||||
|
|
||||||
networks() {
|
networks() {
|
||||||
return this.jsonConfig.networks;
|
return this.jsonConfig.networks;
|
||||||
|
},
|
||||||
|
|
||||||
|
endpoint() {
|
||||||
|
return this.$route.params.endpoint || "";
|
||||||
}
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
|
@ -462,7 +466,7 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
requestServiceStatus() {
|
requestServiceStatus() {
|
||||||
this.$root.getSocket().emit("serviceStatusList", this.stack.name, (res) => {
|
this.$root.emitAgent(this.endpoint, "serviceStatusList", this.stack.name, (res) => {
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
this.serviceStatusList = res.serviceStatusList;
|
this.serviceStatusList = res.serviceStatusList;
|
||||||
}
|
}
|
||||||
|
@ -490,7 +494,7 @@ export default {
|
||||||
|
|
||||||
loadStack() {
|
loadStack() {
|
||||||
this.processing = true;
|
this.processing = true;
|
||||||
this.$root.getSocket().emit("getStack", this.stack.name, (res) => {
|
this.$root.emitAgent(this.endpoint, "getStack", this.stack.name, (res) => {
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
this.stack = res.stack;
|
this.stack = res.stack;
|
||||||
this.yamlCodeChange();
|
this.yamlCodeChange();
|
||||||
|
|
|
@ -22,7 +22,7 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
import { allowedCommandList } from "../../../backend/util-common";
|
import { allowedCommandList } from "../../../common/util-common";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { getContainerExecTerminalName } from "../../../backend/util-common";
|
import { getContainerExecTerminalName } from "../../../common/util-common";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
|
@ -37,7 +37,9 @@ export default {
|
||||||
return getContainerExecTerminalName(this.stackName, this.serviceName, 0);
|
return getContainerExecTerminalName(this.stackName, this.serviceName, 0);
|
||||||
},
|
},
|
||||||
sh() {
|
sh() {
|
||||||
return {
|
let endpoint = this.$route.params.endpoint;
|
||||||
|
|
||||||
|
let data = {
|
||||||
name: "containerTerminal",
|
name: "containerTerminal",
|
||||||
params: {
|
params: {
|
||||||
stackName: this.stackName,
|
stackName: this.stackName,
|
||||||
|
@ -45,6 +47,13 @@ export default {
|
||||||
type: "sh",
|
type: "sh",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (endpoint) {
|
||||||
|
data.name = "containerTerminalEndpoint";
|
||||||
|
data.params.endpoint = endpoint;
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
|
|
|
@ -34,7 +34,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { statusNameShort } from "../../../backend/util-common";
|
import { statusNameShort } from "../../../common/util-common";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
|
|
|
@ -35,16 +35,23 @@ const routes = [
|
||||||
component: Compose,
|
component: Compose,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/compose/:stackName",
|
path: "/compose/:stackName/:endpoint",
|
||||||
name: "compose",
|
component: Compose,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/compose/:stackName",
|
||||||
component: Compose,
|
component: Compose,
|
||||||
props: true,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/terminal/:stackName/:serviceName/:type",
|
path: "/terminal/:stackName/:serviceName/:type",
|
||||||
component: ContainerTerminal,
|
component: ContainerTerminal,
|
||||||
name: "containerTerminal",
|
name: "containerTerminal",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "/terminal/:stackName/:serviceName/:type/:endpoint",
|
||||||
|
component: ContainerTerminal,
|
||||||
|
name: "containerTerminalEndpoint",
|
||||||
|
},
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
Loading…
Reference in a new issue