mirror of
https://github.com/louislam/dockge.git
synced 2025-08-13 19:27:20 +02:00
Compare commits
1 Commits
fix-cpu
...
fix-inacti
Author | SHA1 | Date | |
---|---|---|---|
2a6a1b093a |
@ -30,7 +30,6 @@ 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 childProcess from "child_process";
|
import childProcess from "child_process";
|
||||||
import { Terminal } from "./terminal";
|
|
||||||
|
|
||||||
export class DockgeServer {
|
export class DockgeServer {
|
||||||
app : Express;
|
app : Express;
|
||||||
@ -231,11 +230,6 @@ export class DockgeServer {
|
|||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (isDev) {
|
|
||||||
setInterval(() => {
|
|
||||||
log.debug("terminal", "Terminal count: " + Terminal.getTerminalCount());
|
|
||||||
}, 5000);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async afterLogin(socket : DockgeSocket, user : User) {
|
async afterLogin(socket : DockgeSocket, user : User) {
|
||||||
@ -298,11 +292,11 @@ export class DockgeServer {
|
|||||||
log.info("server", `Listening on ${this.config.port}`);
|
log.info("server", `Listening on ${this.config.port}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run every 10 seconds
|
// Run every 5 seconds
|
||||||
Cron("*/10 * * * * *", {
|
Cron("*/2 * * * * *", {
|
||||||
protect: true, // Enabled over-run protection.
|
protect: true, // Enabled over-run protection.
|
||||||
}, () => {
|
}, () => {
|
||||||
//log.debug("server", "Cron job running");
|
log.debug("server", "Cron job running");
|
||||||
this.sendStackList(true);
|
this.sendStackList(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -75,9 +75,7 @@ export class DockerSocketHandler extends SocketHandler {
|
|||||||
|
|
||||||
const stack = Stack.getStack(server, stackName);
|
const stack = Stack.getStack(server, stackName);
|
||||||
|
|
||||||
if (stack.isManagedByDockge) {
|
stack.joinCombinedTerminal(socket);
|
||||||
stack.joinCombinedTerminal(socket);
|
|
||||||
}
|
|
||||||
|
|
||||||
callback({
|
callback({
|
||||||
ok: true,
|
ok: true,
|
||||||
|
@ -140,26 +140,9 @@ export class TerminalSocketHandler extends SocketHandler {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Leave Combined Terminal
|
// Close Terminal
|
||||||
socket.on("leaveCombinedTerminal", async (stackName : unknown, callback) => {
|
socket.on("terminalClose", async (terminalName : unknown, callback : unknown) => {
|
||||||
try {
|
|
||||||
checkLogin(socket);
|
|
||||||
|
|
||||||
log.debug("leaveCombinedTerminal", "Stack name: " + stackName);
|
|
||||||
|
|
||||||
if (typeof(stackName) !== "string") {
|
|
||||||
throw new ValidationError("Stack name must be a string.");
|
|
||||||
}
|
|
||||||
|
|
||||||
const stack = Stack.getStack(server, stackName);
|
|
||||||
await stack.leaveCombinedTerminal(socket);
|
|
||||||
|
|
||||||
callback({
|
|
||||||
ok: true,
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
callbackError(e, callback);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO: Resize Terminal
|
// TODO: Resize Terminal
|
||||||
|
@ -298,7 +298,7 @@ export class Stack {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
//log.debug("getStack", "Skip FS operations");
|
log.debug("getStack", "Skip FS operations");
|
||||||
}
|
}
|
||||||
|
|
||||||
let stack : Stack;
|
let stack : Stack;
|
||||||
@ -374,21 +374,12 @@ export class Stack {
|
|||||||
async joinCombinedTerminal(socket: DockgeSocket) {
|
async joinCombinedTerminal(socket: DockgeSocket) {
|
||||||
const terminalName = getCombinedTerminalName(this.name);
|
const terminalName = getCombinedTerminalName(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.rows = COMBINED_TERMINAL_ROWS;
|
terminal.rows = COMBINED_TERMINAL_ROWS;
|
||||||
terminal.cols = COMBINED_TERMINAL_COLS;
|
terminal.cols = COMBINED_TERMINAL_COLS;
|
||||||
terminal.join(socket);
|
terminal.join(socket);
|
||||||
terminal.start();
|
terminal.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
async leaveCombinedTerminal(socket: DockgeSocket) {
|
|
||||||
const terminalName = getCombinedTerminalName(this.name);
|
|
||||||
const terminal = Terminal.getTerminal(terminalName);
|
|
||||||
if (terminal) {
|
|
||||||
terminal.leave(socket);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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(this.name, serviceName, index);
|
||||||
let terminal = Terminal.getTerminal(terminalName);
|
let terminal = Terminal.getTerminal(terminalName);
|
||||||
|
@ -5,6 +5,8 @@ import { LimitQueue } from "./utils/limit-queue";
|
|||||||
import { DockgeSocket } from "./util-server";
|
import { DockgeSocket } from "./util-server";
|
||||||
import {
|
import {
|
||||||
allowedCommandList, allowedRawKeys,
|
allowedCommandList, allowedRawKeys,
|
||||||
|
getComposeTerminalName,
|
||||||
|
getCryptoRandomInt,
|
||||||
PROGRESS_TERMINAL_ROWS,
|
PROGRESS_TERMINAL_ROWS,
|
||||||
TERMINAL_COLS,
|
TERMINAL_COLS,
|
||||||
TERMINAL_ROWS
|
TERMINAL_ROWS
|
||||||
@ -32,9 +34,6 @@ export class Terminal {
|
|||||||
protected _rows : number = TERMINAL_ROWS;
|
protected _rows : number = TERMINAL_ROWS;
|
||||||
protected _cols : number = TERMINAL_COLS;
|
protected _cols : number = TERMINAL_COLS;
|
||||||
|
|
||||||
public enableKeepAlive : boolean = false;
|
|
||||||
protected keepAliveInterval? : NodeJS.Timeout;
|
|
||||||
|
|
||||||
constructor(server : DockgeServer, name : string, file : string, args : string | string[], cwd : string) {
|
constructor(server : DockgeServer, name : string, file : string, args : string | string[], cwd : string) {
|
||||||
this.server = server;
|
this.server = server;
|
||||||
this._name = name;
|
this._name = name;
|
||||||
@ -81,25 +80,6 @@ export class Terminal {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.enableKeepAlive) {
|
|
||||||
log.debug("Terminal", "Keep alive enabled for terminal " + this.name);
|
|
||||||
|
|
||||||
// Close if there is no clients
|
|
||||||
this.keepAliveInterval = setInterval(() => {
|
|
||||||
const clients = this.server.io.sockets.adapter.rooms.get(this.name);
|
|
||||||
const numClients = clients ? clients.size : 0;
|
|
||||||
|
|
||||||
if (numClients === 0) {
|
|
||||||
log.debug("Terminal", "Terminal " + this.name + " has no client, closing...");
|
|
||||||
this.close();
|
|
||||||
} else {
|
|
||||||
log.debug("Terminal", "Terminal " + this.name + " has " + numClients + " client(s)");
|
|
||||||
}
|
|
||||||
}, 60 * 1000);
|
|
||||||
} else {
|
|
||||||
log.debug("Terminal", "Keep alive disabled for terminal " + this.name);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
this._ptyProcess = pty.spawn(this.file, this.args, {
|
this._ptyProcess = pty.spawn(this.file, this.args, {
|
||||||
name: this.name,
|
name: this.name,
|
||||||
@ -120,8 +100,6 @@ export class Terminal {
|
|||||||
this._ptyProcess.onExit(this.exit);
|
this._ptyProcess.onExit(this.exit);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof Error) {
|
if (error instanceof Error) {
|
||||||
clearInterval(this.keepAliveInterval);
|
|
||||||
|
|
||||||
log.error("Terminal", "Failed to start terminal: " + error.message);
|
log.error("Terminal", "Failed to start terminal: " + error.message);
|
||||||
const exitCode = Number(error.message.split(" ").pop());
|
const exitCode = Number(error.message.split(" ").pop());
|
||||||
this.exit({
|
this.exit({
|
||||||
@ -144,8 +122,6 @@ export class Terminal {
|
|||||||
Terminal.terminalMap.delete(this.name);
|
Terminal.terminalMap.delete(this.name);
|
||||||
log.debug("Terminal", "Terminal " + this.name + " exited with code " + res.exitCode);
|
log.debug("Terminal", "Terminal " + this.name + " exited with code " + res.exitCode);
|
||||||
|
|
||||||
clearInterval(this.keepAliveInterval);
|
|
||||||
|
|
||||||
if (this.callback) {
|
if (this.callback) {
|
||||||
this.callback(res.exitCode);
|
this.callback(res.exitCode);
|
||||||
}
|
}
|
||||||
@ -182,9 +158,7 @@ export class Terminal {
|
|||||||
}
|
}
|
||||||
|
|
||||||
close() {
|
close() {
|
||||||
clearInterval(this.keepAliveInterval);
|
this._ptyProcess?.kill();
|
||||||
// Send Ctrl+C to the terminal
|
|
||||||
this.ptyProcess?.write("\x03");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -205,30 +179,20 @@ export class Terminal {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static exec(server : DockgeServer, socket : DockgeSocket | undefined, terminalName : string, file : string, args : string | string[], cwd : string) : Promise<number> {
|
public static exec(server : DockgeServer, socket : DockgeSocket | undefined, terminalName : string, file : string, args : string | string[], cwd : string) : Promise<number> {
|
||||||
return new Promise((resolve, reject) => {
|
const terminal = new Terminal(server, terminalName, file, args, cwd);
|
||||||
// check if terminal exists
|
terminal.rows = PROGRESS_TERMINAL_ROWS;
|
||||||
if (Terminal.terminalMap.has(terminalName)) {
|
|
||||||
reject("Another operation is already running, please try again later.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let terminal = new Terminal(server, terminalName, file, args, cwd);
|
if (socket) {
|
||||||
terminal.rows = PROGRESS_TERMINAL_ROWS;
|
terminal.join(socket);
|
||||||
|
}
|
||||||
if (socket) {
|
|
||||||
terminal.join(socket);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
return new Promise((resolve) => {
|
||||||
terminal.onExit((exitCode : number) => {
|
terminal.onExit((exitCode : number) => {
|
||||||
resolve(exitCode);
|
resolve(exitCode);
|
||||||
});
|
});
|
||||||
terminal.start();
|
terminal.start();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public static getTerminalCount() {
|
|
||||||
return Terminal.terminalMap.size;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
FROM node:18.17.1-bookworm-slim
|
FROM node:20-bookworm-slim
|
||||||
ENV PNPM_HOME="/pnpm"
|
ENV PNPM_HOME="/pnpm"
|
||||||
ENV PATH="$PNPM_HOME:$PATH"
|
ENV PATH="$PNPM_HOME:$PATH"
|
||||||
|
|
||||||
@ -24,3 +24,16 @@ RUN apt update && apt install --yes --no-install-recommends \
|
|||||||
&& rm -rf /var/lib/apt/lists/* \
|
&& rm -rf /var/lib/apt/lists/* \
|
||||||
&& npm install pnpm -g \
|
&& npm install pnpm -g \
|
||||||
&& pnpm install -g tsx
|
&& pnpm install -g tsx
|
||||||
|
|
||||||
|
# ensures that /var/run/docker.sock exists
|
||||||
|
# changes the ownership of /var/run/docker.sock
|
||||||
|
RUN touch /var/run/docker.sock && chown node:node /var/run/docker.sock
|
||||||
|
|
||||||
|
# Full Base Image
|
||||||
|
# MariaDB, Chromium and fonts
|
||||||
|
#FROM base-slim AS base
|
||||||
|
#ENV DOCKGE_ENABLE_EMBEDDED_MARIADB=1
|
||||||
|
#RUN apt update && \
|
||||||
|
# apt --yes --no-install-recommends install mariadb-server && \
|
||||||
|
# rm -rf /var/lib/apt/lists/* && \
|
||||||
|
# apt --yes autoremove
|
||||||
|
@ -30,10 +30,6 @@ export default {
|
|||||||
displayName: {
|
displayName: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
|
||||||
objectType: {
|
|
||||||
type: String,
|
|
||||||
default: "service",
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
@ -45,7 +41,8 @@ export default {
|
|||||||
array() {
|
array() {
|
||||||
// Create the array if not exists, it should be safe.
|
// Create the array if not exists, it should be safe.
|
||||||
if (!this.service[this.name]) {
|
if (!this.service[this.name]) {
|
||||||
return [];
|
// eslint-disable-next-line vue/no-side-effects-in-computed-properties
|
||||||
|
this.service[this.name] = [];
|
||||||
}
|
}
|
||||||
return this.service[this.name];
|
return this.service[this.name];
|
||||||
},
|
},
|
||||||
@ -59,24 +56,8 @@ export default {
|
|||||||
return this.service[this.name] !== undefined;
|
return this.service[this.name] !== undefined;
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
|
||||||
* Not a good name, but it is used to get the object.
|
|
||||||
*/
|
|
||||||
service() {
|
service() {
|
||||||
if (this.objectType === "service") {
|
return this.$parent.$parent.service;
|
||||||
// Used in Container.vue
|
|
||||||
return this.$parent.$parent.service;
|
|
||||||
} else if (this.objectType === "x-dockge") {
|
|
||||||
|
|
||||||
if (!this.$parent.$parent.jsonConfig["x-dockge"]) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Used in Compose.vue
|
|
||||||
return this.$parent.$parent.jsonConfig["x-dockge"];
|
|
||||||
} else {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
valid() {
|
valid() {
|
||||||
@ -100,19 +81,6 @@ export default {
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
addField() {
|
addField() {
|
||||||
|
|
||||||
// Create the object if not exists.
|
|
||||||
if (this.objectType === "x-dockge") {
|
|
||||||
if (!this.$parent.$parent.jsonConfig["x-dockge"]) {
|
|
||||||
this.$parent.$parent.jsonConfig["x-dockge"] = {};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create the array if not exists.
|
|
||||||
if (!this.service[this.name]) {
|
|
||||||
this.service[this.name] = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
this.array.push("");
|
this.array.push("");
|
||||||
},
|
},
|
||||||
remove(index) {
|
remove(index) {
|
||||||
|
@ -49,7 +49,8 @@ export default {
|
|||||||
array() {
|
array() {
|
||||||
// Create the array if not exists, it should be safe.
|
// Create the array if not exists, it should be safe.
|
||||||
if (!this.service[this.name]) {
|
if (!this.service[this.name]) {
|
||||||
return [];
|
// eslint-disable-next-line vue/no-side-effects-in-computed-properties
|
||||||
|
this.service[this.name] = [];
|
||||||
}
|
}
|
||||||
return this.service[this.name];
|
return this.service[this.name];
|
||||||
},
|
},
|
||||||
@ -88,10 +89,6 @@ export default {
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
addField() {
|
addField() {
|
||||||
// Create the array if not exists.
|
|
||||||
if (!this.service[this.name]) {
|
|
||||||
this.service[this.name] = [];
|
|
||||||
}
|
|
||||||
this.array.push("");
|
this.array.push("");
|
||||||
},
|
},
|
||||||
remove(index) {
|
remove(index) {
|
||||||
|
@ -45,12 +45,11 @@ export default {
|
|||||||
<style scoped>
|
<style scoped>
|
||||||
.badge {
|
.badge {
|
||||||
min-width: 62px;
|
min-width: 62px;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fixed-width {
|
.fixed-width {
|
||||||
width: 62px;
|
width: 62px;
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -13,7 +13,6 @@ const languageList = {
|
|||||||
"sl": "Slovenščina",
|
"sl": "Slovenščina",
|
||||||
"tr": "Türkçe",
|
"tr": "Türkçe",
|
||||||
"zh-CN": "简体中文",
|
"zh-CN": "简体中文",
|
||||||
"zh-TW": "繁體中文(台灣)",
|
|
||||||
"ur": "Urdu",
|
"ur": "Urdu",
|
||||||
"ko-KR": "한국어",
|
"ko-KR": "한국어",
|
||||||
"ru": "Русский",
|
"ru": "Русский",
|
||||||
@ -21,8 +20,7 @@ const languageList = {
|
|||||||
"ar": "العربية",
|
"ar": "العربية",
|
||||||
"th":"ไทย",
|
"th":"ไทย",
|
||||||
"it-IT":"Italiano",
|
"it-IT":"Italiano",
|
||||||
"sv-SE":"Svenska",
|
"sv-SE":"Svenska"
|
||||||
"uk-UA":"Українська"
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let messages = {
|
let messages = {
|
||||||
|
@ -91,12 +91,5 @@
|
|||||||
"Allowed commands:": "Allowed commands:",
|
"Allowed commands:": "Allowed commands:",
|
||||||
"Internal Networks": "Internal Networks",
|
"Internal Networks": "Internal Networks",
|
||||||
"External Networks": "External Networks",
|
"External Networks": "External Networks",
|
||||||
"No External Networks": "No External Networks",
|
"No External Networks": "No External Networks"
|
||||||
"reverseProxyMsg1": "Using a Reverse Proxy?",
|
|
||||||
"reverseProxyMsg2": "Check how to config it for WebSocket",
|
|
||||||
"Cannot connect to the socket server.": "Cannot connect to the socket server.",
|
|
||||||
"reconnecting...": "Reconnecting...",
|
|
||||||
"connecting...": "Connecting to the socket server...",
|
|
||||||
"url": "URL | URLs",
|
|
||||||
"extra": "Extra"
|
|
||||||
}
|
}
|
||||||
|
@ -1,94 +0,0 @@
|
|||||||
{
|
|
||||||
"languageName": "Українська",
|
|
||||||
"Create your admin account": "Створити акаунт адміністратора",
|
|
||||||
"authIncorrectCreds": "Невірний логін чи пароль",
|
|
||||||
"PasswordsDoNotMatch": "Паролі не співпадають.",
|
|
||||||
"Repeat Password": "Повторіть пароль",
|
|
||||||
"Create": "Створити",
|
|
||||||
"signedInDisp": "Авторизовано як {0}",
|
|
||||||
"signedInDispDisabled": "Авторизацію вимкнено.",
|
|
||||||
"home": "Головна",
|
|
||||||
"console": "Консоль",
|
|
||||||
"registry": "Registry",
|
|
||||||
"compose": "Compose",
|
|
||||||
"addFirstStackMsg": "Додайте свій перший стек!",
|
|
||||||
"stackName" : "Назва стеку",
|
|
||||||
"deployStack": "Розрознути",
|
|
||||||
"deleteStack": "Видалити",
|
|
||||||
"stopStack": "Зупинити",
|
|
||||||
"restartStack": "Перезапустити",
|
|
||||||
"updateStack": "Оновити",
|
|
||||||
"startStack": "Запустити",
|
|
||||||
"editStack": "Редагувати",
|
|
||||||
"discardStack": "Відмінити",
|
|
||||||
"saveStackDraft": "Зберегти",
|
|
||||||
"notAvailableShort" : "Н/Д",
|
|
||||||
"deleteStackMsg": "Ви впевнені що хочете видалити цей стек?",
|
|
||||||
"stackNotManagedByDockgeMsg": "Даний стек не управляється Dockge.",
|
|
||||||
"primaryHostname": "Назва хосту",
|
|
||||||
"general": "Загальне",
|
|
||||||
"container": "Контейнер | Контейнери",
|
|
||||||
"scanFolder": "Сканувати папку зі стеками",
|
|
||||||
"dockerImage": "Образ",
|
|
||||||
"restartPolicyUnlessStopped": "Доки не буде зупинено",
|
|
||||||
"restartPolicyAlways": "Завжди",
|
|
||||||
"restartPolicyOnFailure": "При падінні",
|
|
||||||
"restartPolicyNo": "Ніколи",
|
|
||||||
"environmentVariable": "Змінна середовища | змінні середовища",
|
|
||||||
"restartPolicy": "Перезапуск",
|
|
||||||
"containerName": "Назва контейнеру",
|
|
||||||
"port": "Порт | Порти",
|
|
||||||
"volume": "Сховище | Сховища",
|
|
||||||
"network": "Мережа | Мережі",
|
|
||||||
"dependsOn": "Залежність контейнера | Залежності контейнеру",
|
|
||||||
"addListItem": "Додати {0}",
|
|
||||||
"deleteContainer": "Видалити",
|
|
||||||
"addContainer": "Додати Контейнер",
|
|
||||||
"addNetwork": "Додати Мережу",
|
|
||||||
"disableauth.message1": "Ви впевнені що хочете <strong>вимкнути авторизацію</strong>?",
|
|
||||||
"disableauth.message2": "Це призначено для сценаріїв, <strong>де ви збираєтесь використати сторонню авторизацію</strong> перед Dockge, наприклад Cloudflare Access, Authelia чи інші.",
|
|
||||||
"passwordNotMatchMsg": "Паролі не співпадають.",
|
|
||||||
"autoGet": "Отримати",
|
|
||||||
"add": "Додати",
|
|
||||||
"Edit": "Змінити",
|
|
||||||
"applyToYAML": "Застосувати для YAML",
|
|
||||||
"createExternalNetwork": "Створити",
|
|
||||||
"addInternalNetwork": "Додати",
|
|
||||||
"Save": "Зберегти",
|
|
||||||
"Language": "Мова",
|
|
||||||
"Current User": "Користувач",
|
|
||||||
"Change Password": "Змінити пароль",
|
|
||||||
"Current Password": "Поточний пароль",
|
|
||||||
"New Password": "Новий пароль",
|
|
||||||
"Repeat New Password": "Повторіть новий пароль",
|
|
||||||
"Update Password": "Оновити пароль",
|
|
||||||
"Advanced": "Розширені опції",
|
|
||||||
"Please use this option carefully!": "Будь-ласка, використовуйте цю опцію з обережністю!",
|
|
||||||
"Enable Auth": "Увімкнути автентифікацію",
|
|
||||||
"Disable Auth": "Вимкнути автентифікацію",
|
|
||||||
"I understand, please disable": "Зрозуміло, все-одно вимкнути",
|
|
||||||
"Leave": "Покинути",
|
|
||||||
"Frontend Version": "Версія інтерфейсу",
|
|
||||||
"Check Update On GitHub": "Перевірити оновлення на GitHub",
|
|
||||||
"Show update if available": "Показати оновлення, якщо доступно",
|
|
||||||
"Also check beta release": "Перевіряти оновлення до бета-версії",
|
|
||||||
"Remember me": "Запамʼятати мене",
|
|
||||||
"Login": "Логін",
|
|
||||||
"Username": "Імʼя користувача",
|
|
||||||
"Password": "Пароль",
|
|
||||||
"Settings": "Налаштування",
|
|
||||||
"Logout": "Вийти",
|
|
||||||
"Lowercase only": "Тільки нижній регістр",
|
|
||||||
"Convert to Compose": "Конвертувати в Compose",
|
|
||||||
"Docker Run": "Запустити Docker",
|
|
||||||
"active": "активно",
|
|
||||||
"exited": "завершено",
|
|
||||||
"inactive": "неактивно",
|
|
||||||
"Appearance": "Зовнішній вигляд",
|
|
||||||
"Security": "Безпека",
|
|
||||||
"About": "Про продукт",
|
|
||||||
"Allowed commands:": "Дозволені команди:",
|
|
||||||
"Internal Networks": "Внутрішні мережі",
|
|
||||||
"External Networks": "Зовнішні мережі",
|
|
||||||
"No External Networks": "Немає зовнішніх мереж"
|
|
||||||
}
|
|
@ -1,94 +0,0 @@
|
|||||||
{
|
|
||||||
"languageName": "繁體中文(台灣)",
|
|
||||||
"Create your admin account": "建立您的管理員帳號",
|
|
||||||
"authIncorrectCreds": "使用者名稱或密碼錯誤",
|
|
||||||
"PasswordsDoNotMatch": "兩次輸入的密碼不一致。",
|
|
||||||
"Repeat Password": "重複以確認密碼",
|
|
||||||
"Create": "建立",
|
|
||||||
"signedInDisp": "目前使用者:{0}",
|
|
||||||
"signedInDispDisabled": "已停用身份驗證",
|
|
||||||
"home": "首頁",
|
|
||||||
"console": "主控台",
|
|
||||||
"registry": "映像倉庫",
|
|
||||||
"compose": "Compose",
|
|
||||||
"addFirstStackMsg": "組合您的第一個堆疊!",
|
|
||||||
"stackName": "堆疊名稱",
|
|
||||||
"deployStack": "部署",
|
|
||||||
"deleteStack": "刪除",
|
|
||||||
"stopStack": "停止",
|
|
||||||
"restartStack": "重啟",
|
|
||||||
"updateStack": "更新",
|
|
||||||
"startStack": "啟動",
|
|
||||||
"editStack": "編輯",
|
|
||||||
"discardStack": "捨棄",
|
|
||||||
"saveStackDraft": "儲存",
|
|
||||||
"notAvailableShort": "不可用",
|
|
||||||
"deleteStackMsg": "您確定要刪除這個堆疊嗎?",
|
|
||||||
"stackNotManagedByDockgeMsg": "這個堆疊不由 Dockge 管理",
|
|
||||||
"primaryHostname": "主機名稱",
|
|
||||||
"general": "一般",
|
|
||||||
"container": "容器 | 容器群組",
|
|
||||||
"scanFolder": "掃描堆疊資料夾",
|
|
||||||
"dockerImage": "映像",
|
|
||||||
"restartPolicyUnlessStopped": "除非手動停止",
|
|
||||||
"restartPolicyAlways": "始終",
|
|
||||||
"restartPolicyOnFailure": "在失敗時",
|
|
||||||
"restartPolicyNo": "不重啟",
|
|
||||||
"environmentVariable": "環境變數 | 環境變數群組",
|
|
||||||
"restartPolicy": "重啟策略",
|
|
||||||
"containerName": "容器名稱",
|
|
||||||
"port": "連接埠 | 連接埠群組",
|
|
||||||
"volume": "資料卷 | 資料卷群組",
|
|
||||||
"network": "網路 | 網路群組",
|
|
||||||
"dependsOn": "容器依賴 | 容器依賴關係",
|
|
||||||
"addListItem": "新增 {0}",
|
|
||||||
"deleteContainer": "刪除容器",
|
|
||||||
"addContainer": "新增容器",
|
|
||||||
"addNetwork": "新增網路",
|
|
||||||
"disableauth.message1": "您確定要<strong>停用身份驗證</strong>嗎?",
|
|
||||||
"disableauth.message2": "該選項設計用於某些場景,<strong>例如在 Dockge 之上接入第三方認證</strong>,如 Cloudflare Access、Authelia 或其他認證機制。如果您不清楚這個選項的作用,請不要停用驗證!",
|
|
||||||
"passwordNotMatchMsg": "兩次輸入的密碼不一致。",
|
|
||||||
"autoGet": "自動取得",
|
|
||||||
"add": "新增",
|
|
||||||
"Edit": "編輯",
|
|
||||||
"applyToYAML": "應用到YAML",
|
|
||||||
"createExternalNetwork": "建立",
|
|
||||||
"addInternalNetwork": "新增",
|
|
||||||
"Save": "儲存",
|
|
||||||
"Language": "語言",
|
|
||||||
"Current User": "目前使用者",
|
|
||||||
"Change Password": "更換密碼",
|
|
||||||
"Current Password": "目前密碼",
|
|
||||||
"New Password": "新密碼",
|
|
||||||
"Repeat New Password": "重複以確認新密碼",
|
|
||||||
"Update Password": "更新密碼",
|
|
||||||
"Advanced": "進階",
|
|
||||||
"Please use this option carefully!": "請謹慎使用該選項!",
|
|
||||||
"Enable Auth": "啟用驗證",
|
|
||||||
"Disable Auth": "停用驗證",
|
|
||||||
"I understand, please disable": "我已了解風險,確認停用",
|
|
||||||
"Leave": "離開",
|
|
||||||
"Frontend Version": "前端版本",
|
|
||||||
"Check Update On GitHub": "在 GitHub 上檢查更新",
|
|
||||||
"Show update if available": "有更新時提醒我",
|
|
||||||
"Also check beta release": "同時檢查 Beta 渠道更新",
|
|
||||||
"Remember me": "記住我",
|
|
||||||
"Login": "登入",
|
|
||||||
"Username": "使用者名稱",
|
|
||||||
"Password": "密碼",
|
|
||||||
"Settings": "設定",
|
|
||||||
"Logout": "登出",
|
|
||||||
"Lowercase only": "僅小寫字母",
|
|
||||||
"Convert to Compose": "轉換為 Compose 格式",
|
|
||||||
"Docker Run": "Docker 啟動",
|
|
||||||
"active": "已啟動",
|
|
||||||
"exited": "已退出",
|
|
||||||
"inactive": "未啟動",
|
|
||||||
"Appearance": "外觀",
|
|
||||||
"Security": "安全",
|
|
||||||
"About": "關於",
|
|
||||||
"Allowed commands:": "允許使用的指令:",
|
|
||||||
"Internal Networks": "內部網路",
|
|
||||||
"External Networks": "外部網路",
|
|
||||||
"No External Networks": "無外部網路"
|
|
||||||
}
|
|
@ -3,9 +3,6 @@
|
|||||||
<div v-if="! $root.socketIO.connected && ! $root.socketIO.firstConnect" class="lost-connection">
|
<div v-if="! $root.socketIO.connected && ! $root.socketIO.firstConnect" class="lost-connection">
|
||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
{{ $root.socketIO.connectionErrorMsg }}
|
{{ $root.socketIO.connectionErrorMsg }}
|
||||||
<div v-if="$root.socketIO.showReverseProxyGuide">
|
|
||||||
{{ $t("reverseProxyMsg1") }} <a href="https://github.com/louislam/uptime-kuma/wiki/Reverse-Proxy" target="_blank">{{ $t("reverseProxyMsg2") }}</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -85,10 +82,6 @@
|
|||||||
</header>
|
</header>
|
||||||
|
|
||||||
<main>
|
<main>
|
||||||
<div v-if="$root.socketIO.connecting" class="container mt-5">
|
|
||||||
<h4>{{ $t("connecting...") }}</h4>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<router-view v-if="$root.loggedIn" />
|
<router-view v-if="$root.loggedIn" />
|
||||||
<Login v-if="! $root.loggedIn && $root.allowLoginDialog" />
|
<Login v-if="! $root.loggedIn && $root.allowLoginDialog" />
|
||||||
</main>
|
</main>
|
||||||
|
@ -19,7 +19,6 @@ export default defineComponent({
|
|||||||
initedSocketIO: false,
|
initedSocketIO: false,
|
||||||
connectionErrorMsg: `${this.$t("Cannot connect to the socket server.")} ${this.$t("Reconnecting...")}`,
|
connectionErrorMsg: `${this.$t("Cannot connect to the socket server.")} ${this.$t("Reconnecting...")}`,
|
||||||
showReverseProxyGuide: true,
|
showReverseProxyGuide: true,
|
||||||
connecting: false,
|
|
||||||
},
|
},
|
||||||
info: {
|
info: {
|
||||||
|
|
||||||
@ -104,10 +103,6 @@ export default defineComponent({
|
|||||||
url = location.protocol + "//" + location.host;
|
url = location.protocol + "//" + location.host;
|
||||||
}
|
}
|
||||||
|
|
||||||
let connectingMsgTimeout = setTimeout(() => {
|
|
||||||
this.socketIO.connecting = true;
|
|
||||||
}, 1500);
|
|
||||||
|
|
||||||
socket = io(url, {
|
socket = io(url, {
|
||||||
transports: [ "websocket", "polling" ]
|
transports: [ "websocket", "polling" ]
|
||||||
});
|
});
|
||||||
@ -115,9 +110,6 @@ export default defineComponent({
|
|||||||
socket.on("connect", () => {
|
socket.on("connect", () => {
|
||||||
console.log("Connected to the socket server");
|
console.log("Connected to the socket server");
|
||||||
|
|
||||||
clearTimeout(connectingMsgTimeout);
|
|
||||||
this.socketIO.connecting = false;
|
|
||||||
|
|
||||||
this.socketIO.connectCount++;
|
this.socketIO.connectCount++;
|
||||||
this.socketIO.connected = true;
|
this.socketIO.connected = true;
|
||||||
this.socketIO.showReverseProxyGuide = false;
|
this.socketIO.showReverseProxyGuide = false;
|
||||||
@ -151,11 +143,10 @@ export default defineComponent({
|
|||||||
|
|
||||||
socket.on("connect_error", (err) => {
|
socket.on("connect_error", (err) => {
|
||||||
console.error(`Failed to connect to the backend. Socket.io connect_error: ${err.message}`);
|
console.error(`Failed to connect to the backend. Socket.io connect_error: ${err.message}`);
|
||||||
this.socketIO.connectionErrorMsg = `${this.$t("Cannot connect to the socket server.")} [${err}] ${this.$t("reconnecting...")}`;
|
this.socketIO.connectionErrorMsg = `${this.$t("Cannot connect to the socket server.")} [${err}] ${this.$t("Reconnecting...")}`;
|
||||||
this.socketIO.showReverseProxyGuide = true;
|
this.socketIO.showReverseProxyGuide = true;
|
||||||
this.socketIO.connected = false;
|
this.socketIO.connected = false;
|
||||||
this.socketIO.firstConnect = false;
|
this.socketIO.firstConnect = false;
|
||||||
this.socketIO.connecting = false;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Custom Events
|
// Custom Events
|
||||||
|
@ -41,7 +41,7 @@
|
|||||||
{{ $t("stopStack") }}
|
{{ $t("stopStack") }}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<BDropdown right text="" variant="normal">
|
<BDropdown v-if="!isEditMode && active" right text="" variant="normal">
|
||||||
<BDropdownItem @click="downStack">
|
<BDropdownItem @click="downStack">
|
||||||
<font-awesome-icon icon="stop" class="me-1" />
|
<font-awesome-icon icon="stop" class="me-1" />
|
||||||
{{ $t("downStack") }}
|
{{ $t("downStack") }}
|
||||||
@ -56,13 +56,6 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- URLs -->
|
|
||||||
<div v-if="urls.length > 0" class="mb-3">
|
|
||||||
<a v-for="(url, index) in urls" :key="index" target="_blank" :href="url.url">
|
|
||||||
<span class="badge bg-secondary me-2">{{ url.display }}</span>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Progress Terminal -->
|
<!-- Progress Terminal -->
|
||||||
<transition name="slide-fade" appear>
|
<transition name="slide-fade" appear>
|
||||||
<Terminal
|
<Terminal
|
||||||
@ -118,20 +111,6 @@
|
|||||||
|
|
||||||
<button v-if="false && isEditMode && jsonConfig.services && Object.keys(jsonConfig.services).length > 0" class="btn btn-normal mb-3" @click="addContainer">{{ $t("addContainer") }}</button>
|
<button v-if="false && isEditMode && jsonConfig.services && Object.keys(jsonConfig.services).length > 0" class="btn btn-normal mb-3" @click="addContainer">{{ $t("addContainer") }}</button>
|
||||||
|
|
||||||
<!-- General -->
|
|
||||||
<div v-if="isEditMode">
|
|
||||||
<h4 class="mb-3">{{ $t("extra") }}</h4>
|
|
||||||
<div class="shadow-box big-padding mb-3">
|
|
||||||
<!-- URLs -->
|
|
||||||
<div class="mb-4">
|
|
||||||
<label class="form-label">
|
|
||||||
{{ $tc("url", 2) }}
|
|
||||||
</label>
|
|
||||||
<ArrayInput name="urls" :display-name="$t('url')" placeholder="https://" object-type="x-dockge" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Combined Terminal Output -->
|
<!-- Combined Terminal Output -->
|
||||||
<div v-show="!isEditMode">
|
<div v-show="!isEditMode">
|
||||||
<h4 class="mb-3">Terminal</h4>
|
<h4 class="mb-3">Terminal</h4>
|
||||||
@ -273,34 +252,6 @@ export default {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
|
||||||
urls() {
|
|
||||||
if (!this.jsonConfig["x-dockge"] || !this.jsonConfig["x-dockge"].urls || !Array.isArray(this.jsonConfig["x-dockge"].urls)) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
let urls = [];
|
|
||||||
for (const url of this.jsonConfig["x-dockge"].urls) {
|
|
||||||
let display;
|
|
||||||
try {
|
|
||||||
let obj = new URL(url);
|
|
||||||
let pathname = obj.pathname;
|
|
||||||
if (pathname === "/") {
|
|
||||||
pathname = "";
|
|
||||||
}
|
|
||||||
display = obj.host + pathname + obj.search;
|
|
||||||
} catch (e) {
|
|
||||||
display = url;
|
|
||||||
}
|
|
||||||
|
|
||||||
urls.push({
|
|
||||||
display,
|
|
||||||
url,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return urls;
|
|
||||||
},
|
|
||||||
|
|
||||||
isAdd() {
|
isAdd() {
|
||||||
return this.$route.path === "/compose" && !this.submitted;
|
return this.$route.path === "/compose" && !this.submitted;
|
||||||
},
|
},
|
||||||
@ -368,12 +319,6 @@ export default {
|
|||||||
},
|
},
|
||||||
deep: true,
|
deep: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
$route(to, from) {
|
|
||||||
// Leave Combined Terminal
|
|
||||||
console.debug("leaveCombinedTerminal", from.params.stackName);
|
|
||||||
this.$root.getSocket().emit("leaveCombinedTerminal", this.stack.name, () => {});
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
if (this.isAdd) {
|
if (this.isAdd) {
|
||||||
@ -416,7 +361,7 @@ export default {
|
|||||||
clearTimeout(serviceStatusTimeout);
|
clearTimeout(serviceStatusTimeout);
|
||||||
serviceStatusTimeout = setTimeout(async () => {
|
serviceStatusTimeout = setTimeout(async () => {
|
||||||
this.requestServiceStatus();
|
this.requestServiceStatus();
|
||||||
}, 5000);
|
}, 2000);
|
||||||
},
|
},
|
||||||
|
|
||||||
requestServiceStatus() {
|
requestServiceStatus() {
|
||||||
@ -599,6 +544,10 @@ export default {
|
|||||||
throw new Error("Services must be an object");
|
throw new Error("Services must be an object");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!config.version) {
|
||||||
|
config.version = "3.8";
|
||||||
|
}
|
||||||
|
|
||||||
this.yamlDoc = doc;
|
this.yamlDoc = doc;
|
||||||
this.jsonConfig = config;
|
this.jsonConfig = config;
|
||||||
|
|
||||||
|
@ -2,9 +2,6 @@
|
|||||||
"name": "dockge",
|
"name": "dockge",
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"engines": {
|
|
||||||
"node": ">= 18.0.0 && <= 18.17.1"
|
|
||||||
},
|
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"fmt": "eslint \"**/*.{ts,vue}\" --fix",
|
"fmt": "eslint \"**/*.{ts,vue}\" --fix",
|
||||||
"lint": "eslint \"**/*.{ts,vue}\"",
|
"lint": "eslint \"**/*.{ts,vue}\"",
|
||||||
|
Reference in New Issue
Block a user