From d76b2f2a323dd7a6b5be7761fad28e9035684f34 Mon Sep 17 00:00:00 2001 From: kevin Date: Mon, 15 Jan 2024 15:02:41 -0500 Subject: [PATCH] Global .env page and getting ports from running container --- .../socket-handlers/main-socket-handler.ts | 18 +++ backend/stack.ts | 52 ++++++--- common/util-common.ts | 13 ++- frontend/components.d.ts | 1 + frontend/src/components/Container.vue | 6 +- .../src/components/settings/GlobalEnv.vue | 109 ++++++++++++++++++ frontend/src/lang/en.json | 3 +- frontend/src/pages/Compose.vue | 3 +- frontend/src/pages/Settings.vue | 3 + frontend/src/router.ts | 5 + 10 files changed, 194 insertions(+), 19 deletions(-) create mode 100644 frontend/src/components/settings/GlobalEnv.vue diff --git a/backend/socket-handlers/main-socket-handler.ts b/backend/socket-handlers/main-socket-handler.ts index 5d31878..ab88437 100644 --- a/backend/socket-handlers/main-socket-handler.ts +++ b/backend/socket-handlers/main-socket-handler.ts @@ -18,6 +18,8 @@ import { import { passwordStrength } from "check-password-strength"; import jwt from "jsonwebtoken"; import { Settings } from "../settings"; +import fs, { promises as fsAsync } from "fs"; +import path from "path"; export class MainSocketHandler extends SocketHandler { create(socket : DockgeSocket, server : DockgeServer) { @@ -242,6 +244,12 @@ export class MainSocketHandler extends SocketHandler { checkLogin(socket); const data = await Settings.getSettings("general"); + if (fs.existsSync(path.join(server.stacksDir, "global.env"))) { + data.globalENV = fs.readFileSync(path.join(server.stacksDir, "global.env"), "utf-8"); + } else { + data.globalENV = "# VARIABLE=value #comment"; + } + callback({ ok: true, data: data, @@ -270,6 +278,16 @@ export class MainSocketHandler extends SocketHandler { if (!currentDisabledAuth && data.disableAuth) { await doubleCheckPassword(socket, currentPassword); } + // Handle global.env + if (data.globalENV && data.globalENV != "# VARIABLE=value #comment") { + await fsAsync.writeFile(path.join(server.stacksDir, "global.env"), data.globalENV); + } else { + await fsAsync.rm(path.join(server.stacksDir, "global.env"), { + recursive: true, + force: true + }); + } + delete data.globalENV; await Settings.setSettings("general", data); diff --git a/backend/stack.ts b/backend/stack.ts index fbce500..f217696 100644 --- a/backend/stack.ts +++ b/backend/stack.ts @@ -93,7 +93,7 @@ export class Stack { * Get the status of the stack from `docker compose ps --format json` */ async ps() : Promise { - let res = await childProcessAsync.spawn("docker", [ "compose", "ps", "--format", "json" ], { + let res = await childProcessAsync.spawn("docker", this.getComposeOptions("ps", "--format", "json"), { cwd: this.path, encoding: "utf-8", }); @@ -208,7 +208,7 @@ export class Stack { async deploy(socket : DockgeSocket) : Promise { 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", this.getComposeOptions("up", "-d", "--remove-orphans"), this.path); if (exitCode !== 0) { throw new Error("Failed to deploy, please check the terminal output for more information."); } @@ -217,7 +217,7 @@ export class Stack { async delete(socket: DockgeSocket) : Promise { 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", this.getComposeOptions("down", "--remove-orphans"), this.path); if (exitCode !== 0) { throw new Error("Failed to delete, please check the terminal output for more information."); } @@ -407,9 +407,22 @@ export class Stack { return stack; } + getComposeOptions(command : string, ...extraOptions : string[]) { + //--env-file ./../global.env --env-file .env + let options = [ "compose", command, ...extraOptions ]; + if (fs.existsSync(path.join(this.server.stacksDir, "global.env"))) { + if (fs.existsSync(path.join(this.path, ".env"))) { + options.splice(1, 0, "--env-file", "./.env"); + } + options.splice(1, 0, "--env-file", "../global.env"); + } + console.log(options); + return options; + } + async start(socket: DockgeSocket) { 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", this.getComposeOptions("up", "-d", "--remove-orphans"), this.path); if (exitCode !== 0) { throw new Error("Failed to start, please check the terminal output for more information."); } @@ -418,7 +431,7 @@ export class Stack { async stop(socket: DockgeSocket) : Promise { 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", this.getComposeOptions("stop"), this.path); if (exitCode !== 0) { throw new Error("Failed to stop, please check the terminal output for more information."); } @@ -427,7 +440,7 @@ export class Stack { async restart(socket: DockgeSocket) : Promise { 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", this.getComposeOptions("restart"), this.path); if (exitCode !== 0) { throw new Error("Failed to restart, please check the terminal output for more information."); } @@ -436,7 +449,7 @@ export class Stack { async down(socket: DockgeSocket) : Promise { 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", this.getComposeOptions("down"), this.path); if (exitCode !== 0) { throw new Error("Failed to down, please check the terminal output for more information."); } @@ -445,7 +458,7 @@ export class Stack { async update(socket: DockgeSocket) { 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", this.getComposeOptions("pull"), this.path); if (exitCode !== 0) { throw new Error("Failed to pull, please check the terminal output for more information."); } @@ -457,7 +470,7 @@ export class Stack { return exitCode; } - exitCode = await Terminal.exec(this.server, socket, terminalName, "docker", [ "compose", "up", "-d", "--remove-orphans" ], this.path); + exitCode = await Terminal.exec(this.server, socket, terminalName, "docker", this.getComposeOptions("up", "-d", "--remove-orphans"), this.path); if (exitCode !== 0) { throw new Error("Failed to restart, please check the terminal output for more information."); } @@ -466,7 +479,7 @@ export class Stack { async joinCombinedTerminal(socket: DockgeSocket) { 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", this.getComposeOptions("logs", "-f", "--tail", "100"), this.path); terminal.enableKeepAlive = true; terminal.rows = COMBINED_TERMINAL_ROWS; terminal.cols = COMBINED_TERMINAL_COLS; @@ -487,7 +500,7 @@ export class Stack { let terminal = Terminal.getTerminal(terminalName); if (!terminal) { - terminal = new InteractiveTerminal(this.server, terminalName, "docker", [ "compose", "exec", serviceName, shell ], this.path); + terminal = new InteractiveTerminal(this.server, terminalName, "docker", this.getComposeOptions("exec", serviceName, shell), this.path); terminal.rows = TERMINAL_ROWS; log.debug("joinContainerTerminal", "Terminal created"); } @@ -497,10 +510,10 @@ export class Stack { } async getServiceStatusList() { - let statusList = new Map(); + let statusList = new Map(); try { - let res = await childProcessAsync.spawn("docker", [ "compose", "ps", "--format", "json" ], { + let res = await childProcessAsync.spawn("docker", this.getComposeOptions("ps", "--format", "json"), { cwd: this.path, encoding: "utf-8", }); @@ -514,10 +527,19 @@ export class Stack { for (let line of lines) { try { let obj = JSON.parse(line); + let ports = (obj.Ports as string).split(/,\s*/).filter((s) => { + return s.indexOf("->") >= 0; + }); if (obj.Health === "") { - statusList.set(obj.Service, obj.State); + statusList.set(obj.Service, { + state: obj.State, + ports: ports + }); } else { - statusList.set(obj.Service, obj.Health); + statusList.set(obj.Service, { + state: obj.Health, + ports: ports + }); } } catch (e) { } diff --git a/common/util-common.ts b/common/util-common.ts index 587e6dd..84bdc66 100644 --- a/common/util-common.ts +++ b/common/util-common.ts @@ -289,6 +289,7 @@ function copyYAMLCommentsItems(items : any, srcItems : any) { * - "8000-9000:80" * - "127.0.0.1:8001:8001" * - "127.0.0.1:5000-5010:5000-5010" + * - "0.0.0.0:8080->8080/tcp" * - "6060:6060/udp" * @param input * @param hostname @@ -298,9 +299,19 @@ export function parseDockerPort(input : string, hostname : string) { let display; const parts = input.split("/"); - const part1 = parts[0]; + let part1 = parts[0]; let protocol = parts[1] || "tcp"; + // coming from docker ps, split host part + const arrow = part1.indexOf("->"); + if (arrow >= 0) { + part1 = part1.split("->")[0]; + const colon = part1.indexOf(":"); + if (colon >= 0) { + part1 = part1.split(":")[1]; + } + } + // Split the last ":" const lastColon = part1.lastIndexOf(":"); diff --git a/frontend/components.d.ts b/frontend/components.d.ts index 708dd4e..300dbd7 100644 --- a/frontend/components.d.ts +++ b/frontend/components.d.ts @@ -17,6 +17,7 @@ declare module 'vue' { Confirm: typeof import('./src/components/Confirm.vue')['default'] Container: typeof import('./src/components/Container.vue')['default'] General: typeof import('./src/components/settings/General.vue')['default'] + GlobalEnv: typeof import('./src/components/settings/GlobalEnv.vue')['default'] HiddenInput: typeof import('./src/components/HiddenInput.vue')['default'] Login: typeof import('./src/components/Login.vue')['default'] NetworkInput: typeof import('./src/components/NetworkInput.vue')['default'] diff --git a/frontend/src/components/Container.vue b/frontend/src/components/Container.vue index 12f7709..b0be364 100644 --- a/frontend/src/components/Container.vue +++ b/frontend/src/components/Container.vue @@ -9,7 +9,7 @@ @@ -159,6 +159,10 @@ export default defineComponent({ status: { type: String, default: "N/A", + }, + ports: { + type: Array, + default: null } }, emits: [ diff --git a/frontend/src/components/settings/GlobalEnv.vue b/frontend/src/components/settings/GlobalEnv.vue new file mode 100644 index 0000000..db09dce --- /dev/null +++ b/frontend/src/components/settings/GlobalEnv.vue @@ -0,0 +1,109 @@ + + + + + diff --git a/frontend/src/lang/en.json b/frontend/src/lang/en.json index f05ac32..e3d6909 100644 --- a/frontend/src/lang/en.json +++ b/frontend/src/lang/en.json @@ -111,5 +111,6 @@ "agentAddedSuccessfully": "Agent added successfully.", "agentRemovedSuccessfully": "Agent removed successfully.", "removeAgent": "Remove Agent", - "removeAgentMsg": "Are you sure you want to remove this agent?" + "removeAgentMsg": "Are you sure you want to remove this agent?", + "GlobalEnv": "Global .env" } diff --git a/frontend/src/pages/Compose.vue b/frontend/src/pages/Compose.vue index 7d37854..a5d1cf3 100644 --- a/frontend/src/pages/Compose.vue +++ b/frontend/src/pages/Compose.vue @@ -128,7 +128,8 @@ :name="name" :is-edit-mode="isEditMode" :first="name === Object.keys(jsonConfig.services)[0]" - :status="serviceStatusList[name]" + :status="serviceStatusList[name]?.state" + :ports="serviceStatusList[name]?.ports" /> diff --git a/frontend/src/pages/Settings.vue b/frontend/src/pages/Settings.vue index 82431be..47b9330 100644 --- a/frontend/src/pages/Settings.vue +++ b/frontend/src/pages/Settings.vue @@ -83,6 +83,9 @@ export default { security: { title: this.$t("Security"), }, + globalEnv: { + title: this.$t("GlobalEnv"), + }, about: { title: this.$t("About"), }, diff --git a/frontend/src/router.ts b/frontend/src/router.ts index f3db7a6..f620f5f 100644 --- a/frontend/src/router.ts +++ b/frontend/src/router.ts @@ -14,6 +14,7 @@ const Settings = () => import("./pages/Settings.vue"); import Appearance from "./components/settings/Appearance.vue"; import General from "./components/settings/General.vue"; const Security = () => import("./components/settings/Security.vue"); +const GlobalEnv = () => import("./components/settings/GlobalEnv.vue"); import About from "./components/settings/About.vue"; const routes = [ @@ -78,6 +79,10 @@ const routes = [ path: "security", component: Security, }, + { + path: "globalEnv", + component: GlobalEnv, + }, { path: "about", component: About,