mirror of
https://github.com/louislam/dockge.git
synced 2025-08-13 16:17:32 +02:00
Compare commits
7 Commits
Author | SHA1 | Date | |
---|---|---|---|
f3dd323daa | |||
3848e8cbb9 | |||
5bbd3a906a | |||
fd7f539089 | |||
16a5f2a175 | |||
7a0d4fc62a | |||
d0712d77ab |
21
README.md
21
README.md
@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
A fancy, easy-to-use and reactive self-hosted docker compose.yaml stack-oriented manager.
|
A fancy, easy-to-use and reactive self-hosted docker compose.yaml stack-oriented manager.
|
||||||
|
|
||||||
   
|
      
|
||||||
|
|
||||||
<img src="https://github.com/louislam/dockge/assets/1336778/26a583e1-ecb1-4a8d-aedf-76157d714ad7" width="900" alt="" />
|
<img src="https://github.com/louislam/dockge/assets/1336778/26a583e1-ecb1-4a8d-aedf-76157d714ad7" width="900" alt="" />
|
||||||
|
|
||||||
@ -14,17 +14,20 @@ View Video: https://youtu.be/AWAlOQeNpgU?t=48
|
|||||||
|
|
||||||
## ⭐ Features
|
## ⭐ Features
|
||||||
|
|
||||||
- 🧑💼 Manage your `compose.yaml` files
|
- Manage `compose.yaml`
|
||||||
- Create/Edit/Start/Stop/Restart/Delete
|
- Create/Edit/Start/Stop/Restart/Delete
|
||||||
- Update Docker Images
|
- Update Docker Images
|
||||||
- ⌨️ Interactive Editor for `compose.yaml`
|
- Interactive Editor for `compose.yaml`
|
||||||
- 🦦 Interactive Web Terminal
|
- Interactive Web Terminal
|
||||||
- 🕷️ (1.4.0 🆕) Multiple agents support - You can manage multiple stacks from different Docker hosts in one single interface
|
- Reactive
|
||||||
- 🏪 Convert `docker run ...` commands into `compose.yaml`
|
- Everything is just responsive. Progress (Pull/Up/Down) and terminal output are in real-time
|
||||||
- 📙 File based structure - Dockge won't kidnap your compose files, they are stored on your drive as usual. You can interact with them using normal `docker compose` commands
|
- Easy-to-use & fancy UI
|
||||||
|
- If you love Uptime Kuma's UI/UX, you will love this one too
|
||||||
|
- Convert `docker run ...` commands into `compose.yaml`
|
||||||
|
- File based structure
|
||||||
|
- Dockge won't kidnap your compose files, they are stored on your drive as usual. You can interact with them using normal `docker compose` commands
|
||||||
<img src="https://github.com/louislam/dockge/assets/1336778/cc071864-592e-4909-b73a-343a57494002" width=300 />
|
<img src="https://github.com/louislam/dockge/assets/1336778/cc071864-592e-4909-b73a-343a57494002" width=300 />
|
||||||
- 🚄 Reactive - Everything is just responsive. Progress (Pull/Up/Down) and terminal output are in real-time
|
|
||||||
- 🐣 Easy-to-use & fancy UI - If you love Uptime Kuma's UI/UX, you will love this one too
|
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
@ -1,291 +0,0 @@
|
|||||||
import { DockgeSocket } from "./util-server";
|
|
||||||
import { io, Socket as SocketClient } from "socket.io-client";
|
|
||||||
import { log } from "./log";
|
|
||||||
import { Agent } from "./models/agent";
|
|
||||||
import { isDev, LooseObject, sleep } from "../common/util-common";
|
|
||||||
import semver from "semver";
|
|
||||||
import { R } from "redbean-node";
|
|
||||||
import dayjs, { Dayjs } from "dayjs";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Dockge Instance Manager
|
|
||||||
* One AgentManager per Socket connection
|
|
||||||
*/
|
|
||||||
export class AgentManager {
|
|
||||||
|
|
||||||
protected socket : DockgeSocket;
|
|
||||||
protected agentSocketList : Record<string, SocketClient> = {};
|
|
||||||
protected agentLoggedInList : Record<string, boolean> = {};
|
|
||||||
protected _firstConnectTime : Dayjs = dayjs();
|
|
||||||
|
|
||||||
constructor(socket: DockgeSocket) {
|
|
||||||
this.socket = socket;
|
|
||||||
}
|
|
||||||
|
|
||||||
get firstConnectTime() : Dayjs {
|
|
||||||
return this._firstConnectTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
test(url : string, username : string, password : string) : Promise<void> {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
let obj = new URL(url);
|
|
||||||
let endpoint = obj.host;
|
|
||||||
|
|
||||||
if (!endpoint) {
|
|
||||||
reject(new Error("Invalid Dockge URL"));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.agentSocketList[endpoint]) {
|
|
||||||
reject(new Error("The Dockge URL already exists"));
|
|
||||||
}
|
|
||||||
|
|
||||||
let client = io(url, {
|
|
||||||
reconnection: false,
|
|
||||||
extraHeaders: {
|
|
||||||
endpoint,
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
client.on("connect", () => {
|
|
||||||
client.emit("login", {
|
|
||||||
username: username,
|
|
||||||
password: password,
|
|
||||||
}, (res : LooseObject) => {
|
|
||||||
if (res.ok) {
|
|
||||||
resolve();
|
|
||||||
} else {
|
|
||||||
reject(new Error(res.msg));
|
|
||||||
}
|
|
||||||
client.disconnect();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
client.on("connect_error", (err) => {
|
|
||||||
if (err.message === "xhr poll error") {
|
|
||||||
reject(new Error("Unable to connect to the Dockge instance"));
|
|
||||||
} else {
|
|
||||||
reject(err);
|
|
||||||
}
|
|
||||||
client.disconnect();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param url
|
|
||||||
* @param username
|
|
||||||
* @param password
|
|
||||||
*/
|
|
||||||
async add(url : string, username : string, password : string) : Promise<Agent> {
|
|
||||||
let bean = R.dispense("agent") as Agent;
|
|
||||||
bean.url = url;
|
|
||||||
bean.username = username;
|
|
||||||
bean.password = password;
|
|
||||||
await R.store(bean);
|
|
||||||
return bean;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param url
|
|
||||||
*/
|
|
||||||
async remove(url : string) {
|
|
||||||
let bean = await R.findOne("agent", " url = ? ", [
|
|
||||||
url,
|
|
||||||
]);
|
|
||||||
|
|
||||||
if (bean) {
|
|
||||||
await R.trash(bean);
|
|
||||||
let endpoint = bean.endpoint;
|
|
||||||
delete this.agentSocketList[endpoint];
|
|
||||||
} else {
|
|
||||||
throw new Error("Agent not found");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
connect(url : string, username : string, password : string) {
|
|
||||||
let obj = new URL(url);
|
|
||||||
let endpoint = obj.host;
|
|
||||||
|
|
||||||
this.socket.emit("agentStatus", {
|
|
||||||
endpoint: endpoint,
|
|
||||||
status: "connecting",
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!endpoint) {
|
|
||||||
log.error("agent-manager", "Invalid endpoint: " + endpoint + " URL: " + url);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.agentSocketList[endpoint]) {
|
|
||||||
log.debug("agent-manager", "Already connected to the socket server: " + endpoint);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
log.info("agent-manager", "Connecting to the socket server: " + endpoint);
|
|
||||||
let client = io(url, {
|
|
||||||
extraHeaders: {
|
|
||||||
endpoint,
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
client.on("connect", () => {
|
|
||||||
log.info("agent-manager", "Connected to the socket server: " + endpoint);
|
|
||||||
|
|
||||||
client.emit("login", {
|
|
||||||
username: username,
|
|
||||||
password: password,
|
|
||||||
}, (res : LooseObject) => {
|
|
||||||
if (res.ok) {
|
|
||||||
log.info("agent-manager", "Logged in to the socket server: " + endpoint);
|
|
||||||
this.agentLoggedInList[endpoint] = true;
|
|
||||||
this.socket.emit("agentStatus", {
|
|
||||||
endpoint: endpoint,
|
|
||||||
status: "online",
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
log.error("agent-manager", "Failed to login to the socket server: " + endpoint);
|
|
||||||
this.agentLoggedInList[endpoint] = false;
|
|
||||||
this.socket.emit("agentStatus", {
|
|
||||||
endpoint: endpoint,
|
|
||||||
status: "offline",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
client.on("connect_error", (err) => {
|
|
||||||
log.error("agent-manager", "Error from the socket server: " + endpoint);
|
|
||||||
this.socket.emit("agentStatus", {
|
|
||||||
endpoint: endpoint,
|
|
||||||
status: "offline",
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
client.on("disconnect", () => {
|
|
||||||
log.info("agent-manager", "Disconnected from the socket server: " + endpoint);
|
|
||||||
this.socket.emit("agentStatus", {
|
|
||||||
endpoint: endpoint,
|
|
||||||
status: "offline",
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
client.on("agent", (...args : unknown[]) => {
|
|
||||||
this.socket.emit("agent", ...args);
|
|
||||||
});
|
|
||||||
|
|
||||||
client.on("info", (res) => {
|
|
||||||
log.debug("agent-manager", res);
|
|
||||||
|
|
||||||
// Disconnect if the version is lower than 1.4.0
|
|
||||||
if (!isDev && semver.satisfies(res.version, "< 1.4.0")) {
|
|
||||||
this.socket.emit("agentStatus", {
|
|
||||||
endpoint: endpoint,
|
|
||||||
status: "offline",
|
|
||||||
msg: `${endpoint}: Unsupported version: ` + res.version,
|
|
||||||
});
|
|
||||||
client.disconnect();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.agentSocketList[endpoint] = client;
|
|
||||||
}
|
|
||||||
|
|
||||||
disconnect(endpoint : string) {
|
|
||||||
let client = this.agentSocketList[endpoint];
|
|
||||||
client?.disconnect();
|
|
||||||
}
|
|
||||||
|
|
||||||
async connectAll() {
|
|
||||||
this._firstConnectTime = dayjs();
|
|
||||||
|
|
||||||
if (this.socket.endpoint) {
|
|
||||||
log.info("agent-manager", "This connection is connected as an agent, skip connectAll()");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let list : Record<string, Agent> = await Agent.getAgentList();
|
|
||||||
|
|
||||||
if (Object.keys(list).length !== 0) {
|
|
||||||
log.info("agent-manager", "Connecting to all instance socket server(s)...");
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let endpoint in list) {
|
|
||||||
let agent = list[endpoint];
|
|
||||||
this.connect(agent.url, agent.username, agent.password);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
disconnectAll() {
|
|
||||||
for (let endpoint in this.agentSocketList) {
|
|
||||||
this.disconnect(endpoint);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async emitToEndpoint(endpoint: string, eventName: string, ...args : unknown[]) {
|
|
||||||
log.debug("agent-manager", "Emitting event to endpoint: " + endpoint);
|
|
||||||
let client = this.agentSocketList[endpoint];
|
|
||||||
|
|
||||||
if (!client) {
|
|
||||||
log.error("agent-manager", "Socket client not found for endpoint: " + endpoint);
|
|
||||||
throw new Error("Socket client not found for endpoint: " + endpoint);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!client.connected || !this.agentLoggedInList[endpoint]) {
|
|
||||||
// Maybe the request is too quick, the socket is not connected yet, check firstConnectTime
|
|
||||||
// If it is within 10 seconds, we should apply retry logic here
|
|
||||||
let diff = dayjs().diff(this.firstConnectTime, "second");
|
|
||||||
log.debug("agent-manager", endpoint + ": diff: " + diff);
|
|
||||||
let ok = false;
|
|
||||||
while (diff < 10) {
|
|
||||||
if (client.connected && this.agentLoggedInList[endpoint]) {
|
|
||||||
log.debug("agent-manager", `${endpoint}: Connected & Logged in`);
|
|
||||||
ok = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
log.debug("agent-manager", endpoint + ": not ready yet, retrying in 1 second...");
|
|
||||||
await sleep(1000);
|
|
||||||
diff = dayjs().diff(this.firstConnectTime, "second");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!ok) {
|
|
||||||
log.error("agent-manager", `${endpoint}: Socket client not connected`);
|
|
||||||
throw new Error("Socket client not connected for endpoint: " + endpoint);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
client.emit("agent", endpoint, eventName, ...args);
|
|
||||||
}
|
|
||||||
|
|
||||||
emitToAllEndpoints(eventName: string, ...args : unknown[]) {
|
|
||||||
log.debug("agent-manager", "Emitting event to all endpoints");
|
|
||||||
for (let endpoint in this.agentSocketList) {
|
|
||||||
this.emitToEndpoint(endpoint, eventName, ...args).catch((e) => {
|
|
||||||
log.warn("agent-manager", e.message);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async sendAgentList() {
|
|
||||||
let list = await Agent.getAgentList();
|
|
||||||
let result : Record<string, LooseObject> = {};
|
|
||||||
|
|
||||||
// Myself
|
|
||||||
result[""] = {
|
|
||||||
url: "",
|
|
||||||
username: "",
|
|
||||||
endpoint: "",
|
|
||||||
};
|
|
||||||
|
|
||||||
for (let endpoint in list) {
|
|
||||||
let agent = list[endpoint];
|
|
||||||
result[endpoint] = agent.toJSON();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.socket.emit("agentList", {
|
|
||||||
ok: true,
|
|
||||||
agentList: result,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,7 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
@ -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 "../common/util-common";
|
import { sleep } from "./util-common";
|
||||||
|
|
||||||
interface DBConfig {
|
interface DBConfig {
|
||||||
type?: "sqlite" | "mysql";
|
type?: "sqlite" | "mysql";
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
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";
|
||||||
@ -18,26 +17,23 @@ 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, LooseObject } from "../common/util-common";
|
import { genSecret, isDev } from "./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 "./agent-socket-handlers/docker-socket-handler";
|
import { DockerSocketHandler } from "./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 "./agent-socket-handlers/terminal-socket-handler";
|
import { TerminalSocketHandler } from "./socket-handlers/terminal-socket-handler";
|
||||||
import { Stack } from "./stack";
|
import { Stack } from "./stack";
|
||||||
import { Cron } from "croner";
|
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 { AgentManager } from "./agent-manager";
|
|
||||||
import { AgentProxySocketHandler } from "./socket-handlers/agent-proxy-socket-handler";
|
|
||||||
import { AgentSocketHandler } from "./agent-socket-handler";
|
|
||||||
import { AgentSocket } from "../common/agent-socket";
|
|
||||||
import { ManageAgentSocketHandler } from "./socket-handlers/manage-agent-socket-handler";
|
|
||||||
import { Terminal } from "./terminal";
|
import { Terminal } from "./terminal";
|
||||||
|
|
||||||
|
import "dotenv/config";
|
||||||
|
|
||||||
export class DockgeServer {
|
export class DockgeServer {
|
||||||
app : Express;
|
app : Express;
|
||||||
httpServer : http.Server;
|
httpServer : http.Server;
|
||||||
@ -54,19 +50,10 @@ export class DockgeServer {
|
|||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* List of socket handlers (no agent support)
|
* List of socket handlers
|
||||||
*/
|
*/
|
||||||
socketHandlerList : SocketHandler[] = [
|
socketHandlerList : SocketHandler[] = [
|
||||||
new MainSocketHandler(),
|
new MainSocketHandler(),
|
||||||
new ManageAgentSocketHandler(),
|
|
||||||
];
|
|
||||||
|
|
||||||
agentProxySocketHandler = new AgentProxySocketHandler();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* List of socket handlers (support agent)
|
|
||||||
*/
|
|
||||||
agentSocketHandlerList : AgentSocketHandler[] = [
|
|
||||||
new DockerSocketHandler(),
|
new DockerSocketHandler(),
|
||||||
new TerminalSocketHandler(),
|
new TerminalSocketHandler(),
|
||||||
];
|
];
|
||||||
@ -209,7 +196,7 @@ export class DockgeServer {
|
|||||||
cors,
|
cors,
|
||||||
allowRequest: (req, callback) => {
|
allowRequest: (req, callback) => {
|
||||||
let isOriginValid = true;
|
let isOriginValid = true;
|
||||||
const bypass = isDev || process.env.UPTIME_KUMA_WS_ORIGIN_CHECK === "bypass";
|
const bypass = isDev;
|
||||||
|
|
||||||
if (!bypass) {
|
if (!bypass) {
|
||||||
let host = req.headers.host;
|
let host = req.headers.host;
|
||||||
@ -243,52 +230,20 @@ export class DockgeServer {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.io.on("connection", async (socket: Socket) => {
|
this.io.on("connection", async (socket: Socket) => {
|
||||||
let dockgeSocket = socket as DockgeSocket;
|
log.info("server", "Socket connected!");
|
||||||
dockgeSocket.instanceManager = new AgentManager(dockgeSocket);
|
|
||||||
dockgeSocket.emitAgent = (event : string, ...args : unknown[]) => {
|
|
||||||
let obj = args[0];
|
|
||||||
if (typeof(obj) === "object") {
|
|
||||||
let obj2 = obj as LooseObject;
|
|
||||||
obj2.endpoint = dockgeSocket.endpoint;
|
|
||||||
}
|
|
||||||
dockgeSocket.emit("agent", event, ...args);
|
|
||||||
};
|
|
||||||
|
|
||||||
if (typeof(socket.request.headers.endpoint) === "string") {
|
this.sendInfo(socket, true);
|
||||||
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");
|
||||||
dockgeSocket.emit("setup");
|
socket.emit("setup");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create socket handlers (original, no agent support)
|
// Create socket handlers
|
||||||
for (const socketHandler of this.socketHandlerList) {
|
for (const socketHandler of this.socketHandlerList) {
|
||||||
socketHandler.create(dockgeSocket, this);
|
socketHandler.create(socket as 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
|
||||||
// ***************************
|
// ***************************
|
||||||
@ -296,18 +251,12 @@ 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(dockgeSocket, await R.findOne("user") as User);
|
this.afterLogin(socket as DockgeSocket, await R.findOne("user") as User);
|
||||||
dockgeSocket.emit("autoLogin");
|
socket.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", () => {
|
||||||
@ -332,11 +281,6 @@ export class DockgeServer {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
log.error("server", e);
|
log.error("server", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
socket.instanceManager.sendAgentList();
|
|
||||||
|
|
||||||
// Also connect to other dockge instances
|
|
||||||
socket.instanceManager.connectAll();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -575,34 +519,26 @@ 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 socketList = this.io.sockets.sockets.values();
|
let roomList = this.io.sockets.adapter.rooms.keys();
|
||||||
|
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 (dockgeSocket.userID) {
|
if (Number(room)) {
|
||||||
|
|
||||||
// Get the list only if there is a logged in user
|
// Get the list only if there is a room
|
||||||
if (!stackList) {
|
if (!map) {
|
||||||
stackList = await Stack.getStackList(this, useCache);
|
map = new Map();
|
||||||
|
let stackList = await Stack.getStackList(this, useCache);
|
||||||
|
|
||||||
|
for (let [ stackName, stack ] of stackList) {
|
||||||
|
map.set(stackName, stack.toSimpleJSON());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let map : Map<string, object> = new Map();
|
log.debug("server", "Send stack list to room " + room);
|
||||||
|
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 to user: " + dockgeSocket.id + " (" + dockgeSocket.endpoint + ")");
|
|
||||||
dockgeSocket.emitAgent("stackList", {
|
|
||||||
ok: true,
|
ok: true,
|
||||||
stackList: Object.fromEntries(map),
|
stackList: Object.fromEntries(map),
|
||||||
});
|
});
|
||||||
@ -610,6 +546,25 @@ export class DockgeServer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async sendStackStatusList() {
|
||||||
|
let statusList = await Stack.getStatusList();
|
||||||
|
|
||||||
|
let roomList = this.io.sockets.adapter.rooms.keys();
|
||||||
|
|
||||||
|
for (let room of roomList) {
|
||||||
|
// Check if the room is a number (user id)
|
||||||
|
if (Number(room)) {
|
||||||
|
log.debug("server", "Send stack status list to room " + room);
|
||||||
|
this.io.to(room).emit("stackStatusList", {
|
||||||
|
ok: true,
|
||||||
|
stackStatusList: Object.fromEntries(statusList),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
log.debug("server", "Skip sending stack status list to room " + room);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async getDockerNetworkList() : Promise<string[]> {
|
async getDockerNetworkList() : Promise<string[]> {
|
||||||
let res = await childProcessAsync.spawn("docker", [ "network", "ls", "--format", "{{.Name}}" ], {
|
let res = await childProcessAsync.spawn("docker", [ "network", "ls", "--format", "{{.Name}}" ], {
|
||||||
encoding: "utf-8",
|
encoding: "utf-8",
|
||||||
@ -663,10 +618,10 @@ export class DockgeServer {
|
|||||||
* @param {string} userID
|
* @param {string} userID
|
||||||
* @param {string?} currentSocketID
|
* @param {string?} currentSocketID
|
||||||
*/
|
*/
|
||||||
disconnectAllSocketClients(userID: number | undefined, currentSocketID? : string) {
|
disconnectAllSocketClients(userID: number, currentSocketID? : string) {
|
||||||
for (const rawSocket of this.io.sockets.sockets.values()) {
|
for (const rawSocket of this.io.sockets.sockets.values()) {
|
||||||
let socket = rawSocket as DockgeSocket;
|
let socket = rawSocket as DockgeSocket;
|
||||||
if ((!userID || socket.userID === userID) && socket.id !== currentSocketID) {
|
if (socket.userID === userID && socket.id !== currentSocketID) {
|
||||||
try {
|
try {
|
||||||
socket.emit("refresh");
|
socket.emit("refresh");
|
||||||
socket.disconnect();
|
socket.disconnect();
|
||||||
|
@ -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 "../common/util-common";
|
import { intHash, isDev } from "./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,16 +0,0 @@
|
|||||||
import { Knex } from "knex";
|
|
||||||
|
|
||||||
export async function up(knex: Knex): Promise<void> {
|
|
||||||
// Create the user table
|
|
||||||
return knex.schema.createTable("agent", (table) => {
|
|
||||||
table.increments("id");
|
|
||||||
table.string("url", 255).notNullable().unique();
|
|
||||||
table.string("username", 255).notNullable();
|
|
||||||
table.string("password", 255).notNullable();
|
|
||||||
table.boolean("active").notNullable().defaultTo(true);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function down(knex: Knex): Promise<void> {
|
|
||||||
return knex.schema.dropTable("agent");
|
|
||||||
}
|
|
@ -1,31 +0,0 @@
|
|||||||
import { BeanModel } from "redbean-node/dist/bean-model";
|
|
||||||
import { R } from "redbean-node";
|
|
||||||
import { LooseObject } from "../../common/util-common";
|
|
||||||
|
|
||||||
export class Agent extends BeanModel {
|
|
||||||
|
|
||||||
static async getAgentList() : Promise<Record<string, Agent>> {
|
|
||||||
let list = await R.findAll("agent") as Agent[];
|
|
||||||
let result : Record<string, Agent> = {};
|
|
||||||
for (let agent of list) {
|
|
||||||
result[agent.endpoint] = agent;
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
get endpoint() : string {
|
|
||||||
let obj = new URL(this.url);
|
|
||||||
return obj.host;
|
|
||||||
}
|
|
||||||
|
|
||||||
toJSON() : LooseObject {
|
|
||||||
return {
|
|
||||||
url: this.url,
|
|
||||||
username: this.username,
|
|
||||||
endpoint: this.endpoint,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Agent;
|
|
@ -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 "../common/util-common";
|
import { LooseObject } from "./util-common";
|
||||||
|
|
||||||
export class Settings {
|
export class Settings {
|
||||||
|
|
||||||
|
@ -1,47 +0,0 @@
|
|||||||
import { SocketHandler } from "../socket-handler.js";
|
|
||||||
import { DockgeServer } from "../dockge-server";
|
|
||||||
import { log } from "../log";
|
|
||||||
import { checkLogin, DockgeSocket } from "../util-server";
|
|
||||||
import { AgentSocket } from "../../common/agent-socket";
|
|
||||||
import { ALL_ENDPOINTS } from "../../common/util-common";
|
|
||||||
|
|
||||||
export class 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: " + endpoint);
|
|
||||||
}
|
|
||||||
if (typeof(eventName) !== "string") {
|
|
||||||
throw new Error("Event name must be a string");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (endpoint === ALL_ENDPOINTS) { // Send to all endpoints
|
|
||||||
log.debug("agent", "Sending to all endpoints: " + eventName);
|
|
||||||
socket.instanceManager.emitToAllEndpoints(eventName, ...args);
|
|
||||||
|
|
||||||
} else if (!endpoint || endpoint === socket.endpoint) { // Direct connection or matching endpoint
|
|
||||||
log.debug("agent", "Matched endpoint: " + eventName);
|
|
||||||
agentSocket.call(eventName, ...args);
|
|
||||||
|
|
||||||
} else {
|
|
||||||
log.debug("agent", "Proxying request to " + endpoint + " for " + eventName);
|
|
||||||
await 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,44 +1,45 @@
|
|||||||
import { AgentSocketHandler } from "../agent-socket-handler";
|
import { SocketHandler } from "../socket-handler.js";
|
||||||
import { DockgeServer } from "../dockge-server";
|
import { DockgeServer } from "../dockge-server";
|
||||||
import { callbackError, callbackResult, 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";
|
|
||||||
|
|
||||||
export class DockerSocketHandler extends AgentSocketHandler {
|
// @ts-ignore
|
||||||
create(socket : DockgeSocket, server : DockgeServer, agentSocket : AgentSocket) {
|
import composerize from "composerize";
|
||||||
// Do not call super.create()
|
|
||||||
|
|
||||||
agentSocket.on("deployStack", async (name : unknown, composeYAML : unknown, composeENV : unknown, isAdd : unknown, callback) => {
|
export class DockerSocketHandler extends SocketHandler {
|
||||||
|
create(socket : DockgeSocket, server : DockgeServer) {
|
||||||
|
|
||||||
|
socket.on("deployStack", async (name : unknown, composeYAML : unknown, composeENV : unknown, isAdd : unknown, callback) => {
|
||||||
try {
|
try {
|
||||||
checkLogin(socket);
|
checkLogin(socket);
|
||||||
const stack = await this.saveStack(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();
|
||||||
callbackResult({
|
callback({
|
||||||
ok: true,
|
ok: true,
|
||||||
msg: "Deployed",
|
msg: "Deployed",
|
||||||
}, callback);
|
});
|
||||||
stack.joinCombinedTerminal(socket);
|
stack.joinCombinedTerminal(socket);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
callbackError(e, callback);
|
callbackError(e, callback);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
agentSocket.on("saveStack", async (name : unknown, composeYAML : unknown, composeENV : unknown, isAdd : unknown, callback) => {
|
socket.on("saveStack", async (name : unknown, composeYAML : unknown, composeENV : unknown, isAdd : unknown, callback) => {
|
||||||
try {
|
try {
|
||||||
checkLogin(socket);
|
checkLogin(socket);
|
||||||
await this.saveStack(server, name, composeYAML, composeENV, isAdd);
|
this.saveStack(socket, server, name, composeYAML, composeENV, isAdd);
|
||||||
callbackResult({
|
callback({
|
||||||
ok: true,
|
ok: true,
|
||||||
"msg": "Saved"
|
"msg": "Saved"
|
||||||
}, callback);
|
});
|
||||||
server.sendStackList();
|
server.sendStackList();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
callbackError(e, callback);
|
callbackError(e, callback);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
agentSocket.on("deleteStack", async (name : unknown, callback) => {
|
socket.on("deleteStack", async (name : unknown, callback) => {
|
||||||
try {
|
try {
|
||||||
checkLogin(socket);
|
checkLogin(socket);
|
||||||
if (typeof(name) !== "string") {
|
if (typeof(name) !== "string") {
|
||||||
@ -54,17 +55,17 @@ export class DockerSocketHandler extends AgentSocketHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
server.sendStackList();
|
server.sendStackList();
|
||||||
callbackResult({
|
callback({
|
||||||
ok: true,
|
ok: true,
|
||||||
msg: "Deleted"
|
msg: "Deleted"
|
||||||
}, callback);
|
});
|
||||||
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
callbackError(e, callback);
|
callbackError(e, callback);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
agentSocket.on("getStack", async (stackName : unknown, callback) => {
|
socket.on("getStack", async (stackName : unknown, callback) => {
|
||||||
try {
|
try {
|
||||||
checkLogin(socket);
|
checkLogin(socket);
|
||||||
|
|
||||||
@ -78,31 +79,31 @@ export class DockerSocketHandler extends AgentSocketHandler {
|
|||||||
stack.joinCombinedTerminal(socket);
|
stack.joinCombinedTerminal(socket);
|
||||||
}
|
}
|
||||||
|
|
||||||
callbackResult({
|
callback({
|
||||||
ok: true,
|
ok: true,
|
||||||
stack: await stack.toJSON(socket.endpoint),
|
stack: stack.toJSON(),
|
||||||
}, callback);
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
callbackError(e, callback);
|
callbackError(e, callback);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// requestStackList
|
// requestStackList
|
||||||
agentSocket.on("requestStackList", async (callback) => {
|
socket.on("requestStackList", async (callback) => {
|
||||||
try {
|
try {
|
||||||
checkLogin(socket);
|
checkLogin(socket);
|
||||||
server.sendStackList();
|
server.sendStackList();
|
||||||
callbackResult({
|
callback({
|
||||||
ok: true,
|
ok: true,
|
||||||
msg: "Updated"
|
msg: "Updated"
|
||||||
}, callback);
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
callbackError(e, callback);
|
callbackError(e, callback);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// startStack
|
// startStack
|
||||||
agentSocket.on("startStack", async (stackName : unknown, callback) => {
|
socket.on("startStack", async (stackName : unknown, callback) => {
|
||||||
try {
|
try {
|
||||||
checkLogin(socket);
|
checkLogin(socket);
|
||||||
|
|
||||||
@ -112,10 +113,10 @@ export class DockerSocketHandler extends AgentSocketHandler {
|
|||||||
|
|
||||||
const stack = await Stack.getStack(server, stackName);
|
const stack = await Stack.getStack(server, stackName);
|
||||||
await stack.start(socket);
|
await stack.start(socket);
|
||||||
callbackResult({
|
callback({
|
||||||
ok: true,
|
ok: true,
|
||||||
msg: "Started"
|
msg: "Started"
|
||||||
}, callback);
|
});
|
||||||
server.sendStackList();
|
server.sendStackList();
|
||||||
|
|
||||||
stack.joinCombinedTerminal(socket);
|
stack.joinCombinedTerminal(socket);
|
||||||
@ -126,7 +127,7 @@ export class DockerSocketHandler extends AgentSocketHandler {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// stopStack
|
// stopStack
|
||||||
agentSocket.on("stopStack", async (stackName : unknown, callback) => {
|
socket.on("stopStack", async (stackName : unknown, callback) => {
|
||||||
try {
|
try {
|
||||||
checkLogin(socket);
|
checkLogin(socket);
|
||||||
|
|
||||||
@ -136,10 +137,10 @@ export class DockerSocketHandler extends AgentSocketHandler {
|
|||||||
|
|
||||||
const stack = await Stack.getStack(server, stackName);
|
const stack = await Stack.getStack(server, stackName);
|
||||||
await stack.stop(socket);
|
await stack.stop(socket);
|
||||||
callbackResult({
|
callback({
|
||||||
ok: true,
|
ok: true,
|
||||||
msg: "Stopped"
|
msg: "Stopped"
|
||||||
}, callback);
|
});
|
||||||
server.sendStackList();
|
server.sendStackList();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
callbackError(e, callback);
|
callbackError(e, callback);
|
||||||
@ -147,7 +148,7 @@ export class DockerSocketHandler extends AgentSocketHandler {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// restartStack
|
// restartStack
|
||||||
agentSocket.on("restartStack", async (stackName : unknown, callback) => {
|
socket.on("restartStack", async (stackName : unknown, callback) => {
|
||||||
try {
|
try {
|
||||||
checkLogin(socket);
|
checkLogin(socket);
|
||||||
|
|
||||||
@ -157,10 +158,10 @@ export class DockerSocketHandler extends AgentSocketHandler {
|
|||||||
|
|
||||||
const stack = await Stack.getStack(server, stackName);
|
const stack = await Stack.getStack(server, stackName);
|
||||||
await stack.restart(socket);
|
await stack.restart(socket);
|
||||||
callbackResult({
|
callback({
|
||||||
ok: true,
|
ok: true,
|
||||||
msg: "Restarted"
|
msg: "Restarted"
|
||||||
}, callback);
|
});
|
||||||
server.sendStackList();
|
server.sendStackList();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
callbackError(e, callback);
|
callbackError(e, callback);
|
||||||
@ -168,7 +169,7 @@ export class DockerSocketHandler extends AgentSocketHandler {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// updateStack
|
// updateStack
|
||||||
agentSocket.on("updateStack", async (stackName : unknown, callback) => {
|
socket.on("updateStack", async (stackName : unknown, callback) => {
|
||||||
try {
|
try {
|
||||||
checkLogin(socket);
|
checkLogin(socket);
|
||||||
|
|
||||||
@ -178,10 +179,10 @@ export class DockerSocketHandler extends AgentSocketHandler {
|
|||||||
|
|
||||||
const stack = await Stack.getStack(server, stackName);
|
const stack = await Stack.getStack(server, stackName);
|
||||||
await stack.update(socket);
|
await stack.update(socket);
|
||||||
callbackResult({
|
callback({
|
||||||
ok: true,
|
ok: true,
|
||||||
msg: "Updated"
|
msg: "Updated"
|
||||||
}, callback);
|
});
|
||||||
server.sendStackList();
|
server.sendStackList();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
callbackError(e, callback);
|
callbackError(e, callback);
|
||||||
@ -189,7 +190,7 @@ export class DockerSocketHandler extends AgentSocketHandler {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// down stack
|
// down stack
|
||||||
agentSocket.on("downStack", async (stackName : unknown, callback) => {
|
socket.on("downStack", async (stackName : unknown, callback) => {
|
||||||
try {
|
try {
|
||||||
checkLogin(socket);
|
checkLogin(socket);
|
||||||
|
|
||||||
@ -199,10 +200,10 @@ export class DockerSocketHandler extends AgentSocketHandler {
|
|||||||
|
|
||||||
const stack = await Stack.getStack(server, stackName);
|
const stack = await Stack.getStack(server, stackName);
|
||||||
await stack.down(socket);
|
await stack.down(socket);
|
||||||
callbackResult({
|
callback({
|
||||||
ok: true,
|
ok: true,
|
||||||
msg: "Downed"
|
msg: "Downed"
|
||||||
}, callback);
|
});
|
||||||
server.sendStackList();
|
server.sendStackList();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
callbackError(e, callback);
|
callbackError(e, callback);
|
||||||
@ -210,7 +211,7 @@ export class DockerSocketHandler extends AgentSocketHandler {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Services status
|
// Services status
|
||||||
agentSocket.on("serviceStatusList", async (stackName : unknown, callback) => {
|
socket.on("serviceStatusList", async (stackName : unknown, callback) => {
|
||||||
try {
|
try {
|
||||||
checkLogin(socket);
|
checkLogin(socket);
|
||||||
|
|
||||||
@ -220,31 +221,50 @@ export class DockerSocketHandler extends AgentSocketHandler {
|
|||||||
|
|
||||||
const stack = await Stack.getStack(server, stackName, true);
|
const stack = await Stack.getStack(server, stackName, true);
|
||||||
const serviceStatusList = Object.fromEntries(await stack.getServiceStatusList());
|
const serviceStatusList = Object.fromEntries(await stack.getServiceStatusList());
|
||||||
callbackResult({
|
callback({
|
||||||
ok: true,
|
ok: true,
|
||||||
serviceStatusList,
|
serviceStatusList,
|
||||||
}, callback);
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
callbackError(e, callback);
|
callbackError(e, callback);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// getExternalNetworkList
|
// getExternalNetworkList
|
||||||
agentSocket.on("getDockerNetworkList", async (callback) => {
|
socket.on("getDockerNetworkList", async (callback) => {
|
||||||
try {
|
try {
|
||||||
checkLogin(socket);
|
checkLogin(socket);
|
||||||
const dockerNetworkList = await server.getDockerNetworkList();
|
const dockerNetworkList = await server.getDockerNetworkList();
|
||||||
callbackResult({
|
callback({
|
||||||
ok: true,
|
ok: true,
|
||||||
dockerNetworkList,
|
dockerNetworkList,
|
||||||
}, callback);
|
});
|
||||||
|
} catch (e) {
|
||||||
|
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) {
|
} catch (e) {
|
||||||
callbackError(e, callback);
|
callbackError(e, callback);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async saveStack(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> {
|
||||||
// Check types
|
// Check types
|
||||||
if (typeof(name) !== "string") {
|
if (typeof(name) !== "string") {
|
||||||
throw new ValidationError("Name must be a string");
|
throw new ValidationError("Name must be a string");
|
@ -1,5 +1,3 @@
|
|||||||
// @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";
|
||||||
@ -7,14 +5,7 @@ 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 {
|
import { checkLogin, DockgeSocket, doubleCheckPassword, JWTDecoded } from "../util-server";
|
||||||
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";
|
||||||
@ -271,6 +262,8 @@ export class MainSocketHandler extends SocketHandler {
|
|||||||
await doubleCheckPassword(socket, currentPassword);
|
await doubleCheckPassword(socket, currentPassword);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log(data);
|
||||||
|
|
||||||
await Settings.setSettings("general", data);
|
await Settings.setSettings("general", data);
|
||||||
|
|
||||||
callback({
|
callback({
|
||||||
@ -301,25 +294,6 @@ 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> {
|
||||||
|
@ -1,70 +0,0 @@
|
|||||||
import { SocketHandler } from "../socket-handler.js";
|
|
||||||
import { DockgeServer } from "../dockge-server";
|
|
||||||
import { log } from "../log";
|
|
||||||
import { callbackError, callbackResult, checkLogin, DockgeSocket } from "../util-server";
|
|
||||||
import { LooseObject } from "../../common/util-common";
|
|
||||||
|
|
||||||
export class ManageAgentSocketHandler extends SocketHandler {
|
|
||||||
|
|
||||||
create(socket : DockgeSocket, server : DockgeServer) {
|
|
||||||
// addAgent
|
|
||||||
socket.on("addAgent", async (requestData : unknown, callback : unknown) => {
|
|
||||||
try {
|
|
||||||
log.debug("manage-agent-socket-handler", "addAgent");
|
|
||||||
checkLogin(socket);
|
|
||||||
|
|
||||||
if (typeof(requestData) !== "object") {
|
|
||||||
throw new Error("Data must be an object");
|
|
||||||
}
|
|
||||||
|
|
||||||
let data = requestData as LooseObject;
|
|
||||||
let manager = socket.instanceManager;
|
|
||||||
await manager.test(data.url, data.username, data.password);
|
|
||||||
await manager.add(data.url, data.username, data.password);
|
|
||||||
|
|
||||||
// connect to the agent
|
|
||||||
manager.connect(data.url, data.username, data.password);
|
|
||||||
|
|
||||||
// Refresh another sockets
|
|
||||||
// It is a bit difficult to control another browser sessions to connect/disconnect agents, so force them to refresh the page will be easier.
|
|
||||||
server.disconnectAllSocketClients(undefined, socket.id);
|
|
||||||
manager.sendAgentList();
|
|
||||||
|
|
||||||
callbackResult({
|
|
||||||
ok: true,
|
|
||||||
msg: "agentAddedSuccessfully",
|
|
||||||
msgi18n: true,
|
|
||||||
}, callback);
|
|
||||||
|
|
||||||
} catch (e) {
|
|
||||||
callbackError(e, callback);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// removeAgent
|
|
||||||
socket.on("removeAgent", async (url : unknown, callback : unknown) => {
|
|
||||||
try {
|
|
||||||
log.debug("manage-agent-socket-handler", "removeAgent");
|
|
||||||
checkLogin(socket);
|
|
||||||
|
|
||||||
if (typeof(url) !== "string") {
|
|
||||||
throw new Error("URL must be a string");
|
|
||||||
}
|
|
||||||
|
|
||||||
let manager = socket.instanceManager;
|
|
||||||
await manager.remove(url);
|
|
||||||
|
|
||||||
server.disconnectAllSocketClients(undefined, socket.id);
|
|
||||||
manager.sendAgentList();
|
|
||||||
|
|
||||||
callbackResult({
|
|
||||||
ok: true,
|
|
||||||
msg: "agentRemovedSuccessfully",
|
|
||||||
msgi18n: true,
|
|
||||||
}, callback);
|
|
||||||
} catch (e) {
|
|
||||||
callbackError(e, callback);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,15 +1,24 @@
|
|||||||
|
import { SocketHandler } from "../socket-handler.js";
|
||||||
import { DockgeServer } from "../dockge-server";
|
import { DockgeServer } from "../dockge-server";
|
||||||
import { callbackError, callbackResult, checkLogin, DockgeSocket, ValidationError } from "../util-server";
|
import { callbackError, checkLogin, DockgeSocket, ValidationError } from "../util-server";
|
||||||
import { log } from "../log";
|
import { log } from "../log";
|
||||||
|
import yaml from "yaml";
|
||||||
|
import path from "path";
|
||||||
|
import fs from "fs";
|
||||||
|
import {
|
||||||
|
allowedCommandList,
|
||||||
|
allowedRawKeys,
|
||||||
|
getComposeTerminalName, getContainerExecTerminalName,
|
||||||
|
isDev,
|
||||||
|
PROGRESS_TERMINAL_ROWS
|
||||||
|
} from "../util-common";
|
||||||
import { InteractiveTerminal, MainTerminal, Terminal } from "../terminal";
|
import { InteractiveTerminal, MainTerminal, Terminal } from "../terminal";
|
||||||
import { Stack } from "../stack";
|
import { Stack } from "../stack";
|
||||||
import { AgentSocketHandler } from "../agent-socket-handler";
|
|
||||||
import { AgentSocket } from "../../common/agent-socket";
|
|
||||||
|
|
||||||
export class TerminalSocketHandler extends AgentSocketHandler {
|
export class TerminalSocketHandler extends SocketHandler {
|
||||||
create(socket : DockgeSocket, server : DockgeServer, agentSocket : AgentSocket) {
|
create(socket : DockgeSocket, server : DockgeServer) {
|
||||||
|
|
||||||
agentSocket.on("terminalInput", async (terminalName : unknown, cmd : unknown, callback) => {
|
socket.on("terminalInput", async (terminalName : unknown, cmd : unknown, errorCallback) => {
|
||||||
try {
|
try {
|
||||||
checkLogin(socket);
|
checkLogin(socket);
|
||||||
|
|
||||||
@ -29,12 +38,17 @@ export class TerminalSocketHandler extends AgentSocketHandler {
|
|||||||
throw new Error("Terminal not found or it is not a Interactive Terminal.");
|
throw new Error("Terminal not found or it is not a Interactive Terminal.");
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
callbackError(e, callback);
|
if (e instanceof Error) {
|
||||||
|
errorCallback({
|
||||||
|
ok: false,
|
||||||
|
msg: e.message,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Main Terminal
|
// Main Terminal
|
||||||
agentSocket.on("mainTerminal", async (terminalName : unknown, callback) => {
|
socket.on("mainTerminal", async (terminalName : unknown, callback) => {
|
||||||
try {
|
try {
|
||||||
checkLogin(socket);
|
checkLogin(socket);
|
||||||
|
|
||||||
@ -45,29 +59,29 @@ export class TerminalSocketHandler extends AgentSocketHandler {
|
|||||||
throw new ValidationError("Terminal name must be a string.");
|
throw new ValidationError("Terminal name must be a string.");
|
||||||
}
|
}
|
||||||
|
|
||||||
log.debug("mainTerminal", "Terminal name: " + terminalName);
|
log.debug("deployStack", "Terminal name: " + terminalName);
|
||||||
|
|
||||||
let terminal = Terminal.getTerminal(terminalName);
|
let terminal = Terminal.getTerminal(terminalName);
|
||||||
|
|
||||||
if (!terminal) {
|
if (!terminal) {
|
||||||
terminal = new MainTerminal(server, terminalName);
|
terminal = new MainTerminal(server, terminalName);
|
||||||
terminal.rows = 50;
|
terminal.rows = 50;
|
||||||
log.debug("mainTerminal", "Terminal created");
|
log.debug("deployStack", "Terminal created");
|
||||||
}
|
}
|
||||||
|
|
||||||
terminal.join(socket);
|
terminal.join(socket);
|
||||||
terminal.start();
|
terminal.start();
|
||||||
|
|
||||||
callbackResult({
|
callback({
|
||||||
ok: true,
|
ok: true,
|
||||||
}, callback);
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
callbackError(e, callback);
|
callbackError(e, callback);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Interactive Terminal for containers
|
// Interactive Terminal for containers
|
||||||
agentSocket.on("interactiveTerminal", async (stackName : unknown, serviceName : unknown, shell : unknown, callback) => {
|
socket.on("interactiveTerminal", async (stackName : unknown, serviceName : unknown, shell : unknown, callback) => {
|
||||||
try {
|
try {
|
||||||
checkLogin(socket);
|
checkLogin(socket);
|
||||||
|
|
||||||
@ -90,16 +104,16 @@ export class TerminalSocketHandler extends AgentSocketHandler {
|
|||||||
const stack = await Stack.getStack(server, stackName);
|
const stack = await Stack.getStack(server, stackName);
|
||||||
stack.joinContainerTerminal(socket, serviceName, shell);
|
stack.joinContainerTerminal(socket, serviceName, shell);
|
||||||
|
|
||||||
callbackResult({
|
callback({
|
||||||
ok: true,
|
ok: true,
|
||||||
}, callback);
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
callbackError(e, callback);
|
callbackError(e, callback);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Join Output Terminal
|
// Join Output Terminal
|
||||||
agentSocket.on("terminalJoin", async (terminalName : unknown, callback) => {
|
socket.on("terminalJoin", async (terminalName : unknown, callback) => {
|
||||||
if (typeof(callback) !== "function") {
|
if (typeof(callback) !== "function") {
|
||||||
log.debug("console", "Callback is not a function.");
|
log.debug("console", "Callback is not a function.");
|
||||||
return;
|
return;
|
||||||
@ -127,7 +141,7 @@ export class TerminalSocketHandler extends AgentSocketHandler {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Leave Combined Terminal
|
// Leave Combined Terminal
|
||||||
agentSocket.on("leaveCombinedTerminal", async (stackName : unknown, callback) => {
|
socket.on("leaveCombinedTerminal", async (stackName : unknown, callback) => {
|
||||||
try {
|
try {
|
||||||
checkLogin(socket);
|
checkLogin(socket);
|
||||||
|
|
||||||
@ -140,48 +154,17 @@ export class TerminalSocketHandler extends AgentSocketHandler {
|
|||||||
const stack = await Stack.getStack(server, stackName);
|
const stack = await Stack.getStack(server, stackName);
|
||||||
await stack.leaveCombinedTerminal(socket);
|
await stack.leaveCombinedTerminal(socket);
|
||||||
|
|
||||||
callbackResult({
|
callback({
|
||||||
ok: true,
|
ok: true,
|
||||||
}, callback);
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
callbackError(e, callback);
|
callbackError(e, callback);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Resize Terminal
|
// TODO: Resize Terminal
|
||||||
agentSocket.on("terminalResize", async (terminalName: unknown, rows: unknown, cols: unknown) => {
|
socket.on("terminalResize", async (rows : unknown) => {
|
||||||
log.info("terminalResize", `Terminal: ${terminalName}`);
|
|
||||||
try {
|
|
||||||
checkLogin(socket);
|
|
||||||
if (typeof terminalName !== "string") {
|
|
||||||
throw new Error("Terminal name must be a string.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof rows !== "number") {
|
|
||||||
throw new Error("Command must be a number.");
|
|
||||||
}
|
|
||||||
if (typeof cols !== "number") {
|
|
||||||
throw new Error("Command must be a number.");
|
|
||||||
}
|
|
||||||
|
|
||||||
let terminal = Terminal.getTerminal(terminalName);
|
|
||||||
|
|
||||||
// log.info("terminal", terminal);
|
|
||||||
if (terminal instanceof Terminal) {
|
|
||||||
//log.debug("terminalInput", "Terminal found, writing to terminal.");
|
|
||||||
terminal.rows = rows;
|
|
||||||
terminal.cols = cols;
|
|
||||||
} else {
|
|
||||||
throw new Error(`${terminalName} Terminal not found.`);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
log.debug("terminalResize",
|
|
||||||
// Added to prevent the lint error when adding the type
|
|
||||||
// and ts type checker saying type is unknown.
|
|
||||||
// @ts-ignore
|
|
||||||
`Error on ${terminalName}: ${e.message}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -5,7 +5,6 @@ import yaml from "yaml";
|
|||||||
import { DockgeSocket, fileExists, ValidationError } from "./util-server";
|
import { DockgeSocket, fileExists, ValidationError } from "./util-server";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import {
|
import {
|
||||||
acceptedComposeFileNames,
|
|
||||||
COMBINED_TERMINAL_COLS,
|
COMBINED_TERMINAL_COLS,
|
||||||
COMBINED_TERMINAL_ROWS,
|
COMBINED_TERMINAL_ROWS,
|
||||||
CREATED_FILE,
|
CREATED_FILE,
|
||||||
@ -15,10 +14,9 @@ import {
|
|||||||
PROGRESS_TERMINAL_ROWS,
|
PROGRESS_TERMINAL_ROWS,
|
||||||
RUNNING, TERMINAL_ROWS,
|
RUNNING, TERMINAL_ROWS,
|
||||||
UNKNOWN
|
UNKNOWN
|
||||||
} from "../common/util-common";
|
} from "./util-common";
|
||||||
import { InteractiveTerminal, Terminal } from "./terminal";
|
import { InteractiveTerminal, Terminal } from "./terminal";
|
||||||
import childProcessAsync from "promisify-child-process";
|
import childProcessAsync from "promisify-child-process";
|
||||||
import { Settings } from "./settings";
|
|
||||||
|
|
||||||
export class Stack {
|
export class Stack {
|
||||||
|
|
||||||
@ -42,7 +40,8 @@ export class Stack {
|
|||||||
|
|
||||||
if (!skipFSOperations) {
|
if (!skipFSOperations) {
|
||||||
// Check if compose file name is different from compose.yaml
|
// Check if compose file name is different from compose.yaml
|
||||||
for (const filename of acceptedComposeFileNames) {
|
const supportedFileNames = [ "compose.yaml", "compose.yml", "docker-compose.yml", "docker-compose.yaml" ];
|
||||||
|
for (const filename of supportedFileNames) {
|
||||||
if (fs.existsSync(path.join(this.path, filename))) {
|
if (fs.existsSync(path.join(this.path, filename))) {
|
||||||
this._composeFileName = filename;
|
this._composeFileName = filename;
|
||||||
break;
|
break;
|
||||||
@ -51,41 +50,22 @@ export class Stack {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async toJSON(endpoint : string) : Promise<object> {
|
toJSON() : object {
|
||||||
|
let obj = this.toSimpleJSON();
|
||||||
// Since we have multiple agents now, embed primary hostname in the stack object too.
|
|
||||||
let primaryHostname = await Settings.get("primaryHostname");
|
|
||||||
if (!primaryHostname) {
|
|
||||||
if (!endpoint) {
|
|
||||||
primaryHostname = "localhost";
|
|
||||||
} else {
|
|
||||||
// Use the endpoint as the primary hostname
|
|
||||||
try {
|
|
||||||
primaryHostname = (new URL("https://" + endpoint).hostname);
|
|
||||||
} catch (e) {
|
|
||||||
// Just in case if the endpoint is in a incorrect format
|
|
||||||
primaryHostname = "localhost";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let obj = this.toSimpleJSON(endpoint);
|
|
||||||
return {
|
return {
|
||||||
...obj,
|
...obj,
|
||||||
composeYAML: this.composeYAML,
|
composeYAML: this.composeYAML,
|
||||||
composeENV: this.composeENV,
|
composeENV: this.composeENV,
|
||||||
primaryHostname,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
toSimpleJSON(endpoint : string) : object {
|
toSimpleJSON() : 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,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -206,8 +186,8 @@ export class Stack {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async deploy(socket : DockgeSocket) : Promise<number> {
|
async deploy(socket? : DockgeSocket) : Promise<number> {
|
||||||
const terminalName = getComposeTerminalName(socket.endpoint, this.name);
|
const terminalName = getComposeTerminalName(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.");
|
||||||
@ -215,8 +195,8 @@ export class Stack {
|
|||||||
return exitCode;
|
return exitCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
async delete(socket: DockgeSocket) : Promise<number> {
|
async delete(socket?: DockgeSocket) : Promise<number> {
|
||||||
const terminalName = getComposeTerminalName(socket.endpoint, this.name);
|
const terminalName = getComposeTerminalName(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.");
|
||||||
@ -242,26 +222,6 @@ export class Stack {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if a compose file exists in the specified directory.
|
|
||||||
* @async
|
|
||||||
* @static
|
|
||||||
* @param {string} stacksDir - The directory of the stack.
|
|
||||||
* @param {string} filename - The name of the directory to check for the compose file.
|
|
||||||
* @returns {Promise<boolean>} A promise that resolves to a boolean indicating whether any compose file exists.
|
|
||||||
*/
|
|
||||||
static async composeFileExists(stacksDir : string, filename : string) : Promise<boolean> {
|
|
||||||
let filenamePath = path.join(stacksDir, filename);
|
|
||||||
// Check if any compose file exists
|
|
||||||
for (const filename of acceptedComposeFileNames) {
|
|
||||||
let composeFile = path.join(filenamePath, filename);
|
|
||||||
if (await fileExists(composeFile)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
static async getStackList(server : DockgeServer, useCacheForManaged = false) : Promise<Map<string, Stack>> {
|
static async getStackList(server : DockgeServer, useCacheForManaged = false) : Promise<Map<string, Stack>> {
|
||||||
let stacksDir = server.stacksDir;
|
let stacksDir = server.stacksDir;
|
||||||
let stackList : Map<string, Stack>;
|
let stackList : Map<string, Stack>;
|
||||||
@ -282,10 +242,6 @@ export class Stack {
|
|||||||
if (!stat.isDirectory()) {
|
if (!stat.isDirectory()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// If no compose file exists, skip it
|
|
||||||
if (!await Stack.composeFileExists(stacksDir, filename)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
let stack = await this.getStack(server, filename);
|
let stack = await this.getStack(server, filename);
|
||||||
stack._status = CREATED_FILE;
|
stack._status = CREATED_FILE;
|
||||||
stackList.set(filename, stack);
|
stackList.set(filename, stack);
|
||||||
@ -408,7 +364,7 @@ export class Stack {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async start(socket: DockgeSocket) {
|
async start(socket: DockgeSocket) {
|
||||||
const terminalName = getComposeTerminalName(socket.endpoint, this.name);
|
const terminalName = getComposeTerminalName(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.");
|
||||||
@ -417,7 +373,7 @@ export class Stack {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async stop(socket: DockgeSocket) : Promise<number> {
|
async stop(socket: DockgeSocket) : Promise<number> {
|
||||||
const terminalName = getComposeTerminalName(socket.endpoint, this.name);
|
const terminalName = getComposeTerminalName(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.");
|
||||||
@ -426,7 +382,7 @@ export class Stack {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async restart(socket: DockgeSocket) : Promise<number> {
|
async restart(socket: DockgeSocket) : Promise<number> {
|
||||||
const terminalName = getComposeTerminalName(socket.endpoint, this.name);
|
const terminalName = getComposeTerminalName(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.");
|
||||||
@ -435,7 +391,7 @@ export class Stack {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async down(socket: DockgeSocket) : Promise<number> {
|
async down(socket: DockgeSocket) : Promise<number> {
|
||||||
const terminalName = getComposeTerminalName(socket.endpoint, this.name);
|
const terminalName = getComposeTerminalName(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.");
|
||||||
@ -444,7 +400,7 @@ export class Stack {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async update(socket: DockgeSocket) {
|
async update(socket: DockgeSocket) {
|
||||||
const terminalName = getComposeTerminalName(socket.endpoint, this.name);
|
const terminalName = getComposeTerminalName(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.");
|
||||||
@ -465,7 +421,7 @@ export class Stack {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async joinCombinedTerminal(socket: DockgeSocket) {
|
async joinCombinedTerminal(socket: DockgeSocket) {
|
||||||
const terminalName = getCombinedTerminalName(socket.endpoint, 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.enableKeepAlive = true;
|
||||||
terminal.rows = COMBINED_TERMINAL_ROWS;
|
terminal.rows = COMBINED_TERMINAL_ROWS;
|
||||||
@ -475,7 +431,7 @@ export class Stack {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async leaveCombinedTerminal(socket: DockgeSocket) {
|
async leaveCombinedTerminal(socket: DockgeSocket) {
|
||||||
const terminalName = getCombinedTerminalName(socket.endpoint, this.name);
|
const terminalName = getCombinedTerminalName(this.name);
|
||||||
const terminal = Terminal.getTerminal(terminalName);
|
const terminal = Terminal.getTerminal(terminalName);
|
||||||
if (terminal) {
|
if (terminal) {
|
||||||
terminal.leave(socket);
|
terminal.leave(socket);
|
||||||
@ -483,7 +439,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(socket.endpoint, this.name, serviceName, index);
|
const terminalName = getContainerExecTerminalName(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 "../common/util-common";
|
} from "./util-common";
|
||||||
import { sync as commandExistsSync } from "command-exists";
|
import { sync as commandExistsSync } from "command-exists";
|
||||||
import { log } from "./log";
|
import { log } from "./log";
|
||||||
|
|
||||||
@ -34,9 +34,6 @@ export class Terminal {
|
|||||||
|
|
||||||
public enableKeepAlive : boolean = false;
|
public enableKeepAlive : boolean = false;
|
||||||
protected keepAliveInterval? : NodeJS.Timeout;
|
protected keepAliveInterval? : NodeJS.Timeout;
|
||||||
protected kickDisconnectedClientsInterval? : NodeJS.Timeout;
|
|
||||||
|
|
||||||
protected socketList : Record<string, DockgeSocket> = {};
|
|
||||||
|
|
||||||
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;
|
||||||
@ -70,7 +67,6 @@ export class Terminal {
|
|||||||
|
|
||||||
set cols(cols : number) {
|
set cols(cols : number) {
|
||||||
this._cols = cols;
|
this._cols = cols;
|
||||||
log.debug("Terminal", `Terminal cols: ${this._cols}`); // Added to check if cols is being set when changing terminal size.
|
|
||||||
try {
|
try {
|
||||||
this.ptyProcess?.resize(this.cols, this.rows);
|
this.ptyProcess?.resize(this.cols, this.rows);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -85,22 +81,13 @@ export class Terminal {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.kickDisconnectedClientsInterval = setInterval(() => {
|
|
||||||
for (const socketID in this.socketList) {
|
|
||||||
const socket = this.socketList[socketID];
|
|
||||||
if (!socket.connected) {
|
|
||||||
log.debug("Terminal", "Kicking disconnected client " + socket.id + " from terminal " + this.name);
|
|
||||||
this.leave(socket);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, 60 * 1000);
|
|
||||||
|
|
||||||
if (this.enableKeepAlive) {
|
if (this.enableKeepAlive) {
|
||||||
log.debug("Terminal", "Keep alive enabled for terminal " + this.name);
|
log.debug("Terminal", "Keep alive enabled for terminal " + this.name);
|
||||||
|
|
||||||
// Close if there is no clients
|
// Close if there is no clients
|
||||||
this.keepAliveInterval = setInterval(() => {
|
this.keepAliveInterval = setInterval(() => {
|
||||||
const numClients = Object.keys(this.socketList).length;
|
const clients = this.server.io.sockets.adapter.rooms.get(this.name);
|
||||||
|
const numClients = clients ? clients.size : 0;
|
||||||
|
|
||||||
if (numClients === 0) {
|
if (numClients === 0) {
|
||||||
log.debug("Terminal", "Terminal " + this.name + " has no client, closing...");
|
log.debug("Terminal", "Terminal " + this.name + " has no client, closing...");
|
||||||
@ -124,10 +111,8 @@ export class Terminal {
|
|||||||
// On Data
|
// On Data
|
||||||
this._ptyProcess.onData((data) => {
|
this._ptyProcess.onData((data) => {
|
||||||
this.buffer.pushItem(data);
|
this.buffer.pushItem(data);
|
||||||
|
if (this.server.io) {
|
||||||
for (const socketID in this.socketList) {
|
this.server.io.to(this.name).emit("terminalWrite", this.name, data);
|
||||||
const socket = this.socketList[socketID];
|
|
||||||
socket.emitAgent("terminalWrite", this.name, data);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -151,19 +136,15 @@ export class Terminal {
|
|||||||
* @param res
|
* @param res
|
||||||
*/
|
*/
|
||||||
protected exit = (res : {exitCode: number, signal?: number | undefined}) => {
|
protected exit = (res : {exitCode: number, signal?: number | undefined}) => {
|
||||||
for (const socketID in this.socketList) {
|
this.server.io.to(this.name).emit("terminalExit", this.name, res.exitCode);
|
||||||
const socket = this.socketList[socketID];
|
|
||||||
socket.emitAgent("terminalExit", this.name, res.exitCode);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove all clients
|
// Remove room
|
||||||
this.socketList = {};
|
this.server.io.in(this.name).socketsLeave(this.name);
|
||||||
|
|
||||||
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);
|
clearInterval(this.keepAliveInterval);
|
||||||
clearInterval(this.kickDisconnectedClientsInterval);
|
|
||||||
|
|
||||||
if (this.callback) {
|
if (this.callback) {
|
||||||
this.callback(res.exitCode);
|
this.callback(res.exitCode);
|
||||||
@ -175,11 +156,11 @@ export class Terminal {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public join(socket : DockgeSocket) {
|
public join(socket : DockgeSocket) {
|
||||||
this.socketList[socket.id] = socket;
|
socket.join(this.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
public leave(socket : DockgeSocket) {
|
public leave(socket : DockgeSocket) {
|
||||||
delete this.socketList[socket.id];
|
socket.leave(this.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
public get ptyProcess() {
|
public get ptyProcess() {
|
||||||
|
@ -43,8 +43,6 @@ async function initRandomBytes() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ALL_ENDPOINTS = "##ALL_DOCKGE_ENDPOINTS##";
|
|
||||||
|
|
||||||
// Stack Status
|
// Stack Status
|
||||||
export const UNKNOWN = 0;
|
export const UNKNOWN = 0;
|
||||||
export const CREATED_FILE = 1;
|
export const CREATED_FILE = 1;
|
||||||
@ -118,13 +116,6 @@ export const allowedRawKeys = [
|
|||||||
"\u0003", // Ctrl + C
|
"\u0003", // Ctrl + C
|
||||||
];
|
];
|
||||||
|
|
||||||
export const acceptedComposeFileNames = [
|
|
||||||
"compose.yaml",
|
|
||||||
"docker-compose.yaml",
|
|
||||||
"docker-compose.yml",
|
|
||||||
"compose.yml",
|
|
||||||
];
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate a decimal integer number from a string
|
* Generate a decimal integer number from a string
|
||||||
* @param str Input
|
* @param str Input
|
||||||
@ -208,20 +199,20 @@ export function getCryptoRandomInt(min: number, max: number):number {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getComposeTerminalName(endpoint : string, stack : string) {
|
export function getComposeTerminalName(stack : string) {
|
||||||
return "compose-" + endpoint + "-" + stack;
|
return "compose-" + stack;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getCombinedTerminalName(endpoint : string, stack : string) {
|
export function getCombinedTerminalName(stack : string) {
|
||||||
return "combined-" + endpoint + "-" + stack;
|
return "combined-" + stack;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getContainerTerminalName(endpoint : string, container : string) {
|
export function getContainerTerminalName(container : string) {
|
||||||
return "container-" + endpoint + "-" + container;
|
return "container-" + container;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getContainerExecTerminalName(endpoint : string, stackName : string, container : string, index : number) {
|
export function getContainerExecTerminalName(stackName : string, container : string, index : number) {
|
||||||
return "container-exec-" + endpoint + "-" + stackName + "-" + container + "-" + index;
|
return "container-exec-" + stackName + "-" + container + "-" + index;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function copyYAMLComments(doc : Document, src : Document) {
|
export function copyYAMLComments(doc : Document, src : Document) {
|
||||||
@ -291,9 +282,10 @@ function copyYAMLCommentsItems(items : any, srcItems : any) {
|
|||||||
* - "127.0.0.1:5000-5010:5000-5010"
|
* - "127.0.0.1:5000-5010:5000-5010"
|
||||||
* - "6060:6060/udp"
|
* - "6060:6060/udp"
|
||||||
* @param input
|
* @param input
|
||||||
* @param hostname
|
* @param defaultHostname
|
||||||
*/
|
*/
|
||||||
export function parseDockerPort(input : string, hostname : string) {
|
export function parseDockerPort(input : string, defaultHostname : string = "localhost") {
|
||||||
|
let hostname = defaultHostname;
|
||||||
let port;
|
let port;
|
||||||
let display;
|
let display;
|
||||||
|
|
||||||
@ -393,11 +385,7 @@ function traverseYAML(pair : Pair, env : DotenvParseOutput) : void {
|
|||||||
if (item instanceof Pair) {
|
if (item instanceof Pair) {
|
||||||
traverseYAML(item, env);
|
traverseYAML(item, env);
|
||||||
} else if (item instanceof Scalar) {
|
} else if (item instanceof Scalar) {
|
||||||
let value = item.value as unknown;
|
item.value = envsubst(item.value, env);
|
||||||
|
|
||||||
if (typeof(value) === "string") {
|
|
||||||
item.value = envsubst(value, env);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
@ -406,4 +394,3 @@ function traverseYAML(pair : Pair, env : DotenvParseOutput) : void {
|
|||||||
pair.value.value = envsubst(pair.value.value, env);
|
pair.value.value = envsubst(pair.value.value, env);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -2,11 +2,10 @@ 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 "../common/util-common";
|
import { ERROR_TYPE_VALIDATION } from "./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 { AgentManager } from "./agent-manager";
|
|
||||||
|
|
||||||
export interface JWTDecoded {
|
export interface JWTDecoded {
|
||||||
username : string;
|
username : string;
|
||||||
@ -16,9 +15,6 @@ export interface JWTDecoded {
|
|||||||
export interface DockgeSocket extends Socket {
|
export interface DockgeSocket extends Socket {
|
||||||
userID: number;
|
userID: number;
|
||||||
consoleTerminal? : Terminal;
|
consoleTerminal? : Terminal;
|
||||||
instanceManager : AgentManager;
|
|
||||||
endpoint : string;
|
|
||||||
emitAgent : (eventName : string, ...args : unknown[]) => void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// For command line arguments, so they are nullable
|
// For command line arguments, so they are nullable
|
||||||
@ -60,28 +56,18 @@ export function callbackError(error : unknown, callback : unknown) {
|
|||||||
callback({
|
callback({
|
||||||
ok: false,
|
ok: false,
|
||||||
msg: error.message,
|
msg: error.message,
|
||||||
msgi18n: true,
|
|
||||||
});
|
});
|
||||||
} else if (error instanceof ValidationError) {
|
} else if (error instanceof ValidationError) {
|
||||||
callback({
|
callback({
|
||||||
ok: false,
|
ok: false,
|
||||||
type: ERROR_TYPE_VALIDATION,
|
type: ERROR_TYPE_VALIDATION,
|
||||||
msg: error.message,
|
msg: error.message,
|
||||||
msgi18n: true,
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
log.debug("console", "Unknown error: " + error);
|
log.debug("console", "Unknown error: " + error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function callbackResult(result : unknown, callback : unknown) {
|
|
||||||
if (typeof(callback) !== "function") {
|
|
||||||
log.error("console", "Callback is not a function");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
callback(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function doubleCheckPassword(socket : DockgeSocket, currentPassword : unknown) {
|
export async function doubleCheckPassword(socket : DockgeSocket, currentPassword : unknown) {
|
||||||
if (typeof currentPassword !== "string") {
|
if (typeof currentPassword !== "string") {
|
||||||
throw new Error("Wrong data type?");
|
throw new Error("Wrong data type?");
|
||||||
|
@ -1,15 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,37 +1,19 @@
|
|||||||
// Generate on GitHub
|
// Generate on GitHub
|
||||||
const input = `
|
const input = `
|
||||||
* Fixed envsubst issue by @louislam in https://github.com/louislam/dockge/pull/301
|
* Add Korean translation by @Alanimdeo in https://github.com/louislam/dockge/pull/86
|
||||||
* Fix: Only adding folders to stack with a compose file. by @Ozy-Viking in https://github.com/louislam/dockge/pull/299
|
|
||||||
* Terminal text cols adjusts to terminal container. by @Ozy-Viking in https://github.com/louislam/dockge/pull/285
|
|
||||||
* Update Docker Dompose plugin to 2.23.3 by @louislam in https://github.com/louislam/dockge/pull/303
|
|
||||||
* Translations update from Kuma Weblate by @UptimeKumaBot in https://github.com/louislam/dockge/pull/302
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const template = `
|
const template = `
|
||||||
|
|
||||||
> [!WARNING]
|
|
||||||
>
|
|
||||||
|
|
||||||
### 🆕 New Features
|
### 🆕 New Features
|
||||||
-
|
|
||||||
|
|
||||||
### ⬆️ Improvements
|
### Improvements
|
||||||
-
|
|
||||||
|
|
||||||
### 🐛 Bug Fixes
|
### 🐞 Bug Fixes
|
||||||
-
|
|
||||||
|
|
||||||
### 🦎 Translation Contributions
|
### 🦎 Translation Contributions
|
||||||
-
|
|
||||||
|
|
||||||
### ⬆️ Security Fixes
|
|
||||||
-
|
|
||||||
|
|
||||||
### Others
|
### Others
|
||||||
- Other small changes, code refactoring and comment/doc updates in this repo:
|
- Other small changes, code refactoring and comment/doc updates in this repo:
|
||||||
-
|
|
||||||
|
|
||||||
Please let me know if your username is missing, if your pull request has been merged in this version, or your commit has been included in one of the pull requests.
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const lines = input.split("\n").filter((line) => line.trim() !== "");
|
const lines = input.split("\n").filter((line) => line.trim() !== "");
|
||||||
@ -55,12 +37,6 @@ for (const line of lines) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
message = message.split("* ").pop();
|
message = message.split("* ").pop();
|
||||||
|
console.log("-", pullRequestID, message, `(Thanks ${username})`);
|
||||||
let thanks = "";
|
|
||||||
if (username != "@louislam") {
|
|
||||||
thanks = `(Thanks ${username})`;
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(pullRequestID, message, thanks);
|
|
||||||
}
|
}
|
||||||
console.log(template);
|
console.log(template);
|
||||||
|
@ -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 "../common/util-common";
|
import { BaseRes } from "../backend/util-common";
|
||||||
|
|
||||||
console.log("== Dockge Reset Password Tool ==");
|
console.log("== Dockge Reset Password Tool ==");
|
||||||
|
|
||||||
@ -92,6 +92,7 @@ function disconnectAllSocketClients(username : string, password : string) : Prom
|
|||||||
|
|
||||||
// Disconnect all socket connections
|
// Disconnect all socket connections
|
||||||
const socket = io(url, {
|
const socket = io(url, {
|
||||||
|
transports: [ "websocket" ],
|
||||||
reconnection: false,
|
reconnection: false,
|
||||||
timeout: 5000,
|
timeout: 5000,
|
||||||
});
|
});
|
||||||
|
@ -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 "../../../common/util-common";
|
import { parseDockerPort } from "../../../backend/util-common";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: {
|
components: {
|
||||||
@ -189,34 +189,14 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
|
|
||||||
terminalRouteLink() {
|
terminalRouteLink() {
|
||||||
if (this.endpoint) {
|
return {
|
||||||
return {
|
name: "containerTerminal",
|
||||||
name: "containerTerminalEndpoint",
|
params: {
|
||||||
params: {
|
stackName: this.stackName,
|
||||||
endpoint: this.endpoint,
|
serviceName: this.name,
|
||||||
stackName: this.stackName,
|
type: "bash",
|
||||||
serviceName: this.name,
|
},
|
||||||
type: "bash",
|
};
|
||||||
},
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
return {
|
|
||||||
name: "containerTerminal",
|
|
||||||
params: {
|
|
||||||
stackName: this.stackName,
|
|
||||||
serviceName: this.name,
|
|
||||||
type: "bash",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
endpoint() {
|
|
||||||
return this.$parent.$parent.endpoint;
|
|
||||||
},
|
|
||||||
|
|
||||||
stack() {
|
|
||||||
return this.$parent.$parent.stack;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
stackName() {
|
stackName() {
|
||||||
@ -274,7 +254,8 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
parsePort(port) {
|
parsePort(port) {
|
||||||
return parseDockerPort(port, this.stack.primaryHostname);
|
let hostname = this.$root.info.primaryHostname || location.hostname;
|
||||||
|
return parseDockerPort(port, hostname);
|
||||||
},
|
},
|
||||||
remove() {
|
remove() {
|
||||||
delete this.jsonObject.services[this.name];
|
delete this.jsonObject.services[this.name];
|
||||||
|
@ -65,10 +65,6 @@ export default {
|
|||||||
editorFocus() {
|
editorFocus() {
|
||||||
return this.$parent.$parent.editorFocus;
|
return this.$parent.$parent.editorFocus;
|
||||||
},
|
},
|
||||||
|
|
||||||
endpoint() {
|
|
||||||
return this.$parent.$parent.endpoint;
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
"jsonConfig.networks": {
|
"jsonConfig.networks": {
|
||||||
@ -138,7 +134,7 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
loadExternalNetworkList() {
|
loadExternalNetworkList() {
|
||||||
this.$root.emitAgent(this.endpoint, "getDockerNetworkList", (res) => {
|
this.$root.getSocket().emit("getDockerNetworkList", (res) => {
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
this.externalNetworkList = res.dockerNetworkList.filter((n) => {
|
this.externalNetworkList = res.dockerNetworkList.filter((n) => {
|
||||||
// Filter out this stack networks
|
// Filter out this stack networks
|
||||||
|
@ -43,7 +43,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div ref="stackList" class="stack-list" :class="{ scrollbar: scrollbar }" :style="stackListStyle">
|
<div ref="stackList" class="stack-list" :class="{ scrollbar: scrollbar }" :style="stackListStyle">
|
||||||
<div v-if="Object.keys(sortedStackList).length === 0" class="text-center mt-3">
|
<div v-if="Object.keys($root.stackList).length === 0" class="text-center mt-3">
|
||||||
<router-link to="/compose">{{ $t("addFirstStackMsg") }}</router-link>
|
<router-link to="/compose">{{ $t("addFirstStackMsg") }}</router-link>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -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 "../../../common/util-common";
|
import { CREATED_FILE, CREATED_STACK, EXITED, RUNNING, UNKNOWN } from "../../../backend/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.completeStackList);
|
let result = Object.values(this.$root.stackList);
|
||||||
|
|
||||||
result = result.filter(stack => {
|
result = result.filter(stack => {
|
||||||
// filter by search text
|
// filter by search text
|
||||||
@ -160,7 +160,6 @@ 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,14 +1,12 @@
|
|||||||
<template>
|
<template>
|
||||||
<router-link :to="url" :class="{ 'dim' : !stack.isManagedByDockge }" class="item">
|
<router-link :to="`/compose/${stack.name}`" :class="{ 'dim' : !stack.isManagedByDockge }" class="item">
|
||||||
<Uptime :stack="stack" :fixed-width="true" class="me-2" />
|
<Uptime :stack="stack" :fixed-width="true" class="me-2" />
|
||||||
<div class="title">
|
<span class="title">{{ stackName }}</span>
|
||||||
<span>{{ stackName }}</span>
|
|
||||||
<div v-if="$root.agentCount > 1" class="endpoint">{{ endpointDisplay }}</div>
|
|
||||||
</div>
|
|
||||||
</router-link>
|
</router-link>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
import Uptime from "./Uptime.vue";
|
import Uptime from "./Uptime.vue";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@ -53,16 +51,6 @@ export default {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
endpointDisplay() {
|
|
||||||
return this.$root.endpointDisplayFunction(this.stack.endpoint);
|
|
||||||
},
|
|
||||||
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`,
|
||||||
@ -129,31 +117,16 @@ export default {
|
|||||||
padding-right: 2px !important;
|
padding-right: 2px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.item {
|
// .stack-item {
|
||||||
text-decoration: none;
|
// width: 100%;
|
||||||
|
// }
|
||||||
|
|
||||||
|
.tags {
|
||||||
|
margin-top: 4px;
|
||||||
|
padding-left: 67px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
flex-wrap: wrap;
|
||||||
min-height: 52px;
|
gap: 0;
|
||||||
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 {
|
||||||
|
@ -5,9 +5,9 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { Terminal } from "@xterm/xterm";
|
import { Terminal } from "xterm";
|
||||||
import { FitAddon } from "@xterm/addon-fit";
|
import { WebLinksAddon } from "xterm-addon-web-links";
|
||||||
import { TERMINAL_COLS, TERMINAL_ROWS } from "../../../common/util-common";
|
import { TERMINAL_COLS, TERMINAL_ROWS } from "../../../backend/util-common";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
/**
|
/**
|
||||||
@ -23,11 +23,6 @@ export default {
|
|||||||
require: true,
|
require: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
endpoint: {
|
|
||||||
type: String,
|
|
||||||
require: true,
|
|
||||||
},
|
|
||||||
|
|
||||||
// Require if mode is interactive
|
// Require if mode is interactive
|
||||||
stackName: {
|
stackName: {
|
||||||
type: String,
|
type: String,
|
||||||
@ -114,39 +109,37 @@ export default {
|
|||||||
|
|
||||||
// Create a new Terminal
|
// Create a new Terminal
|
||||||
if (this.mode === "mainTerminal") {
|
if (this.mode === "mainTerminal") {
|
||||||
this.$root.emitAgent(this.endpoint, "mainTerminal", this.name, (res) => {
|
this.$root.getSocket().emit("mainTerminal", this.name, (res) => {
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
this.$root.toastRes(res);
|
this.$root.toastRes(res);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else if (this.mode === "interactive") {
|
} else if (this.mode === "interactive") {
|
||||||
console.debug("Create Interactive terminal:", this.name);
|
console.debug("Create Interactive terminal:", this.name);
|
||||||
this.$root.emitAgent(this.endpoint, "interactiveTerminal", this.stackName, this.serviceName, this.shell, (res) => {
|
this.$root.getSocket().emit("interactiveTerminal", this.stackName, this.serviceName, this.shell, (res) => {
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
this.$root.toastRes(res);
|
this.$root.toastRes(res);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
// Fit the terminal width to the div container size after terminal is created.
|
|
||||||
this.updateTerminalSize();
|
|
||||||
},
|
},
|
||||||
|
|
||||||
unmounted() {
|
unmounted() {
|
||||||
window.removeEventListener("resize", this.onResizeEvent); // Remove the resize event listener from the window object.
|
|
||||||
this.$root.unbindTerminal(this.name);
|
this.$root.unbindTerminal(this.name);
|
||||||
this.terminal.dispose();
|
this.terminal.dispose();
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
bind(endpoint, name) {
|
bind(name) {
|
||||||
// Workaround: normally this.name should be set, but it is not sometimes, so we use the parameter, but eventually this.name and name must be the same name
|
// Workaround: normally this.name should be set, but it is not sometimes, so we use the parameter, but eventually this.name and name must be the same name
|
||||||
if (name) {
|
if (name) {
|
||||||
this.$root.unbindTerminal(name);
|
this.$root.unbindTerminal(name);
|
||||||
this.$root.bindTerminal(endpoint, name, this.terminal);
|
this.$root.bindTerminal(name, this.terminal);
|
||||||
console.debug("Terminal bound via parameter: " + name);
|
console.debug("Terminal bound via parameter: " + name);
|
||||||
} else if (this.name) {
|
} else if (this.name) {
|
||||||
this.$root.unbindTerminal(this.name);
|
this.$root.unbindTerminal(this.name);
|
||||||
this.$root.bindTerminal(this.endpoint, this.name, this.terminal);
|
this.$root.bindTerminal(this.name, this.terminal);
|
||||||
console.debug("Terminal bound: " + this.name);
|
console.debug("Terminal bound: " + this.name);
|
||||||
} else {
|
} else {
|
||||||
console.debug("Terminal name not set");
|
console.debug("Terminal name not set");
|
||||||
@ -177,7 +170,7 @@ export default {
|
|||||||
// Remove the input from the terminal
|
// Remove the input from the terminal
|
||||||
this.removeInput();
|
this.removeInput();
|
||||||
|
|
||||||
this.$root.emitAgent(this.endpoint, "terminalInput", this.name, buffer + e.key, (err) => {
|
this.$root.getSocket().emit("terminalInput", this.name, buffer + e.key, (err) => {
|
||||||
this.$root.toastError(err.msg);
|
this.$root.toastError(err.msg);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -196,7 +189,7 @@ export default {
|
|||||||
// TODO
|
// TODO
|
||||||
} else if (e.key === "\u0003") { // Ctrl + C
|
} else if (e.key === "\u0003") { // Ctrl + C
|
||||||
console.debug("Ctrl + C");
|
console.debug("Ctrl + C");
|
||||||
this.$root.emitAgent(this.endpoint, "terminalInput", this.name, e.key);
|
this.$root.getSocket().emit("terminalInput", this.name, e.key);
|
||||||
this.removeInput();
|
this.removeInput();
|
||||||
} else {
|
} else {
|
||||||
this.cursorPosition++;
|
this.cursorPosition++;
|
||||||
@ -209,36 +202,12 @@ export default {
|
|||||||
|
|
||||||
interactiveTerminalConfig() {
|
interactiveTerminalConfig() {
|
||||||
this.terminal.onKey(e => {
|
this.terminal.onKey(e => {
|
||||||
this.$root.emitAgent(this.endpoint, "terminalInput", this.name, e.key, (res) => {
|
this.$root.getSocket().emit("terminalInput", this.name, e.key, (res) => {
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
this.$root.toastRes(res);
|
this.$root.toastRes(res);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update the terminal size to fit the container size.
|
|
||||||
*
|
|
||||||
* If the terminalFitAddOn is not created, creates it, loads it and then fits the terminal to the appropriate size.
|
|
||||||
* It then addes an event listener to the window object to listen for resize events and calls the fit method of the terminalFitAddOn.
|
|
||||||
*/
|
|
||||||
updateTerminalSize() {
|
|
||||||
if (!Object.hasOwn(this, "terminalFitAddOn")) {
|
|
||||||
this.terminalFitAddOn = new FitAddon();
|
|
||||||
this.terminal.loadAddon(this.terminalFitAddOn);
|
|
||||||
window.addEventListener("resize", this.onResizeEvent);
|
|
||||||
}
|
|
||||||
this.terminalFitAddOn.fit();
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Handles the resize event of the terminal component.
|
|
||||||
*/
|
|
||||||
onResizeEvent() {
|
|
||||||
this.terminalFitAddOn.fit();
|
|
||||||
let rows = this.terminal.rows;
|
|
||||||
let cols = this.terminal.cols;
|
|
||||||
this.$root.emitAgent(this.endpoint, "terminalResize", this.name, rows, cols);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { statusColor, statusNameShort } from "../../../common/util-common";
|
import { statusColor, statusNameShort } from "../../../backend/util-common";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
"registry": "Регистър",
|
"registry": "Регистър",
|
||||||
"compose": "Compose",
|
"compose": "Compose",
|
||||||
"addFirstStackMsg": "Създайте вашия първи стак!",
|
"addFirstStackMsg": "Създайте вашия първи стак!",
|
||||||
"stackName": "Име на стак",
|
"stackName" : "Име на стак",
|
||||||
"deployStack": "Разположи",
|
"deployStack": "Разположи",
|
||||||
"deleteStack": "Изтрий",
|
"deleteStack": "Изтрий",
|
||||||
"stopStack": "Спри",
|
"stopStack": "Спри",
|
||||||
@ -22,7 +22,7 @@
|
|||||||
"editStack": "Редактирай",
|
"editStack": "Редактирай",
|
||||||
"discardStack": "Отхвърли",
|
"discardStack": "Отхвърли",
|
||||||
"saveStackDraft": "Запази",
|
"saveStackDraft": "Запази",
|
||||||
"notAvailableShort": "N/A",
|
"notAvailableShort" : "N/A",
|
||||||
"deleteStackMsg": "Сигурни ли сте, че желаете да изтриете този стак?",
|
"deleteStackMsg": "Сигурни ли сте, че желаете да изтриете този стак?",
|
||||||
"stackNotManagedByDockgeMsg": "Този стак не се управлява от Dockge.",
|
"stackNotManagedByDockgeMsg": "Този стак не се управлява от Dockge.",
|
||||||
"primaryHostname": "Основно име на хост",
|
"primaryHostname": "Основно име на хост",
|
||||||
@ -90,13 +90,5 @@
|
|||||||
"Allowed commands:": "Позволени команди:",
|
"Allowed commands:": "Позволени команди:",
|
||||||
"Internal Networks": "Вътрешни мрежи",
|
"Internal Networks": "Вътрешни мрежи",
|
||||||
"External Networks": "Външни мрежи",
|
"External Networks": "Външни мрежи",
|
||||||
"No External Networks": "Не са налични външни мрежи",
|
"No External Networks": "Не са налични външни мрежи"
|
||||||
"reverseProxyMsg2": "Проверете как да го конфигурирате за WebSocket",
|
|
||||||
"downStack": "Спри и изключи",
|
|
||||||
"reverseProxyMsg1": "Използвате ревърс прокси?",
|
|
||||||
"Cannot connect to the socket server.": "Не може да се свърже със сокет сървъра.",
|
|
||||||
"url": "URL адрес | URL адреси",
|
|
||||||
"extra": "Допълнително",
|
|
||||||
"reconnecting...": "Повторно свързване…",
|
|
||||||
"connecting...": "Свързване със сокет сървъра…"
|
|
||||||
}
|
}
|
||||||
|
@ -3,39 +3,39 @@
|
|||||||
"Create your admin account": "Vytvořit účet administrátora",
|
"Create your admin account": "Vytvořit účet administrátora",
|
||||||
"authIncorrectCreds": "Nesprávné uživatelské jméno nebo heslo.",
|
"authIncorrectCreds": "Nesprávné uživatelské jméno nebo heslo.",
|
||||||
"PasswordsDoNotMatch": "Hesla se neshodují.",
|
"PasswordsDoNotMatch": "Hesla se neshodují.",
|
||||||
"Repeat Password": "Napište Heslo Znovu",
|
"Repeat Password": "Opakujte heslo",
|
||||||
"Create": "Vytvořit",
|
"Create": "Vytvořit",
|
||||||
"signedInDisp": "Přihlášen jako {0}",
|
"signedInDisp": "Přihlášen jako {0}",
|
||||||
"signedInDispDisabled": "Ověření Zakázáno.",
|
"signedInDispDisabled": "Ověření zakázáno.",
|
||||||
"home": "Domů",
|
"home": "Domů",
|
||||||
"console": "Konzole",
|
"console": "Konzole",
|
||||||
"registry": "Registry",
|
"registry": "Registry",
|
||||||
"compose": "Komponovat",
|
"compose": "Compose",
|
||||||
"addFirstStackMsg": "Vytvořte svůj první zásobník!",
|
"addFirstStackMsg": "Vytvořte svůj první stack!",
|
||||||
"stackName": "Název Zásobníku",
|
"stackName": "Název stacku",
|
||||||
"deployStack": "Nainstalovat",
|
"deployStack": "Nainstalovat",
|
||||||
"deleteStack": "Smazat",
|
"deleteStack": "Smazat",
|
||||||
"stopStack": "Zastavit",
|
"stopStack": "Zastavit",
|
||||||
"restartStack": "Restartovat",
|
"restartStack": "Restartovat",
|
||||||
"updateStack": "Aktualizovat",
|
"updateStack": "Aktualizovat",
|
||||||
"startStack": "Spustit",
|
"startStack": "Spustit",
|
||||||
"downStack": "Zastavit & Vypnout",
|
"downStack": "Zastavit a vypnout",
|
||||||
"editStack": "Upravit",
|
"editStack": "Upravit",
|
||||||
"discardStack": "Zahodit",
|
"discardStack": "Zahodit",
|
||||||
"saveStackDraft": "Uložit",
|
"saveStackDraft": "Uložit",
|
||||||
"notAvailableShort": "N/A",
|
"notAvailableShort": "N/A",
|
||||||
"deleteStackMsg": "Opravdu chcete smazat tento zásobník?",
|
"deleteStackMsg": "Opravdu chcete smazat tento stack?",
|
||||||
"stackNotManagedByDockgeMsg": "Tento stack není spravován systémem Dockge.",
|
"stackNotManagedByDockgeMsg": "Tento stack není spravován systémem Dockge.",
|
||||||
"primaryHostname": "Primární název hostitele",
|
"primaryHostname": "Primární název hostitele",
|
||||||
"general": "Obecné",
|
"general": "Obecné",
|
||||||
"container": "Kontejner | Kontejnery",
|
"container": "Kontejner | Kontejnery",
|
||||||
"scanFolder": "Prohledat složku se zásobníky",
|
"scanFolder": "Prohledat složku se stacky",
|
||||||
"dockerImage": "Obrázek",
|
"dockerImage": "Obrázek",
|
||||||
"restartPolicyUnlessStopped": "Pokud není zastaveno",
|
"restartPolicyUnlessStopped": "Pokud není zastaveno",
|
||||||
"restartPolicyAlways": "Vždy",
|
"restartPolicyAlways": "Vždy",
|
||||||
"restartPolicyOnFailure": "Při Selhání",
|
"restartPolicyOnFailure": "Při selhání",
|
||||||
"restartPolicyNo": "Ne",
|
"restartPolicyNo": "Ne",
|
||||||
"environmentVariable": "Proměnná Prostředí | Proměnné Prostředí",
|
"environmentVariable": "Proměnná prostředí | Proměnné prostředí",
|
||||||
"restartPolicy": "Politika restartu",
|
"restartPolicy": "Politika restartu",
|
||||||
"containerName": "Název kontejneru",
|
"containerName": "Název kontejneru",
|
||||||
"port": "Port | Porty",
|
"port": "Port | Porty",
|
||||||
@ -91,11 +91,5 @@
|
|||||||
"Allowed commands:": "Povolené příkazy:",
|
"Allowed commands:": "Povolené příkazy:",
|
||||||
"Internal Networks": "Interní sítě",
|
"Internal Networks": "Interní sítě",
|
||||||
"External Networks": "Externí sítě",
|
"External Networks": "Externí sítě",
|
||||||
"No External Networks": "Žádné externí sítě",
|
"No External Networks": "Žádné externí sítě"
|
||||||
"reconnecting...": "Opětovné připojení…",
|
|
||||||
"url": "Adresa URL | Adresy URL",
|
|
||||||
"extra": "Extra",
|
|
||||||
"reverseProxyMsg1": "Používáte Reverzní proxy server?",
|
|
||||||
"reverseProxyMsg2": "Podívat se jak to nastavit pro WebSocket",
|
|
||||||
"Cannot connect to the socket server.": "Nelze se připojit k serveru ."
|
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
"home": "Startseite",
|
"home": "Startseite",
|
||||||
"console": "Konsole",
|
"console": "Konsole",
|
||||||
"registry": "Container Registry",
|
"registry": "Container Registry",
|
||||||
"compose": "Compose",
|
"compose": "",
|
||||||
"addFirstStackMsg": "Stelle deinen ersten Stack zusammen!",
|
"addFirstStackMsg": "Stelle deinen ersten Stack zusammen!",
|
||||||
"stackName": "Stack-Name",
|
"stackName": "Stack-Name",
|
||||||
"deployStack": "Deployen",
|
"deployStack": "Deployen",
|
||||||
@ -72,15 +72,15 @@
|
|||||||
"Check Update On GitHub": "Update auf GitHub überprüfen",
|
"Check Update On GitHub": "Update auf GitHub überprüfen",
|
||||||
"Show update if available": "Update anzeigen, wenn verfügbar",
|
"Show update if available": "Update anzeigen, wenn verfügbar",
|
||||||
"Also check beta release": "Auch Beta-Version überprüfen",
|
"Also check beta release": "Auch Beta-Version überprüfen",
|
||||||
"Remember me": "Angemeldet bleiben",
|
"Remember me": "Anmeldung beibehalten",
|
||||||
"Login": "Anmelden",
|
"Login": "Anmelden",
|
||||||
"Username": "Benutzername",
|
"Username": "Benutzername",
|
||||||
"Password": "Passwort",
|
"Password": "Passwort",
|
||||||
"Settings": "Einstellungen",
|
"Settings": "Einstellungen",
|
||||||
"Logout": "Abmelden",
|
"Logout": "Abmelden",
|
||||||
"Lowercase only": "Nur Kleinbuchstaben",
|
"Lowercase only": "Nur Kleinbuchstaben",
|
||||||
"Convert to Compose": "In Compose-Syntax umwandeln",
|
"Convert to Compose": "In Compose Syntax umwandeln",
|
||||||
"Docker Run": "Docker Run",
|
"Docker Run": "Docker ausführen",
|
||||||
"active": "aktiv",
|
"active": "aktiv",
|
||||||
"exited": "beendet",
|
"exited": "beendet",
|
||||||
"inactive": "inaktiv",
|
"inactive": "inaktiv",
|
||||||
|
@ -99,17 +99,5 @@
|
|||||||
"connecting...": "Connecting to the socket server…",
|
"connecting...": "Connecting to the socket server…",
|
||||||
"url": "URL | URLs",
|
"url": "URL | URLs",
|
||||||
"extra": "Extra",
|
"extra": "Extra",
|
||||||
"newUpdate": "New Update",
|
"newUpdate": "New Update"
|
||||||
"dockgeAgent": "Dockge Agent | Dockge Agents",
|
|
||||||
"currentEndpoint": "Current",
|
|
||||||
"dockgeURL": "Dockge URL (e.g. http://127.0.0.1:5001)",
|
|
||||||
"agentOnline": "Online",
|
|
||||||
"agentOffline": "Offline",
|
|
||||||
"connecting": "Connecting",
|
|
||||||
"connect": "Connect",
|
|
||||||
"addAgent": "Add Agent",
|
|
||||||
"agentAddedSuccessfully": "Agent added successfully.",
|
|
||||||
"agentRemovedSuccessfully": "Agent removed successfully.",
|
|
||||||
"removeAgent": "Remove Agent",
|
|
||||||
"removeAgentMsg": "Are you sure you want to remove this agent?"
|
|
||||||
}
|
}
|
||||||
|
@ -98,6 +98,5 @@
|
|||||||
"downStack": "Arrêter et désactiver",
|
"downStack": "Arrêter et désactiver",
|
||||||
"reverseProxyMsg1": "Utilisez vous un proxy inverse ?",
|
"reverseProxyMsg1": "Utilisez vous un proxy inverse ?",
|
||||||
"Cannot connect to the socket server.": "Impossible de se connecter au serveur socket.",
|
"Cannot connect to the socket server.": "Impossible de se connecter au serveur socket.",
|
||||||
"reconnecting...": "Reconnexion…",
|
"reconnecting...": "Reconnexion…"
|
||||||
"newUpdate": "Nouvelle mise à jour"
|
|
||||||
}
|
}
|
||||||
|
@ -10,10 +10,10 @@
|
|||||||
"home": "Home",
|
"home": "Home",
|
||||||
"console": "Console",
|
"console": "Console",
|
||||||
"registry": "Registro",
|
"registry": "Registro",
|
||||||
"compose": "Componi",
|
"compose": "Compose",
|
||||||
"addFirstStackMsg": "Componi il tuo primo stack!",
|
"addFirstStackMsg": "Componi il tuo primo stack!",
|
||||||
"stackName": "Nome dello stack",
|
"stackName": "Nome dello stack",
|
||||||
"deployStack": "Rilascia",
|
"deployStack": "Deploy",
|
||||||
"deleteStack": "Cancella",
|
"deleteStack": "Cancella",
|
||||||
"stopStack": "Stop",
|
"stopStack": "Stop",
|
||||||
"restartStack": "Riavvia",
|
"restartStack": "Riavvia",
|
||||||
@ -75,7 +75,7 @@
|
|||||||
"Also check beta release": "Controlla anche le release in beta",
|
"Also check beta release": "Controlla anche le release in beta",
|
||||||
"Remember me": "Ricordami",
|
"Remember me": "Ricordami",
|
||||||
"Login": "Login",
|
"Login": "Login",
|
||||||
"Username": "Nome Utente",
|
"Username": "Username",
|
||||||
"Password": "Password",
|
"Password": "Password",
|
||||||
"Settings": "Impostazioni",
|
"Settings": "Impostazioni",
|
||||||
"Logout": "Logout",
|
"Logout": "Logout",
|
||||||
@ -97,6 +97,5 @@
|
|||||||
"Cannot connect to the socket server.": "Impossibile connettersi al server socket.",
|
"Cannot connect to the socket server.": "Impossibile connettersi al server socket.",
|
||||||
"connecting...": "Connessione al server socket…",
|
"connecting...": "Connessione al server socket…",
|
||||||
"extra": "Extra",
|
"extra": "Extra",
|
||||||
"reconnecting...": "Riconnessione…",
|
"reconnecting...": "Riconnessione…"
|
||||||
"url": "Indirizzo | Indirizzi"
|
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
"registry": "Registro",
|
"registry": "Registro",
|
||||||
"compose": "Compose",
|
"compose": "Compose",
|
||||||
"addFirstStackMsg": "Crie sua primeira stack!",
|
"addFirstStackMsg": "Crie sua primeira stack!",
|
||||||
"stackName": "Nome da stack",
|
"stackName" : "Nome da stack",
|
||||||
"deployStack": "Deploy",
|
"deployStack": "Deploy",
|
||||||
"deleteStack": "Excluir",
|
"deleteStack": "Excluir",
|
||||||
"stopStack": "Parar",
|
"stopStack": "Parar",
|
||||||
@ -22,7 +22,7 @@
|
|||||||
"editStack": "Editar",
|
"editStack": "Editar",
|
||||||
"discardStack": "Descartar",
|
"discardStack": "Descartar",
|
||||||
"saveStackDraft": "Salvar",
|
"saveStackDraft": "Salvar",
|
||||||
"notAvailableShort": "N/D",
|
"notAvailableShort" : "N/D",
|
||||||
"deleteStackMsg": "Tem certeza que deseja excluir esta stack?",
|
"deleteStackMsg": "Tem certeza que deseja excluir esta stack?",
|
||||||
"stackNotManagedByDockgeMsg": "Esta stack não é gerenciada pelo Dockge.",
|
"stackNotManagedByDockgeMsg": "Esta stack não é gerenciada pelo Dockge.",
|
||||||
"primaryHostname": "Nome do Host Primário",
|
"primaryHostname": "Nome do Host Primário",
|
||||||
@ -90,13 +90,5 @@
|
|||||||
"Allowed commands:": "Comandos permitidos:",
|
"Allowed commands:": "Comandos permitidos:",
|
||||||
"Internal Networks": "Redes internas",
|
"Internal Networks": "Redes internas",
|
||||||
"External Networks": "Redes externas",
|
"External Networks": "Redes externas",
|
||||||
"No External Networks": "Sem redes externas",
|
"No External Networks": "Sem redes externas"
|
||||||
"reverseProxyMsg2": "Veja como configurar para WebSocket",
|
|
||||||
"downStack": "Parar & Encerrar",
|
|
||||||
"reverseProxyMsg1": "Utiliza proxy reverso?",
|
|
||||||
"Cannot connect to the socket server.": "Não é possível conectar ao socket server.",
|
|
||||||
"connecting...": "Conectando ao socket server…",
|
|
||||||
"url": "URL | URLs",
|
|
||||||
"extra": "Extra",
|
|
||||||
"reconnecting...": "Reconectando…"
|
|
||||||
}
|
}
|
||||||
|
@ -5,12 +5,12 @@
|
|||||||
"PasswordsDoNotMatch": "Пароль не совпадает.",
|
"PasswordsDoNotMatch": "Пароль не совпадает.",
|
||||||
"Repeat Password": "Повторите пароль",
|
"Repeat Password": "Повторите пароль",
|
||||||
"Create": "Создать",
|
"Create": "Создать",
|
||||||
"signedInDisp": "Авторизован как {0}",
|
"signedInDisp": "Авторизован как",
|
||||||
"signedInDispDisabled": "Авторизация выключена.",
|
"signedInDispDisabled": "Авторизация выключена.",
|
||||||
"home": "Главная",
|
"home": "Главная",
|
||||||
"console": "Консоль",
|
"console": "Консоль",
|
||||||
"registry": "Реестр (Registry)",
|
"registry": "Registry",
|
||||||
"compose": "Составить (Compose)",
|
"compose": "Compose",
|
||||||
"addFirstStackMsg": "Создайте свой первый стек!",
|
"addFirstStackMsg": "Создайте свой первый стек!",
|
||||||
"stackName": "Имя стека",
|
"stackName": "Имя стека",
|
||||||
"deployStack": "Развернуть",
|
"deployStack": "Развернуть",
|
||||||
@ -90,13 +90,5 @@
|
|||||||
"Allowed commands:": "Разрешенные команды:",
|
"Allowed commands:": "Разрешенные команды:",
|
||||||
"Internal Networks": "Внутренние сети",
|
"Internal Networks": "Внутренние сети",
|
||||||
"External Networks": "Внешние сети",
|
"External Networks": "Внешние сети",
|
||||||
"No External Networks": "Нет внешних сетей",
|
"No External Networks": "Нет внешних сетей"
|
||||||
"downStack": "Остановить и выключить",
|
|
||||||
"reverseProxyMsg1": "Использовать Реверс Прокси?",
|
|
||||||
"reconnecting...": "Переподключение…",
|
|
||||||
"Cannot connect to the socket server.": "Не удается подключиться к серверу сокетов.",
|
|
||||||
"url": "URL адрес(а)",
|
|
||||||
"extra": "Дополнительно",
|
|
||||||
"reverseProxyMsg2": "Проверьте, как настроить его для WebSocket",
|
|
||||||
"connecting...": "Подключение к серверу сокетов…"
|
|
||||||
}
|
}
|
||||||
|
@ -90,10 +90,5 @@
|
|||||||
"Allowed commands:": "Dovoljeni ukazi:",
|
"Allowed commands:": "Dovoljeni ukazi:",
|
||||||
"Internal Networks": "Notranja omrežja",
|
"Internal Networks": "Notranja omrežja",
|
||||||
"External Networks": "Zunanja omrežja",
|
"External Networks": "Zunanja omrežja",
|
||||||
"No External Networks": "Ni zunanjih omrežij",
|
"No External Networks": "Ni zunanjih omrežij"
|
||||||
"downStack": "Ustavi & Odstrani",
|
|
||||||
"connecting...": "Povezovanje s strežnikom…",
|
|
||||||
"reverseProxyMsg1": "Uporabljate obratni proxy?",
|
|
||||||
"extra": "Dodatno",
|
|
||||||
"reconnecting...": "Ponovna povezava …"
|
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"languageName": "Svenska",
|
"languageName": "Svenska",
|
||||||
"Create your admin account": "Skapa ditt Admin-konto",
|
"Create your admin account": "Skapa ditt Admin-konto.",
|
||||||
"authIncorrectCreds": "Fel användarnamn eller lösenord.",
|
"authIncorrectCreds": "Fel användarnamn eller lösenord.",
|
||||||
"PasswordsDoNotMatch": "Lösenorden matchar inte.",
|
"PasswordsDoNotMatch": "Lösenorden matchar inte.",
|
||||||
"Repeat Password": "Repetera lösenord",
|
"Repeat Password": "Repetera lösenord",
|
||||||
@ -12,28 +12,28 @@
|
|||||||
"registry": "Register",
|
"registry": "Register",
|
||||||
"compose": "Komponera",
|
"compose": "Komponera",
|
||||||
"addFirstStackMsg": "Komponera din första stack!",
|
"addFirstStackMsg": "Komponera din första stack!",
|
||||||
"stackName": "Stacknamn",
|
"stackName" : "Stacknamn",
|
||||||
"deployStack": "Distribuera",
|
"deployStack": "Distribuera",
|
||||||
"deleteStack": "Radera",
|
"deleteStack": "Radera",
|
||||||
"stopStack": "Stoppa",
|
"stopStack": "Stop",
|
||||||
"restartStack": "Starta om",
|
"restartStack": "Starta om",
|
||||||
"updateStack": "Uppdatera",
|
"updateStack": "Uppdatera",
|
||||||
"startStack": "Starta",
|
"startStack": "Starta",
|
||||||
"downStack": "Stoppa & Ner",
|
"downStack": "Stop & Ner",
|
||||||
"editStack": "Redigera",
|
"editStack": "Redigera",
|
||||||
"discardStack": "Kasta",
|
"discardStack": "Kasta",
|
||||||
"saveStackDraft": "Spara",
|
"saveStackDraft": "Spara",
|
||||||
"notAvailableShort": "N/A",
|
"notAvailableShort" : "N/A",
|
||||||
"deleteStackMsg": "Är du säker på att du vill radera stacken?",
|
"deleteStackMsg": "Är du säker på att du vill radera stacken?",
|
||||||
"stackNotManagedByDockgeMsg": "Denna stacken hanteras inte av Dockge.",
|
"stackNotManagedByDockgeMsg": "Denna stacken hanteras inte av Dockge.",
|
||||||
"primaryHostname": "Primärt värdnamn",
|
"primaryHostname": "Primärt värdnamn",
|
||||||
"general": "Allmän",
|
"general": "Allmän",
|
||||||
"container": "Container | Containrar",
|
"container": "Container | Containrar",
|
||||||
"scanFolder": "Skanna Stackmapp",
|
"scanFolder": "Scanna Stackfolder",
|
||||||
"dockerImage": "Avbild",
|
"dockerImage": "Bild",
|
||||||
"restartPolicyUnlessStopped": "Om inte stoppad",
|
"restartPolicyUnlessStopped": "Om inte stoppas",
|
||||||
"restartPolicyAlways": "Alltid",
|
"restartPolicyAlways": "Alltid",
|
||||||
"restartPolicyOnFailure": "Vid misslyckande",
|
"restartPolicyOnFailure": "Vid Misslyckande",
|
||||||
"restartPolicyNo": "Nej",
|
"restartPolicyNo": "Nej",
|
||||||
"environmentVariable": "Miljövariabel | Miljövariabler",
|
"environmentVariable": "Miljövariabel | Miljövariabler",
|
||||||
"restartPolicy": "Omstartspolicy",
|
"restartPolicy": "Omstartspolicy",
|
||||||
@ -44,12 +44,12 @@
|
|||||||
"dependsOn": "Containerberoende | Containerberoenden",
|
"dependsOn": "Containerberoende | Containerberoenden",
|
||||||
"addListItem": "Lägg till {0}",
|
"addListItem": "Lägg till {0}",
|
||||||
"deleteContainer": "Radera",
|
"deleteContainer": "Radera",
|
||||||
"addContainer": "Lägg till container",
|
"addContainer": "Lägg till Container",
|
||||||
"addNetwork": "Lägg till nätverk",
|
"addNetwork": "Lägg till Nätverk",
|
||||||
"disableauth.message1": "Är du säker på att du vill <strong>inaktivera autentisering</strong>?",
|
"disableauth.message1": "Är du säker på att du vill <strong>inaktivera autentisering</strong>?",
|
||||||
"disableauth.message2": "Det är designat för scenarion <strong>där du ska implementera tredjepartsautentisering</strong> framför Dockge som Cloudflare Access, Authelia eller andra autentiseringsmekanismer.",
|
"disableauth.message2": "Det är designat för senarion <stong>när du ska implementera tredjeparts autentisering</strong> framör Dockge som Cloudflare Access, Authelia eller andra autentiseringsmekanismer.",
|
||||||
"passwordNotMatchMsg": "Det upprepade lösenordet matchar inte.",
|
"passwordNotMatchMsg": "Det upprepade lösenordet matchar inte",
|
||||||
"autoGet": "Auto-hämta",
|
"autoGet": "Auto Hämta",
|
||||||
"add": "Lägg till",
|
"add": "Lägg till",
|
||||||
"Edit": "Redigera",
|
"Edit": "Redigera",
|
||||||
"applyToYAML": "Lägg till i YAML",
|
"applyToYAML": "Lägg till i YAML",
|
||||||
@ -57,8 +57,8 @@
|
|||||||
"addInternalNetwork": "Lägg till",
|
"addInternalNetwork": "Lägg till",
|
||||||
"Save": "Spara",
|
"Save": "Spara",
|
||||||
"Language": "Språk",
|
"Language": "Språk",
|
||||||
"Current User": "Nuvarande användare",
|
"Current User": "Nuvarande användaren",
|
||||||
"Change Password": "Ändra lösenord",
|
"Change Password": "Byt lösenord",
|
||||||
"Current Password": "Nuvarande lösenord",
|
"Current Password": "Nuvarande lösenord",
|
||||||
"New Password": "Nytt lösenord",
|
"New Password": "Nytt lösenord",
|
||||||
"Repeat New Password": "Upprepa nytt lösenord",
|
"Repeat New Password": "Upprepa nytt lösenord",
|
||||||
@ -70,9 +70,9 @@
|
|||||||
"I understand, please disable": "Jag förstår, vänligen inaktivera",
|
"I understand, please disable": "Jag förstår, vänligen inaktivera",
|
||||||
"Leave": "Lämna",
|
"Leave": "Lämna",
|
||||||
"Frontend Version": "Frontendversion",
|
"Frontend Version": "Frontendversion",
|
||||||
"Check Update On GitHub": "Kontrollera uppdatering på GitHub",
|
"Check Update On GitHub": "Kontrollera Uppdatering på GitHub",
|
||||||
"Show update if available": "Visa uppdatering om tillgänglig",
|
"Show update if available": "Visa uppdatering om tillgänglig",
|
||||||
"Also check beta release": "Kontrollera även betaversioner",
|
"Also check beta release": "Kontrollera även betaversionen",
|
||||||
"Remember me": "Kom ihåg mig",
|
"Remember me": "Kom ihåg mig",
|
||||||
"Login": "Logga in",
|
"Login": "Logga in",
|
||||||
"Username": "Användarnamn",
|
"Username": "Användarnamn",
|
||||||
@ -80,8 +80,8 @@
|
|||||||
"Settings": "Inställningar",
|
"Settings": "Inställningar",
|
||||||
"Logout": "Logga ut",
|
"Logout": "Logga ut",
|
||||||
"Lowercase only": "Endast små tecken",
|
"Lowercase only": "Endast små tecken",
|
||||||
"Convert to Compose": "Omvandla till compose",
|
"Convert to Compose": "Omvandla till Compose",
|
||||||
"Docker Run": "Docker kör",
|
"Docker Run": "Docker Run",
|
||||||
"active": "aktiv",
|
"active": "aktiv",
|
||||||
"exited": "avslutad",
|
"exited": "avslutad",
|
||||||
"inactive": "inaktiv",
|
"inactive": "inaktiv",
|
||||||
@ -89,14 +89,7 @@
|
|||||||
"Security": "Säkerhet",
|
"Security": "Säkerhet",
|
||||||
"About": "Om",
|
"About": "Om",
|
||||||
"Allowed commands:": "Tillåtna kommandon:",
|
"Allowed commands:": "Tillåtna kommandon:",
|
||||||
"Internal Networks": "Interna nätverk",
|
"Internal Networks": "Interna Nätverk",
|
||||||
"External Networks": "Externa nätverk",
|
"External Networks": "Externa Nätverk",
|
||||||
"No External Networks": "Inga externa nätverk",
|
"No External Networks": "Inga Externa Nätverk"
|
||||||
"reverseProxyMsg1": "Används omvänd proxy?",
|
|
||||||
"connecting...": "Ansluter till socketserver…",
|
|
||||||
"Cannot connect to the socket server.": "Kan inte ansluta till socketservern.",
|
|
||||||
"reverseProxyMsg2": "Kontrollera hur man konfigurerar webbsocket",
|
|
||||||
"url": "URL | URLer",
|
|
||||||
"extra": "Extra",
|
|
||||||
"reconnecting...": "Återansluter…"
|
|
||||||
}
|
}
|
||||||
|
@ -91,12 +91,5 @@
|
|||||||
"Allowed commands:": "คำสั่งที่อนุญาต:",
|
"Allowed commands:": "คำสั่งที่อนุญาต:",
|
||||||
"Internal Networks": "เครือข่ายภายใน",
|
"Internal Networks": "เครือข่ายภายใน",
|
||||||
"External Networks": "เครือข่ายภายนอก",
|
"External Networks": "เครือข่ายภายนอก",
|
||||||
"No External Networks": "ไม่มีเครือข่ายภายนอก",
|
"No External Networks": "ไม่มีเครือข่ายภายนอก"
|
||||||
"reverseProxyMsg2": "ตรวจสอบวิธีกำหนดค่าสำหรับ WebSocket",
|
}
|
||||||
"Cannot connect to the socket server.": "ไม่สามารถเชื่อมต่อกับเซิร์ฟเวอร์ socket ได้",
|
|
||||||
"reverseProxyMsg1": "ใช้ Reverse Proxy หรือไม่?",
|
|
||||||
"connecting...": "กำลังเชื่อมต่อกับเซิร์ฟเวอร์ socket…",
|
|
||||||
"url": "URL | URLs",
|
|
||||||
"extra": "พิเศษ",
|
|
||||||
"reconnecting...": "กำลังเชื่อมต่อใหม่…"
|
|
||||||
}
|
|
@ -92,7 +92,7 @@
|
|||||||
"External Networks": "Зовнішні мережі",
|
"External Networks": "Зовнішні мережі",
|
||||||
"No External Networks": "Немає зовнішніх мереж",
|
"No External Networks": "Немає зовнішніх мереж",
|
||||||
"downStack": "Зупинити і вимкнути",
|
"downStack": "Зупинити і вимкнути",
|
||||||
"reverseProxyMsg1": "Використовуєте зворотній проксі?",
|
"reverseProxyMsg1": "Використовувати зворотній проксі?",
|
||||||
"Cannot connect to the socket server.": "Не вдається підключитися до сервера сокетів.",
|
"Cannot connect to the socket server.": "Не вдається підключитися до сервера сокетів.",
|
||||||
"reconnecting...": "Повторне підключення…",
|
"reconnecting...": "Повторне підключення…",
|
||||||
"connecting...": "Підключення до сервера сокетів…",
|
"connecting...": "Підключення до сервера сокетів…",
|
||||||
|
@ -51,7 +51,7 @@
|
|||||||
"autoGet": "自動取得",
|
"autoGet": "自動取得",
|
||||||
"add": "新增",
|
"add": "新增",
|
||||||
"Edit": "編輯",
|
"Edit": "編輯",
|
||||||
"applyToYAML": "套用到 YAML",
|
"applyToYAML": "應用到YAML",
|
||||||
"createExternalNetwork": "建立",
|
"createExternalNetwork": "建立",
|
||||||
"addInternalNetwork": "新增",
|
"addInternalNetwork": "新增",
|
||||||
"Save": "儲存",
|
"Save": "儲存",
|
||||||
@ -71,7 +71,7 @@
|
|||||||
"Frontend Version": "前端版本",
|
"Frontend Version": "前端版本",
|
||||||
"Check Update On GitHub": "在 GitHub 上檢查更新",
|
"Check Update On GitHub": "在 GitHub 上檢查更新",
|
||||||
"Show update if available": "有更新時提醒我",
|
"Show update if available": "有更新時提醒我",
|
||||||
"Also check beta release": "同時檢查 Beta 版更新",
|
"Also check beta release": "同時檢查 Beta 渠道更新",
|
||||||
"Remember me": "記住我",
|
"Remember me": "記住我",
|
||||||
"Login": "登入",
|
"Login": "登入",
|
||||||
"Username": "使用者名稱",
|
"Username": "使用者名稱",
|
||||||
@ -92,7 +92,7 @@
|
|||||||
"External Networks": "外部網路",
|
"External Networks": "外部網路",
|
||||||
"No External Networks": "無外部網路",
|
"No External Networks": "無外部網路",
|
||||||
"downStack": "停止",
|
"downStack": "停止",
|
||||||
"reverseProxyMsg1": "在使用反向代理嗎?",
|
"reverseProxyMsg1": "在使用反向代理吗?",
|
||||||
"reverseProxyMsg2": "點擊這裡了解如何為 WebSocket 配置反向代理",
|
"reverseProxyMsg2": "點擊這裡了解如何為 WebSocket 配置反向代理",
|
||||||
"Cannot connect to the socket server.": "無法連接到 Socket 伺服器。",
|
"Cannot connect to the socket server.": "無法連接到 Socket 伺服器。",
|
||||||
"reconnecting...": "重新連線中…",
|
"reconnecting...": "重新連線中…",
|
||||||
|
@ -98,7 +98,6 @@
|
|||||||
<script>
|
<script>
|
||||||
import Login from "../components/Login.vue";
|
import Login from "../components/Login.vue";
|
||||||
import { compareVersions } from "compare-versions";
|
import { compareVersions } from "compare-versions";
|
||||||
import { ALL_ENDPOINTS } from "../../../common/util-common";
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
|
||||||
@ -146,7 +145,7 @@ export default {
|
|||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
scanFolder() {
|
scanFolder() {
|
||||||
this.$root.emitAgent(ALL_ENDPOINTS, "requestStackList", (res) => {
|
this.$root.getSocket().emit("requestStackList", (res) => {
|
||||||
this.$root.toastRes(res);
|
this.$root.toastRes(res);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -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 "../../common/util-common";
|
import "../../backend/util-common";
|
||||||
|
|
||||||
import { createApp, defineComponent, h } from "vue";
|
import { createApp, defineComponent, h } from "vue";
|
||||||
import App from "./App.vue";
|
import App from "./App.vue";
|
||||||
@ -10,12 +10,12 @@ import { i18n } from "./i18n";
|
|||||||
// Dependencies
|
// Dependencies
|
||||||
import "bootstrap";
|
import "bootstrap";
|
||||||
import Toast, { POSITION, useToast } from "vue-toastification";
|
import Toast, { POSITION, useToast } from "vue-toastification";
|
||||||
import "@xterm/xterm/lib/xterm.js";
|
import "xterm/lib/xterm.js";
|
||||||
|
|
||||||
// CSS
|
// CSS
|
||||||
import "@fontsource/jetbrains-mono";
|
import "@fontsource/jetbrains-mono";
|
||||||
import "vue-toastification/dist/index.css";
|
import "vue-toastification/dist/index.css";
|
||||||
import "@xterm/xterm/css/xterm.css";
|
import "xterm/css/xterm.css";
|
||||||
import "./styles/main.scss";
|
import "./styles/main.scss";
|
||||||
|
|
||||||
// Minxins
|
// Minxins
|
||||||
|
@ -2,8 +2,7 @@ import { io } from "socket.io-client";
|
|||||||
import { Socket } from "socket.io-client";
|
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";
|
||||||
import { AgentSocket } from "../../../common/agent-socket";
|
|
||||||
|
|
||||||
let socket : Socket;
|
let socket : Socket;
|
||||||
|
|
||||||
@ -29,51 +28,16 @@ export default defineComponent({
|
|||||||
loggedIn: false,
|
loggedIn: false,
|
||||||
allowLoginDialog: false,
|
allowLoginDialog: false,
|
||||||
username: null,
|
username: null,
|
||||||
composeTemplate: "",
|
|
||||||
|
|
||||||
stackList: {},
|
stackList: {},
|
||||||
|
composeTemplate: "",
|
||||||
// All stack list from all agents
|
|
||||||
allAgentStackList: {} as Record<string, object>,
|
|
||||||
|
|
||||||
// online / offline / connecting
|
|
||||||
agentStatusList: {
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
// Agent List
|
|
||||||
agentList: {
|
|
||||||
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
|
||||||
agentCount() {
|
|
||||||
return Object.keys(this.agentList).length;
|
|
||||||
},
|
|
||||||
|
|
||||||
completeStackList() {
|
|
||||||
let list : Record<string, object> = {};
|
|
||||||
|
|
||||||
for (let stackName in this.stackList) {
|
|
||||||
list[stackName + "_"] = this.stackList[stackName];
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let endpoint in this.allAgentStackList) {
|
|
||||||
let instance = this.allAgentStackList[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 "🐻";
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -101,15 +65,6 @@ export default defineComponent({
|
|||||||
|
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
|
|
||||||
"socketIO.connected"() {
|
|
||||||
if (this.socketIO.connected) {
|
|
||||||
this.agentStatusList[""] = "online";
|
|
||||||
} else {
|
|
||||||
this.agentStatusList[""] = "offline";
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
remember() {
|
remember() {
|
||||||
localStorage.remember = (this.remember) ? "1" : "0";
|
localStorage.remember = (this.remember) ? "1" : "0";
|
||||||
},
|
},
|
||||||
@ -129,15 +84,6 @@ export default defineComponent({
|
|||||||
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
|
||||||
endpointDisplayFunction(endpoint : string) {
|
|
||||||
if (endpoint) {
|
|
||||||
return endpoint;
|
|
||||||
} else {
|
|
||||||
return this.$t("currentEndpoint");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize connection to socket server
|
* Initialize connection to socket server
|
||||||
* @param bypass Should the check for if we
|
* @param bypass Should the check for if we
|
||||||
@ -162,12 +108,8 @@ export default defineComponent({
|
|||||||
this.socketIO.connecting = true;
|
this.socketIO.connecting = true;
|
||||||
}, 1500);
|
}, 1500);
|
||||||
|
|
||||||
socket = io(url);
|
socket = io(url, {
|
||||||
|
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", () => {
|
||||||
@ -235,7 +177,7 @@ export default defineComponent({
|
|||||||
this.$router.push("/setup");
|
this.$router.push("/setup");
|
||||||
});
|
});
|
||||||
|
|
||||||
agentSocket.on("terminalWrite", (terminalName, data) => {
|
socket.on("terminalWrite", (terminalName, data) => {
|
||||||
const terminal = terminalMap.get(terminalName);
|
const terminal = terminalMap.get(terminalName);
|
||||||
if (!terminal) {
|
if (!terminal) {
|
||||||
//console.error("Terminal not found: " + terminalName);
|
//console.error("Terminal not found: " + terminalName);
|
||||||
@ -244,18 +186,9 @@ export default defineComponent({
|
|||||||
terminal.write(data);
|
terminal.write(data);
|
||||||
});
|
});
|
||||||
|
|
||||||
agentSocket.on("stackList", (res) => {
|
socket.on("stackList", (res) => {
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
if (!res.endpoint) {
|
this.stackList = res.stackList;
|
||||||
this.stackList = res.stackList;
|
|
||||||
} else {
|
|
||||||
if (!this.allAgentStackList[res.endpoint]) {
|
|
||||||
this.allAgentStackList[res.endpoint] = {
|
|
||||||
stackList: {},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
this.allAgentStackList[res.endpoint].stackList = res.stackList;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -270,21 +203,6 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on("agentStatus", (res) => {
|
|
||||||
this.agentStatusList[res.endpoint] = res.status;
|
|
||||||
|
|
||||||
if (res.msg) {
|
|
||||||
this.toastError(res.msg);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
socket.on("agentList", (res) => {
|
|
||||||
console.log(res);
|
|
||||||
if (res.ok) {
|
|
||||||
this.agentList = res.agentList;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
socket.on("refresh", () => {
|
socket.on("refresh", () => {
|
||||||
location.reload();
|
location.reload();
|
||||||
});
|
});
|
||||||
@ -302,10 +220,6 @@ 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
|
||||||
@ -396,9 +310,9 @@ export default defineComponent({
|
|||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
bindTerminal(endpoint : string, terminalName : string, terminal : Terminal) {
|
bindTerminal(terminalName : string, terminal : Terminal) {
|
||||||
// Load terminal, get terminal screen
|
// Load terminal, get terminal screen
|
||||||
this.emitAgent(endpoint, "terminalJoin", terminalName, (res) => {
|
socket.emit("terminalJoin", terminalName, (res) => {
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
terminal.write(res.buffer);
|
terminal.write(res.buffer);
|
||||||
terminalMap.set(terminalName, terminal);
|
terminalMap.set(terminalName, terminal);
|
||||||
|
@ -2,12 +2,7 @@
|
|||||||
<transition name="slide-fade" appear>
|
<transition name="slide-fade" appear>
|
||||||
<div>
|
<div>
|
||||||
<h1 v-if="isAdd" class="mb-3">Compose</h1>
|
<h1 v-if="isAdd" class="mb-3">Compose</h1>
|
||||||
<h1 v-else class="mb-3">
|
<h1 v-else class="mb-3"><Uptime :stack="globalStack" :pill="true" /> {{ stack.name }}</h1>
|
||||||
<Uptime :stack="globalStack" :pill="true" /> {{ stack.name }}
|
|
||||||
<span v-if="$root.agentCount > 1" class="agent-name">
|
|
||||||
({{ endpointDisplay }})
|
|
||||||
</span>
|
|
||||||
</h1>
|
|
||||||
|
|
||||||
<div v-if="stack.isManagedByDockge" class="mb-3">
|
<div v-if="stack.isManagedByDockge" class="mb-3">
|
||||||
<div class="btn-group me-2" role="group">
|
<div class="btn-group me-2" role="group">
|
||||||
@ -75,7 +70,6 @@
|
|||||||
ref="progressTerminal"
|
ref="progressTerminal"
|
||||||
class="mb-3 terminal"
|
class="mb-3 terminal"
|
||||||
:name="terminalName"
|
:name="terminalName"
|
||||||
:endpoint="endpoint"
|
|
||||||
:rows="progressTerminalRows"
|
:rows="progressTerminalRows"
|
||||||
@has-data="showProgressTerminal = true; submitted = true;"
|
@has-data="showProgressTerminal = true; submitted = true;"
|
||||||
></Terminal>
|
></Terminal>
|
||||||
@ -93,16 +87,6 @@
|
|||||||
<input id="name" v-model="stack.name" type="text" class="form-control" required @blur="stackNameToLowercase">
|
<input id="name" v-model="stack.name" type="text" class="form-control" required @blur="stackNameToLowercase">
|
||||||
<div class="form-text">{{ $t("Lowercase only") }}</div>
|
<div class="form-text">{{ $t("Lowercase only") }}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Endpoint -->
|
|
||||||
<div class="mt-3">
|
|
||||||
<label for="name" class="form-label">{{ $t("dockgeAgent") }}</label>
|
|
||||||
<select v-model="stack.endpoint" class="form-select">
|
|
||||||
<option v-for="(agent, endpoint) in $root.agentList" :key="endpoint" :value="endpoint" :disabled="$root.agentStatusList[endpoint] != 'online'">
|
|
||||||
({{ $root.agentStatusList[endpoint] }}) {{ (endpoint) ? endpoint : $t("currentEndpoint") }}
|
|
||||||
</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -155,7 +139,6 @@
|
|||||||
ref="combinedTerminal"
|
ref="combinedTerminal"
|
||||||
class="mb-3 terminal"
|
class="mb-3 terminal"
|
||||||
:name="combinedTerminalName"
|
:name="combinedTerminalName"
|
||||||
:endpoint="endpoint"
|
|
||||||
:rows="combinedTerminalRows"
|
:rows="combinedTerminalRows"
|
||||||
:cols="combinedTerminalCols"
|
:cols="combinedTerminalCols"
|
||||||
style="height: 350px;"
|
style="height: 350px;"
|
||||||
@ -253,7 +236,7 @@ import {
|
|||||||
getComposeTerminalName,
|
getComposeTerminalName,
|
||||||
PROGRESS_TERMINAL_ROWS,
|
PROGRESS_TERMINAL_ROWS,
|
||||||
RUNNING
|
RUNNING
|
||||||
} from "../../../common/util-common";
|
} from "../../../backend/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";
|
||||||
@ -315,17 +298,13 @@ export default {
|
|||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
|
||||||
endpointDisplay() {
|
|
||||||
return this.$root.endpointDisplayFunction(this.endpoint);
|
|
||||||
},
|
|
||||||
|
|
||||||
urls() {
|
urls() {
|
||||||
if (!this.envsubstJSONConfig["x-dockge"] || !this.envsubstJSONConfig["x-dockge"].urls || !Array.isArray(this.envsubstJSONConfig["x-dockge"].urls)) {
|
if (!this.jsonConfig["x-dockge"] || !this.jsonConfig["x-dockge"].urls || !Array.isArray(this.jsonConfig["x-dockge"].urls)) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
let urls = [];
|
let urls = [];
|
||||||
for (const url of this.envsubstJSONConfig["x-dockge"].urls) {
|
for (const url of this.jsonConfig["x-dockge"].urls) {
|
||||||
let display;
|
let display;
|
||||||
try {
|
try {
|
||||||
let obj = new URL(url);
|
let obj = new URL(url);
|
||||||
@ -355,7 +334,7 @@ export default {
|
|||||||
* @return {*}
|
* @return {*}
|
||||||
*/
|
*/
|
||||||
globalStack() {
|
globalStack() {
|
||||||
return this.$root.completeStackList[this.stack.name + "_" + this.endpoint];
|
return this.$root.stackList[this.stack.name];
|
||||||
},
|
},
|
||||||
|
|
||||||
status() {
|
status() {
|
||||||
@ -370,31 +349,20 @@ export default {
|
|||||||
if (!this.stack.name) {
|
if (!this.stack.name) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
return getComposeTerminalName(this.endpoint, this.stack.name);
|
return getComposeTerminalName(this.stack.name);
|
||||||
},
|
},
|
||||||
|
|
||||||
combinedTerminalName() {
|
combinedTerminalName() {
|
||||||
if (!this.stack.name) {
|
if (!this.stack.name) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
return getCombinedTerminalName(this.endpoint, this.stack.name);
|
return getCombinedTerminalName(this.stack.name);
|
||||||
},
|
},
|
||||||
|
|
||||||
networks() {
|
networks() {
|
||||||
return this.jsonConfig.networks;
|
return this.jsonConfig.networks;
|
||||||
},
|
}
|
||||||
|
|
||||||
endpoint() {
|
|
||||||
return this.stack.endpoint || this.$route.params.endpoint || "";
|
|
||||||
},
|
|
||||||
|
|
||||||
url() {
|
|
||||||
if (this.stack.endpoint) {
|
|
||||||
return `/compose/${this.stack.name}/${this.stack.endpoint}`;
|
|
||||||
} else {
|
|
||||||
return `/compose/${this.stack.name}`;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
"stack.composeYAML": {
|
"stack.composeYAML": {
|
||||||
@ -437,7 +405,9 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
$route(to, from) {
|
$route(to, from) {
|
||||||
|
// Leave Combined Terminal
|
||||||
|
console.debug("leaveCombinedTerminal", from.params.stackName);
|
||||||
|
this.$root.getSocket().emit("leaveCombinedTerminal", this.stack.name, () => {});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
@ -467,7 +437,6 @@ export default {
|
|||||||
composeYAML,
|
composeYAML,
|
||||||
composeENV,
|
composeENV,
|
||||||
isManagedByDockge: true,
|
isManagedByDockge: true,
|
||||||
endpoint: "",
|
|
||||||
};
|
};
|
||||||
|
|
||||||
this.yamlCodeChange();
|
this.yamlCodeChange();
|
||||||
@ -480,9 +449,11 @@ export default {
|
|||||||
this.requestServiceStatus();
|
this.requestServiceStatus();
|
||||||
},
|
},
|
||||||
unmounted() {
|
unmounted() {
|
||||||
|
this.stopServiceStatusTimeout = true;
|
||||||
|
clearTimeout(serviceStatusTimeout);
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
|
||||||
startServiceStatusTimeout() {
|
startServiceStatusTimeout() {
|
||||||
clearTimeout(serviceStatusTimeout);
|
clearTimeout(serviceStatusTimeout);
|
||||||
serviceStatusTimeout = setTimeout(async () => {
|
serviceStatusTimeout = setTimeout(async () => {
|
||||||
@ -491,7 +462,7 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
requestServiceStatus() {
|
requestServiceStatus() {
|
||||||
this.$root.emitAgent(this.endpoint, "serviceStatusList", this.stack.name, (res) => {
|
this.$root.getSocket().emit("serviceStatusList", this.stack.name, (res) => {
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
this.serviceStatusList = res.serviceStatusList;
|
this.serviceStatusList = res.serviceStatusList;
|
||||||
}
|
}
|
||||||
@ -504,34 +475,22 @@ export default {
|
|||||||
exitConfirm(next) {
|
exitConfirm(next) {
|
||||||
if (this.isEditMode) {
|
if (this.isEditMode) {
|
||||||
if (confirm("You are currently editing a stack. Are you sure you want to leave?")) {
|
if (confirm("You are currently editing a stack. Are you sure you want to leave?")) {
|
||||||
this.exitAction();
|
|
||||||
next();
|
next();
|
||||||
} else {
|
} else {
|
||||||
next(false);
|
next(false);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.exitAction();
|
|
||||||
next();
|
next();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
exitAction() {
|
|
||||||
console.log("exitAction");
|
|
||||||
this.stopServiceStatusTimeout = true;
|
|
||||||
clearTimeout(serviceStatusTimeout);
|
|
||||||
|
|
||||||
// Leave Combined Terminal
|
|
||||||
console.debug("leaveCombinedTerminal", this.endpoint, this.stack.name);
|
|
||||||
this.$root.emitAgent(this.endpoint, "leaveCombinedTerminal", this.stack.name, () => {});
|
|
||||||
},
|
|
||||||
|
|
||||||
bindTerminal() {
|
bindTerminal() {
|
||||||
this.$refs.progressTerminal?.bind(this.endpoint, this.terminalName);
|
this.$refs.progressTerminal?.bind(this.terminalName);
|
||||||
},
|
},
|
||||||
|
|
||||||
loadStack() {
|
loadStack() {
|
||||||
this.processing = true;
|
this.processing = true;
|
||||||
this.$root.emitAgent(this.endpoint, "getStack", this.stack.name, (res) => {
|
this.$root.getSocket().emit("getStack", this.stack.name, (res) => {
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
this.stack = res.stack;
|
this.stack = res.stack;
|
||||||
this.yamlCodeChange();
|
this.yamlCodeChange();
|
||||||
@ -573,15 +532,15 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.bindTerminal();
|
this.bindTerminal(this.terminalName);
|
||||||
|
|
||||||
this.$root.emitAgent(this.stack.endpoint, "deployStack", this.stack.name, this.stack.composeYAML, this.stack.composeENV, this.isAdd, (res) => {
|
this.$root.getSocket().emit("deployStack", this.stack.name, this.stack.composeYAML, this.stack.composeENV, this.isAdd, (res) => {
|
||||||
this.processing = false;
|
this.processing = false;
|
||||||
this.$root.toastRes(res);
|
this.$root.toastRes(res);
|
||||||
|
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
this.isEditMode = false;
|
this.isEditMode = false;
|
||||||
this.$router.push(this.url);
|
this.$router.push("/compose/" + this.stack.name);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@ -589,13 +548,13 @@ export default {
|
|||||||
saveStack() {
|
saveStack() {
|
||||||
this.processing = true;
|
this.processing = true;
|
||||||
|
|
||||||
this.$root.emitAgent(this.stack.endpoint, "saveStack", this.stack.name, this.stack.composeYAML, this.stack.composeENV, this.isAdd, (res) => {
|
this.$root.getSocket().emit("saveStack", this.stack.name, this.stack.composeYAML, this.stack.composeENV, this.isAdd, (res) => {
|
||||||
this.processing = false;
|
this.processing = false;
|
||||||
this.$root.toastRes(res);
|
this.$root.toastRes(res);
|
||||||
|
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
this.isEditMode = false;
|
this.isEditMode = false;
|
||||||
this.$router.push(this.url);
|
this.$router.push("/compose/" + this.stack.name);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@ -603,7 +562,7 @@ export default {
|
|||||||
startStack() {
|
startStack() {
|
||||||
this.processing = true;
|
this.processing = true;
|
||||||
|
|
||||||
this.$root.emitAgent(this.endpoint, "startStack", this.stack.name, (res) => {
|
this.$root.getSocket().emit("startStack", this.stack.name, (res) => {
|
||||||
this.processing = false;
|
this.processing = false;
|
||||||
this.$root.toastRes(res);
|
this.$root.toastRes(res);
|
||||||
});
|
});
|
||||||
@ -612,7 +571,7 @@ export default {
|
|||||||
stopStack() {
|
stopStack() {
|
||||||
this.processing = true;
|
this.processing = true;
|
||||||
|
|
||||||
this.$root.emitAgent(this.endpoint, "stopStack", this.stack.name, (res) => {
|
this.$root.getSocket().emit("stopStack", this.stack.name, (res) => {
|
||||||
this.processing = false;
|
this.processing = false;
|
||||||
this.$root.toastRes(res);
|
this.$root.toastRes(res);
|
||||||
});
|
});
|
||||||
@ -621,7 +580,7 @@ export default {
|
|||||||
downStack() {
|
downStack() {
|
||||||
this.processing = true;
|
this.processing = true;
|
||||||
|
|
||||||
this.$root.emitAgent(this.endpoint, "downStack", this.stack.name, (res) => {
|
this.$root.getSocket().emit("downStack", this.stack.name, (res) => {
|
||||||
this.processing = false;
|
this.processing = false;
|
||||||
this.$root.toastRes(res);
|
this.$root.toastRes(res);
|
||||||
});
|
});
|
||||||
@ -630,7 +589,7 @@ export default {
|
|||||||
restartStack() {
|
restartStack() {
|
||||||
this.processing = true;
|
this.processing = true;
|
||||||
|
|
||||||
this.$root.emitAgent(this.endpoint, "restartStack", this.stack.name, (res) => {
|
this.$root.getSocket().emit("restartStack", this.stack.name, (res) => {
|
||||||
this.processing = false;
|
this.processing = false;
|
||||||
this.$root.toastRes(res);
|
this.$root.toastRes(res);
|
||||||
});
|
});
|
||||||
@ -639,14 +598,14 @@ export default {
|
|||||||
updateStack() {
|
updateStack() {
|
||||||
this.processing = true;
|
this.processing = true;
|
||||||
|
|
||||||
this.$root.emitAgent(this.endpoint, "updateStack", this.stack.name, (res) => {
|
this.$root.getSocket().emit("updateStack", this.stack.name, (res) => {
|
||||||
this.processing = false;
|
this.processing = false;
|
||||||
this.$root.toastRes(res);
|
this.$root.toastRes(res);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
deleteDialog() {
|
deleteDialog() {
|
||||||
this.$root.emitAgent(this.endpoint, "deleteStack", this.stack.name, (res) => {
|
this.$root.getSocket().emit("deleteStack", this.stack.name, (res) => {
|
||||||
this.$root.toastRes(res);
|
this.$root.toastRes(res);
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
this.$router.push("/");
|
this.$router.push("/");
|
||||||
@ -791,8 +750,6 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
@import "../styles/vars.scss";
|
|
||||||
|
|
||||||
.terminal {
|
.terminal {
|
||||||
height: 200px;
|
height: 200px;
|
||||||
}
|
}
|
||||||
@ -804,9 +761,4 @@ export default {
|
|||||||
background-color: #2c2f38 !important;
|
background-color: #2c2f38 !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.agent-name {
|
|
||||||
font-size: 13px;
|
|
||||||
color: $dark-font-color3;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
@ -15,14 +15,14 @@
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Terminal class="terminal" :rows="20" mode="mainTerminal" name="console" :endpoint="endpoint"></Terminal>
|
<Terminal class="terminal" :rows="20" mode="mainTerminal" name="console"></Terminal>
|
||||||
</div>
|
</div>
|
||||||
</transition>
|
</transition>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
import { allowedCommandList } from "../../../common/util-common";
|
import { allowedCommandList } from "../../../backend/util-common";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
@ -32,11 +32,6 @@ export default {
|
|||||||
allowedCommandList,
|
allowedCommandList,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
|
||||||
endpoint() {
|
|
||||||
return this.$route.params.endpoint || "";
|
|
||||||
},
|
|
||||||
},
|
|
||||||
mounted() {
|
mounted() {
|
||||||
|
|
||||||
},
|
},
|
||||||
|
@ -7,13 +7,13 @@
|
|||||||
<router-link :to="sh" class="btn btn-normal me-2">Switch to sh</router-link>
|
<router-link :to="sh" class="btn btn-normal me-2">Switch to sh</router-link>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Terminal class="terminal" :rows="20" mode="interactive" :name="terminalName" :stack-name="stackName" :service-name="serviceName" :shell="shell" :endpoint="endpoint"></Terminal>
|
<Terminal class="terminal" :rows="20" mode="interactive" :name="terminalName" :stack-name="stackName" :service-name="serviceName" :shell="shell"></Terminal>
|
||||||
</div>
|
</div>
|
||||||
</transition>
|
</transition>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { getContainerExecTerminalName } from "../../../common/util-common";
|
import { getContainerExecTerminalName } from "../../../backend/util-common";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
@ -27,9 +27,6 @@ export default {
|
|||||||
stackName() {
|
stackName() {
|
||||||
return this.$route.params.stackName;
|
return this.$route.params.stackName;
|
||||||
},
|
},
|
||||||
endpoint() {
|
|
||||||
return this.$route.params.endpoint || "";
|
|
||||||
},
|
|
||||||
shell() {
|
shell() {
|
||||||
return this.$route.params.type;
|
return this.$route.params.type;
|
||||||
},
|
},
|
||||||
@ -37,12 +34,10 @@ export default {
|
|||||||
return this.$route.params.serviceName;
|
return this.$route.params.serviceName;
|
||||||
},
|
},
|
||||||
terminalName() {
|
terminalName() {
|
||||||
return getContainerExecTerminalName(this.endpoint, this.stackName, this.serviceName, 0);
|
return getContainerExecTerminalName(this.stackName, this.serviceName, 0);
|
||||||
},
|
},
|
||||||
sh() {
|
sh() {
|
||||||
let endpoint = this.$route.params.endpoint;
|
return {
|
||||||
|
|
||||||
let data = {
|
|
||||||
name: "containerTerminal",
|
name: "containerTerminal",
|
||||||
params: {
|
params: {
|
||||||
stackName: this.stackName,
|
stackName: this.stackName,
|
||||||
@ -50,13 +45,6 @@ export default {
|
|||||||
type: "sh",
|
type: "sh",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
if (endpoint) {
|
|
||||||
data.name = "containerTerminalEndpoint";
|
|
||||||
data.params.endpoint = endpoint;
|
|
||||||
}
|
|
||||||
|
|
||||||
return data;
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
|
@ -5,97 +5,36 @@
|
|||||||
{{ $t("home") }}
|
{{ $t("home") }}
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<div class="row first-row">
|
<div class="shadow-box big-padding text-center mb-4">
|
||||||
<!-- Left -->
|
<div class="row">
|
||||||
<div class="col-md-7">
|
<div class="col">
|
||||||
<!-- Stats -->
|
<h3>{{ $t("active") }}</h3>
|
||||||
<div class="shadow-box big-padding text-center mb-4">
|
<span class="num active">{{ activeNum }}</span>
|
||||||
<div class="row">
|
|
||||||
<div class="col">
|
|
||||||
<h3>{{ $t("active") }}</h3>
|
|
||||||
<span class="num active">{{ activeNum }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="col">
|
|
||||||
<h3>{{ $t("exited") }}</h3>
|
|
||||||
<span class="num exited">{{ exitedNum }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="col">
|
|
||||||
<h3>{{ $t("inactive") }}</h3>
|
|
||||||
<span class="num inactive">{{ inactiveNum }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div class="col">
|
||||||
<!-- Docker Run -->
|
<h3>{{ $t("exited") }}</h3>
|
||||||
<h2 class="mb-3">{{ $t("Docker Run") }}</h2>
|
<span class="num exited">{{ exitedNum }}</span>
|
||||||
<div class="mb-3">
|
|
||||||
<textarea id="name" v-model="dockerRunCommand" type="text" class="form-control docker-run" required placeholder="docker run ..."></textarea>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div class="col">
|
||||||
<button class="btn-normal btn mb-4" @click="convertDockerRun">{{ $t("Convert to Compose") }}</button>
|
<h3>{{ $t("inactive") }}</h3>
|
||||||
</div>
|
<span class="num inactive">{{ inactiveNum }}</span>
|
||||||
<!-- Right -->
|
|
||||||
<div class="col-md-5">
|
|
||||||
<!-- Agent List -->
|
|
||||||
<div class="shadow-box big-padding">
|
|
||||||
<h4 class="mb-3">{{ $tc("dockgeAgent", 2) }} <span class="badge bg-warning" style="font-size: 12px;">beta</span></h4>
|
|
||||||
|
|
||||||
<div v-for="(agent, endpoint) in $root.agentList" :key="endpoint" class="mb-3 agent">
|
|
||||||
<!-- Agent Status -->
|
|
||||||
<template v-if="$root.agentStatusList[endpoint]">
|
|
||||||
<span v-if="$root.agentStatusList[endpoint] === 'online'" class="badge bg-primary me-2">{{ $t("agentOnline") }}</span>
|
|
||||||
<span v-else-if="$root.agentStatusList[endpoint] === 'offline'" class="badge bg-danger me-2">{{ $t("agentOffline") }}</span>
|
|
||||||
<span v-else class="badge bg-secondary me-2">{{ $t($root.agentStatusList[endpoint]) }}</span>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<!-- Agent Display Name -->
|
|
||||||
<span v-if="endpoint === ''">{{ $t("currentEndpoint") }}</span>
|
|
||||||
<a v-else :href="agent.url" target="_blank">{{ endpoint }}</a>
|
|
||||||
|
|
||||||
<!-- Remove Button -->
|
|
||||||
<font-awesome-icon v-if="endpoint !== ''" class="ms-2 remove-agent" icon="trash" @click="showRemoveAgentDialog[agent.url] = !showRemoveAgentDialog[agent.url]" />
|
|
||||||
|
|
||||||
<!-- Remoe Agent Dialog -->
|
|
||||||
<BModal v-model="showRemoveAgentDialog[agent.url]" :okTitle="$t('removeAgent')" okVariant="danger" @ok="removeAgent(agent.url)">
|
|
||||||
<p>{{ agent.url }}</p>
|
|
||||||
{{ $t("removeAgentMsg") }}
|
|
||||||
</BModal>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button v-if="!showAgentForm" class="btn btn-normal" @click="showAgentForm = !showAgentForm">{{ $t("addAgent") }}</button>
|
|
||||||
|
|
||||||
<!-- Add Agent Form -->
|
|
||||||
<form v-if="showAgentForm" @submit.prevent="addAgent">
|
|
||||||
<div class="mb-3">
|
|
||||||
<label for="url" class="form-label">{{ $t("dockgeURL") }}</label>
|
|
||||||
<input id="url" v-model="agent.url" type="url" class="form-control" required placeholder="http://">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mb-3">
|
|
||||||
<label for="username" class="form-label">{{ $t("Username") }}</label>
|
|
||||||
<input id="username" v-model="agent.username" type="text" class="form-control" required>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mb-3">
|
|
||||||
<label for="password" class="form-label">{{ $t("Password") }}</label>
|
|
||||||
<input id="password" v-model="agent.password" type="password" class="form-control" required autocomplete="new-password">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button type="submit" class="btn btn-primary" :disabled="connectingAgent">
|
|
||||||
<template v-if="connectingAgent">{{ $t("connecting") }}</template>
|
|
||||||
<template v-else>{{ $t("connect") }}</template>
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<h2 class="mb-3">{{ $t("Docker Run") }}</h2>
|
||||||
|
<div class="mb-3">
|
||||||
|
<textarea id="name" v-model="dockerRunCommand" type="text" class="form-control docker-run" required placeholder="docker run ..."></textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button class="btn-normal btn" @click="convertDockerRun">{{ $t("Convert to Compose") }}</button>
|
||||||
</div>
|
</div>
|
||||||
</transition>
|
</transition>
|
||||||
<router-view ref="child" />
|
<router-view ref="child" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { statusNameShort } from "../../../common/util-common";
|
import { statusNameShort } from "../../../backend/util-common";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
@ -119,14 +58,6 @@ export default {
|
|||||||
importantHeartBeatListLength: 0,
|
importantHeartBeatListLength: 0,
|
||||||
displayedRecords: [],
|
displayedRecords: [],
|
||||||
dockerRunCommand: "",
|
dockerRunCommand: "",
|
||||||
showAgentForm: false,
|
|
||||||
showRemoveAgentDialog: {},
|
|
||||||
connectingAgent: false,
|
|
||||||
agent: {
|
|
||||||
url: "http://",
|
|
||||||
username: "",
|
|
||||||
password: "",
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -167,43 +98,11 @@ export default {
|
|||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
|
|
||||||
addAgent() {
|
|
||||||
this.connectingAgent = true;
|
|
||||||
this.$root.getSocket().emit("addAgent", this.agent, (res) => {
|
|
||||||
this.$root.toastRes(res);
|
|
||||||
|
|
||||||
if (res.ok) {
|
|
||||||
this.showAgentForm = false;
|
|
||||||
this.agent = {
|
|
||||||
url: "http://",
|
|
||||||
username: "",
|
|
||||||
password: "",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
this.connectingAgent = false;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
removeAgent(url) {
|
|
||||||
this.$root.getSocket().emit("removeAgent", url, (res) => {
|
|
||||||
if (res.ok) {
|
|
||||||
this.$root.toastRes(res);
|
|
||||||
|
|
||||||
let urlObj = new URL(url);
|
|
||||||
let endpoint = urlObj.host;
|
|
||||||
|
|
||||||
// Remove the stack list and status list of the removed agent
|
|
||||||
delete this.$root.allAgentStackList[endpoint];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
getStatusNum(statusName) {
|
getStatusNum(statusName) {
|
||||||
let num = 0;
|
let num = 0;
|
||||||
|
|
||||||
for (let stackName in this.$root.completeStackList) {
|
for (let stackName in this.$root.stackList) {
|
||||||
const stack = this.$root.completeStackList[stackName];
|
const stack = this.$root.stackList[stackName];
|
||||||
if (statusNameShort(stack.status) === statusName) {
|
if (statusNameShort(stack.status) === statusName) {
|
||||||
num += 1;
|
num += 1;
|
||||||
}
|
}
|
||||||
@ -331,20 +230,4 @@ table {
|
|||||||
font-family: 'JetBrains Mono', monospace;
|
font-family: 'JetBrains Mono', monospace;
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.first-row .shadow-box {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
.remove-agent {
|
|
||||||
cursor: pointer;
|
|
||||||
color: rgba(255, 255, 255, 0.3);
|
|
||||||
}
|
|
||||||
|
|
||||||
.agent {
|
|
||||||
a {
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
@ -34,34 +34,23 @@ const routes = [
|
|||||||
path: "/compose",
|
path: "/compose",
|
||||||
component: Compose,
|
component: Compose,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
path: "/compose/:stackName/:endpoint",
|
|
||||||
component: Compose,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
path: "/compose/:stackName",
|
path: "/compose/:stackName",
|
||||||
|
name: "compose",
|
||||||
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",
|
|
||||||
},
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/console",
|
path: "/console",
|
||||||
component: Console,
|
component: Console,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
path: "/console/:endpoint",
|
|
||||||
component: Console,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
path: "/settings",
|
path: "/settings",
|
||||||
component: Settings,
|
component: Settings,
|
||||||
|
26
package.json
26
package.json
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "dockge",
|
"name": "dockge",
|
||||||
"version": "1.3.5",
|
"version": "1.3.2",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 18.0.0 && <= 18.17.1"
|
"node": ">= 18.0.0 && <= 18.17.1"
|
||||||
@ -10,15 +10,12 @@
|
|||||||
"lint": "eslint \"**/*.{ts,vue}\"",
|
"lint": "eslint \"**/*.{ts,vue}\"",
|
||||||
"check-ts": "tsc --noEmit",
|
"check-ts": "tsc --noEmit",
|
||||||
"start": "tsx ./backend/index.ts",
|
"start": "tsx ./backend/index.ts",
|
||||||
"dev": "concurrently -k -r \"wait-on tcp:5000 && pnpm run dev:backend \" \"pnpm run dev:frontend\"",
|
|
||||||
"dev:backend": "cross-env NODE_ENV=development tsx watch --inspect ./backend/index.ts",
|
"dev:backend": "cross-env NODE_ENV=development tsx watch --inspect ./backend/index.ts",
|
||||||
"dev:frontend": "cross-env NODE_ENV=development vite --host --config ./frontend/vite.config.ts",
|
"dev:frontend": "cross-env NODE_ENV=development vite --host --config ./frontend/vite.config.ts",
|
||||||
"release-final": "tsx ./extra/test-docker.ts && tsx extra/update-version.ts && pnpm run build:frontend && npm run build:docker",
|
"release-final": "tsx ./extra/test-docker.ts && tsx extra/update-version.ts && pnpm run build:frontend && npm run build:docker",
|
||||||
"release-beta": "tsx ./extra/test-docker.ts && tsx extra/update-version.ts && pnpm run build:frontend && npm run build:docker-beta",
|
|
||||||
"build:frontend": "vite build --config ./frontend/vite.config.ts",
|
"build:frontend": "vite build --config ./frontend/vite.config.ts",
|
||||||
"build:docker-base": "docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/dockge:base -f ./docker/Base.Dockerfile . --push",
|
"build:docker-base": "docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/dockge:base -f ./docker/Base.Dockerfile . --push",
|
||||||
"build:docker": "node ./extra/env2arg.js docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/dockge:latest -t louislam/dockge:1 -t louislam/dockge:$VERSION --target release -f ./docker/Dockerfile . --push",
|
"build:docker": "node ./extra/env2arg.js docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/dockge:latest -t louislam/dockge:1 -t louislam/dockge:$VERSION --target release -f ./docker/Dockerfile . --push",
|
||||||
"build:docker-beta": "node ./extra/env2arg.js docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/dockge:beta -t louislam/dockge:$VERSION --target release -f ./docker/Dockerfile . --push",
|
|
||||||
"build:docker-nightly": "pnpm run build:frontend && docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/dockge:nightly --target nightly -f ./docker/Dockerfile . --push",
|
"build:docker-nightly": "pnpm run build:frontend && docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/dockge:nightly --target nightly -f ./docker/Dockerfile . --push",
|
||||||
"build:healthcheck": "docker buildx build -f docker/BuildHealthCheck.Dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/dockge:build-healthcheck . --push",
|
"build:healthcheck": "docker buildx build -f docker/BuildHealthCheck.Dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/dockge:build-healthcheck . --push",
|
||||||
"start-docker": "docker run --rm -p 5001:5001 --name dockge louislam/dockge:latest",
|
"start-docker": "docker run --rm -p 5001:5001 --name dockge louislam/dockge:latest",
|
||||||
@ -27,7 +24,7 @@
|
|||||||
"reset-password": "tsx ./extra/reset-password.ts"
|
"reset-password": "tsx ./extra/reset-password.ts"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@homebridge/node-pty-prebuilt-multiarch": "~0.11.12",
|
"@homebridge/node-pty-prebuilt-multiarch": "~0.11.11",
|
||||||
"@inventage/envsubst": "^0.16.0",
|
"@inventage/envsubst": "^0.16.0",
|
||||||
"@louislam/sqlite3": "~15.1.6",
|
"@louislam/sqlite3": "~15.1.6",
|
||||||
"bcryptjs": "~2.4.3",
|
"bcryptjs": "~2.4.3",
|
||||||
@ -45,10 +42,9 @@
|
|||||||
"jwt-decode": "~3.1.2",
|
"jwt-decode": "~3.1.2",
|
||||||
"knex": "~2.5.1",
|
"knex": "~2.5.1",
|
||||||
"limiter-es6-compat": "~2.1.2",
|
"limiter-es6-compat": "~2.1.2",
|
||||||
"mysql2": "~3.6.5",
|
"mysql2": "~3.6.3",
|
||||||
"promisify-child-process": "~4.1.2",
|
"promisify-child-process": "~4.1.2",
|
||||||
"redbean-node": "~0.3.3",
|
"redbean-node": "~0.3.3",
|
||||||
"semver": "^7.5.4",
|
|
||||||
"socket.io": "~4.7.2",
|
"socket.io": "~4.7.2",
|
||||||
"socket.io-client": "~4.7.2",
|
"socket.io-client": "~4.7.2",
|
||||||
"timezones-list": "~3.0.2",
|
"timezones-list": "~3.0.2",
|
||||||
@ -59,25 +55,21 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@actions/github": "^6.0.0",
|
"@actions/github": "^6.0.0",
|
||||||
"@fontsource/jetbrains-mono": "^5.0.18",
|
"@fontsource/jetbrains-mono": "^5.0.17",
|
||||||
"@fortawesome/fontawesome-svg-core": "6.4.2",
|
"@fortawesome/fontawesome-svg-core": "6.4.2",
|
||||||
"@fortawesome/free-regular-svg-icons": "6.4.2",
|
"@fortawesome/free-regular-svg-icons": "6.4.2",
|
||||||
"@fortawesome/free-solid-svg-icons": "6.4.2",
|
"@fortawesome/free-solid-svg-icons": "6.4.2",
|
||||||
"@fortawesome/vue-fontawesome": "3.0.3",
|
"@fortawesome/vue-fontawesome": "3.0.3",
|
||||||
"@types/bcryptjs": "^2.4.6",
|
"@types/bcryptjs": "^2.4.6",
|
||||||
"@types/bootstrap": "~5.2.10",
|
"@types/bootstrap": "~5.2.9",
|
||||||
"@types/command-exists": "~1.2.3",
|
"@types/command-exists": "~1.2.3",
|
||||||
"@types/express": "~4.17.21",
|
"@types/express": "~4.17.21",
|
||||||
"@types/jsonwebtoken": "~9.0.5",
|
"@types/jsonwebtoken": "~9.0.5",
|
||||||
"@types/semver": "^7.5.6",
|
|
||||||
"@typescript-eslint/eslint-plugin": "~6.8.0",
|
"@typescript-eslint/eslint-plugin": "~6.8.0",
|
||||||
"@typescript-eslint/parser": "~6.8.0",
|
"@typescript-eslint/parser": "~6.8.0",
|
||||||
"@vitejs/plugin-vue": "~4.5.2",
|
"@vitejs/plugin-vue": "~4.5.0",
|
||||||
"@xterm/addon-fit": "beta",
|
|
||||||
"@xterm/xterm": "beta",
|
|
||||||
"bootstrap": "5.3.2",
|
"bootstrap": "5.3.2",
|
||||||
"bootstrap-vue-next": "~0.14.10",
|
"bootstrap-vue-next": "~0.14.10",
|
||||||
"concurrently": "^8.2.2",
|
|
||||||
"cross-env": "~7.0.3",
|
"cross-env": "~7.0.3",
|
||||||
"eslint": "~8.50.0",
|
"eslint": "~8.50.0",
|
||||||
"eslint-plugin-jsdoc": "~46.8.2",
|
"eslint-plugin-jsdoc": "~46.8.2",
|
||||||
@ -86,16 +78,16 @@
|
|||||||
"sass": "~1.68.0",
|
"sass": "~1.68.0",
|
||||||
"typescript": "~5.2.2",
|
"typescript": "~5.2.2",
|
||||||
"unplugin-vue-components": "~0.25.2",
|
"unplugin-vue-components": "~0.25.2",
|
||||||
"vite": "~5.0.10",
|
"vite": "~5.0.0",
|
||||||
"vite-plugin-compression": "~0.5.1",
|
"vite-plugin-compression": "~0.5.1",
|
||||||
"vue": "~3.3.13",
|
"vue": "~3.3.8",
|
||||||
"vue-eslint-parser": "~9.3.2",
|
"vue-eslint-parser": "~9.3.2",
|
||||||
"vue-i18n": "~9.5.0",
|
"vue-i18n": "~9.5.0",
|
||||||
"vue-prism-editor": "2.0.0-alpha.2",
|
"vue-prism-editor": "2.0.0-alpha.2",
|
||||||
"vue-qrcode": "~2.2.0",
|
"vue-qrcode": "~2.2.0",
|
||||||
"vue-router": "~4.2.5",
|
"vue-router": "~4.2.5",
|
||||||
"vue-toastification": "2.0.0-rc.5",
|
"vue-toastification": "2.0.0-rc.5",
|
||||||
"wait-on": "^7.2.0",
|
"xterm": "5.4.0-beta.37",
|
||||||
"xterm-addon-web-links": "~0.9.0"
|
"xterm-addon-web-links": "~0.9.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
939
pnpm-lock.yaml
generated
939
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@ -7,7 +7,6 @@
|
|||||||
"skipLibCheck": true
|
"skipLibCheck": true
|
||||||
},
|
},
|
||||||
"include": [
|
"include": [
|
||||||
"backend/**/*",
|
"backend/**/*"
|
||||||
"common/**/*"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user