Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Global.env editor and usage in docker operations #387

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions backend/socket-handlers/main-socket-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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);

Expand Down
52 changes: 37 additions & 15 deletions backend/stack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ export class Stack {
* Get the status of the stack from `docker compose ps --format json`
*/
async ps() : Promise<object> {
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",
});
Expand Down Expand Up @@ -208,7 +208,7 @@ export class Stack {

async deploy(socket : DockgeSocket) : Promise<number> {
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.");
}
Expand All @@ -217,7 +217,7 @@ export class Stack {

async delete(socket: DockgeSocket) : Promise<number> {
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.");
}
Expand Down Expand Up @@ -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.");
}
Expand All @@ -418,7 +431,7 @@ export class Stack {

async stop(socket: DockgeSocket) : Promise<number> {
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.");
}
Expand All @@ -427,7 +440,7 @@ export class Stack {

async restart(socket: DockgeSocket) : Promise<number> {
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.");
}
Expand All @@ -436,7 +449,7 @@ export class Stack {

async down(socket: DockgeSocket) : Promise<number> {
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.");
}
Expand All @@ -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.");
}
Expand All @@ -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.");
}
Expand All @@ -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;
Expand All @@ -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");
}
Expand All @@ -497,10 +510,10 @@ export class Stack {
}

async getServiceStatusList() {
let statusList = new Map<string, number>();
let statusList = new Map<string, { state: string, ports: string[] }>();

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",
});
Expand All @@ -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) {
}
Expand Down
13 changes: 12 additions & 1 deletion common/util-common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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(":");

Expand Down
1 change: 1 addition & 0 deletions frontend/components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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']
Expand Down
6 changes: 5 additions & 1 deletion frontend/src/components/Container.vue
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
<div v-if="!isEditMode">
<span class="badge me-1" :class="bgStyle">{{ status }}</span>

<a v-for="port in envsubstService.ports" :key="port" :href="parsePort(port).url" target="_blank">
<a v-for="port in (ports ?? envsubstService.ports)" :key="port" :href="parsePort(port).url" target="_blank">
<span class="badge me-1 bg-secondary">{{ parsePort(port).display }}</span>
</a>
</div>
Expand Down Expand Up @@ -159,6 +159,10 @@ export default defineComponent({
status: {
type: String,
default: "N/A",
},
ports: {
type: Array,
default: null
}
},
emits: [
Expand Down
109 changes: 109 additions & 0 deletions frontend/src/components/settings/GlobalEnv.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
<template>
<div>
<div v-if="settingsLoaded" class="my-4">
<form class="my-4" autocomplete="off" @submit.prevent="saveGeneral">
<div class="shadow-box mb-3 editor-box edit-mode">
<prism-editor
ref="editor"
v-model="settings.globalENV"
class="env-editor"
:highlight="highlighterENV"
line-numbers
></prism-editor>
</div>

<div class="my-4">
<!-- Save Button -->
<div>
<button class="btn btn-primary" type="submit">
{{ $t("Save") }}
</button>
</div>
</div>
</form>
</div>
</div>
</template>

<script>
import { highlight, languages } from "prismjs/components/prism-core";
import { PrismEditor } from "vue-prism-editor";

let prismjsSymbolDefinition = {
"symbol": {
pattern: /(?<!\$)\$(\{[^{}]*\}|\w+)/,
}
};

export default {
components: {
PrismEditor,
},

data() {
return {
};
},

computed: {
settings() {
return this.$parent.$parent.$parent.settings;
},
saveSettings() {
return this.$parent.$parent.$parent.saveSettings;
},
settingsLoaded() {
return this.$parent.$parent.$parent.settingsLoaded;
}
},

methods: {
/** Save the settings */
saveGeneral() {
this.saveSettings();
},

highlighterENV(code) {
if (!languages.docker_env) {
languages.docker_env = {
"comment": {
pattern: /(^#| #).*$/m,
greedy: true
},
"keyword": {
pattern: /^\w*(?=[:=])/m,
greedy: true
},
"value": {
pattern: /(?<=[:=]).*?((?= #)|$)/m,
greedy: true,
inside: {
"string": [
{
pattern: /^ *'.*?(?<!\\)'/m,
},
{
pattern: /^ *".*?(?<!\\)"|^.*$/m,
inside: prismjsSymbolDefinition
},
],
},
},
};
}
return highlight(code, languages.docker_env);
},
},
};
</script>

<style scoped lang="scss">

.editor-box {
font-family: 'JetBrains Mono', monospace;
font-size: 14px;
&.edit-mode {
background-color: #2c2f38 !important;
}
}
</style>
1 change: 1 addition & 0 deletions frontend/src/lang/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -112,5 +112,6 @@
"agentRemovedSuccessfully": "Agent removed successfully.",
"removeAgent": "Remove Agent",
"removeAgentMsg": "Are you sure you want to remove this agent?",
"GlobalEnv": "Global .env",
"LongSyntaxNotSupported": "Long syntax is not supported here. Please use the YAML editor."
}
3 changes: 2 additions & 1 deletion frontend/src/pages/Compose.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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"
/>
</div>

Expand Down
3 changes: 3 additions & 0 deletions frontend/src/pages/Settings.vue
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,9 @@ export default {
security: {
title: this.$t("Security"),
},
globalEnv: {
title: this.$t("GlobalEnv"),
},
about: {
title: this.$t("About"),
},
Expand Down
Loading
Loading