mirror of
https://github.com/louislam/dockge.git
synced 2026-05-21 14:02:17 +00:00
Compare commits
27 Commits
fix-consol
...
codemirror
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f16b00908d | ||
|
|
3b9f0b9a4f | ||
|
|
ca9c8b4ba1 | ||
|
|
b02f5e092e | ||
|
|
078f762631 | ||
|
|
e589d4ec7e | ||
|
|
7e324d9015 | ||
|
|
72a941712d | ||
|
|
46ce4228a5 | ||
|
|
cc180562fc | ||
|
|
27bc4cd25c | ||
|
|
52605de5cd | ||
|
|
420c3af66d | ||
|
|
98cd537ba8 | ||
|
|
33fb84b4db | ||
|
|
f2575d5c05 | ||
|
|
65e2e26c43 | ||
|
|
cbb6b87a37 | ||
|
|
98cba39004 | ||
|
|
e31f766516 | ||
|
|
27bfe723d7 | ||
|
|
69818d665d | ||
|
|
bac498f97f | ||
|
|
3e37f38fc7 | ||
|
|
6dff52cc73 | ||
|
|
7fcc4c510c | ||
|
|
0ceb6336dd |
52
.github/workflows/nightly-release.yml
vendored
Normal file
52
.github/workflows/nightly-release.yml
vendored
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
name: Nightly Release
|
||||||
|
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
# Runs at 2:00 AM UTC every day
|
||||||
|
- cron: "0 2 * * *"
|
||||||
|
workflow_dispatch: # Allow manual trigger
|
||||||
|
|
||||||
|
permissions: {}
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
release-nightly:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 120
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
packages: write
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
persist-credentials: false
|
||||||
|
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v3
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
|
- name: Login to Docker Hub
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Login to GitHub Container Registry
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ghcr.io
|
||||||
|
username: ${{ github.repository_owner }}
|
||||||
|
password: ${{ secrets.GHCR_TOKEN }}
|
||||||
|
|
||||||
|
- name: Use Node.js 22
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: 22
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: npm clean-install --no-fund
|
||||||
|
|
||||||
|
- name: Run release-nightly
|
||||||
|
run: npm run release-nightly
|
||||||
45
README.md
45
README.md
@@ -81,9 +81,52 @@ curl "https://dockge.kuma.pet/compose.yaml?port=5001&stacksPath=/opt/stacks" --o
|
|||||||
- port=`5001`
|
- port=`5001`
|
||||||
- stacksPath=`/opt/stacks`
|
- stacksPath=`/opt/stacks`
|
||||||
|
|
||||||
|
Also, once compose is generated/downloaded, add the `PUID` and `PGID` section below to your compose `environment:` section to set stack ownership, otherwise default is `root`
|
||||||
|
|
||||||
|
```
|
||||||
|
# Both PUID and PGID must be set for it to do anything
|
||||||
|
- PUID=1000 # Set the stack file/dir ownership to this user
|
||||||
|
- PGID=1000 # Set the stack file/dir ownership to this group
|
||||||
|
```
|
||||||
|
|
||||||
Interactive compose.yaml generator is available on:
|
Interactive compose.yaml generator is available on:
|
||||||
https://dockge.kuma.pet
|
https://dockge.kuma.pet
|
||||||
|
|
||||||
|
### -OR-
|
||||||
|
Copy and paste your compose from the following:
|
||||||
|
|
||||||
|
If you want to store your stacks in another directory, you can change the `DOCKGE_STACKS_DIR` environment variable and volumes.
|
||||||
|
|
||||||
|
compose:
|
||||||
|
```
|
||||||
|
services:
|
||||||
|
dockge:
|
||||||
|
image: louislam/dockge:1
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
# Host Port:Container Port
|
||||||
|
- 5001:5001
|
||||||
|
volumes:
|
||||||
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
|
- ./data:/app/data
|
||||||
|
|
||||||
|
# If you want to use private registries, you need to share the auth file with Dockge:
|
||||||
|
# - /root/.docker/:/root/.docker
|
||||||
|
|
||||||
|
# Stacks Directory
|
||||||
|
# Your stacks directory in the host (The paths inside container must be the same as the host)
|
||||||
|
# ⚠️ If you did it wrong, your data could end up be written into a wrong path.
|
||||||
|
# ✔️ CORRECT EXAMPLE: - /my-stacks:/my-stacks (Both paths match)
|
||||||
|
# ❌ WRONG EXAMPLE: - /docker:/my-stacks (Both paths do not match)
|
||||||
|
- /opt/stacks:/opt/stacks
|
||||||
|
environment:
|
||||||
|
# Tell Dockge where your stacks directory is
|
||||||
|
- DOCKGE_STACKS_DIR=/opt/stacks
|
||||||
|
# Both PUID and PGID must be set for it to do anything
|
||||||
|
- PUID=1000 # Set the stack file/dir ownership to this user
|
||||||
|
- PGID=1000 # Set the stack file/dir ownership to this group
|
||||||
|
```
|
||||||
|
|
||||||
## How to Update
|
## How to Update
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -106,7 +149,7 @@ docker compose pull && docker compose up -d
|
|||||||
## Motivations
|
## Motivations
|
||||||
|
|
||||||
- I have been using Portainer for some time, but for the stack management, I am sometimes not satisfied with it. For example, sometimes when I try to deploy a stack, the loading icon keeps spinning for a few minutes without progress. And sometimes error messages are not clear.
|
- I have been using Portainer for some time, but for the stack management, I am sometimes not satisfied with it. For example, sometimes when I try to deploy a stack, the loading icon keeps spinning for a few minutes without progress. And sometimes error messages are not clear.
|
||||||
- Try to develop with ES Module + TypeScript (Originally, I planned to use Deno or Bun.js, but they don't have support for arm64, so I stepped back to Node.js)
|
- Try to develop with ES Module + TypeScript
|
||||||
|
|
||||||
If you love this project, please consider giving it a ⭐.
|
If you love this project, please consider giving it a ⭐.
|
||||||
|
|
||||||
|
|||||||
@@ -76,12 +76,14 @@ export class AgentManager {
|
|||||||
* @param url
|
* @param url
|
||||||
* @param username
|
* @param username
|
||||||
* @param password
|
* @param password
|
||||||
|
* @param name
|
||||||
*/
|
*/
|
||||||
async add(url : string, username : string, password : string) : Promise<Agent> {
|
async add(url: string, username: string, password: string, name: string): Promise<Agent> {
|
||||||
let bean = R.dispense("agent") as Agent;
|
let bean = R.dispense("agent") as Agent;
|
||||||
bean.url = url;
|
bean.url = url;
|
||||||
bean.username = username;
|
bean.username = username;
|
||||||
bean.password = password;
|
bean.password = password;
|
||||||
|
bean.name = name;
|
||||||
await R.store(bean);
|
await R.store(bean);
|
||||||
return bean;
|
return bean;
|
||||||
}
|
}
|
||||||
@@ -106,6 +108,23 @@ export class AgentManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param url
|
||||||
|
* @param updatedName
|
||||||
|
*/
|
||||||
|
async update(url: string, updatedName: string) {
|
||||||
|
const agent = await R.findOne("agent", " url = ? ", [
|
||||||
|
url,
|
||||||
|
]);
|
||||||
|
if (agent) {
|
||||||
|
agent.name = updatedName;
|
||||||
|
await R.store(agent);
|
||||||
|
} else {
|
||||||
|
throw new Error("Agent not found");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
connect(url : string, username : string, password : string) {
|
connect(url : string, username : string, password : string) {
|
||||||
let obj = new URL(url);
|
let obj = new URL(url);
|
||||||
let endpoint = obj.host;
|
let endpoint = obj.host;
|
||||||
@@ -278,6 +297,8 @@ export class AgentManager {
|
|||||||
url: "",
|
url: "",
|
||||||
username: "",
|
username: "",
|
||||||
endpoint: "",
|
endpoint: "",
|
||||||
|
name: "",
|
||||||
|
updatedName: "",
|
||||||
};
|
};
|
||||||
|
|
||||||
for (let endpoint in list) {
|
for (let endpoint in list) {
|
||||||
|
|||||||
@@ -147,6 +147,8 @@ export class DockerSocketHandler extends AgentSocketHandler {
|
|||||||
msgi18n: true,
|
msgi18n: true,
|
||||||
}, callback);
|
}, callback);
|
||||||
server.sendStackList();
|
server.sendStackList();
|
||||||
|
|
||||||
|
stack.leaveCombinedTerminal(socket);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
callbackError(e, callback);
|
callbackError(e, callback);
|
||||||
}
|
}
|
||||||
@@ -238,6 +240,84 @@ export class DockerSocketHandler extends AgentSocketHandler {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Docker stats
|
||||||
|
agentSocket.on("dockerStats", async (callback) => {
|
||||||
|
try {
|
||||||
|
checkLogin(socket);
|
||||||
|
|
||||||
|
const dockerStats = Object.fromEntries(await server.getDockerStats());
|
||||||
|
callbackResult({
|
||||||
|
ok: true,
|
||||||
|
dockerStats,
|
||||||
|
}, callback);
|
||||||
|
server.sendStackList();
|
||||||
|
} catch (e) {
|
||||||
|
callbackError(e, callback);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Start a service
|
||||||
|
agentSocket.on("startService", async (stackName: unknown, serviceName: unknown, callback) => {
|
||||||
|
try {
|
||||||
|
checkLogin(socket);
|
||||||
|
|
||||||
|
if (typeof (stackName) !== "string" || typeof (serviceName) !== "string") {
|
||||||
|
throw new ValidationError("Stack name and service name must be strings");
|
||||||
|
}
|
||||||
|
|
||||||
|
const stack = await Stack.getStack(server, stackName);
|
||||||
|
await stack.startService(socket, serviceName);
|
||||||
|
stack.joinCombinedTerminal(socket); // Ensure the combined terminal is joined
|
||||||
|
callbackResult({
|
||||||
|
ok: true,
|
||||||
|
msg: "Service " + serviceName + " started"
|
||||||
|
}, callback);
|
||||||
|
server.sendStackList();
|
||||||
|
} catch (e) {
|
||||||
|
callbackError(e, callback);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Stop a service
|
||||||
|
agentSocket.on("stopService", async (stackName: unknown, serviceName: unknown, callback) => {
|
||||||
|
try {
|
||||||
|
checkLogin(socket);
|
||||||
|
|
||||||
|
if (typeof (stackName) !== "string" || typeof (serviceName) !== "string") {
|
||||||
|
throw new ValidationError("Stack name and service name must be strings");
|
||||||
|
}
|
||||||
|
|
||||||
|
const stack = await Stack.getStack(server, stackName);
|
||||||
|
await stack.stopService(socket, serviceName);
|
||||||
|
callbackResult({
|
||||||
|
ok: true,
|
||||||
|
msg: "Service " + serviceName + " stopped"
|
||||||
|
}, callback);
|
||||||
|
server.sendStackList();
|
||||||
|
} catch (e) {
|
||||||
|
callbackError(e, callback);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
agentSocket.on("restartService", async (stackName: unknown, serviceName: unknown, callback) => {
|
||||||
|
try {
|
||||||
|
checkLogin(socket);
|
||||||
|
|
||||||
|
if (typeof stackName !== "string" || typeof serviceName !== "string") {
|
||||||
|
throw new Error("Invalid stackName or serviceName");
|
||||||
|
}
|
||||||
|
|
||||||
|
const stack = await Stack.getStack(server, stackName, true);
|
||||||
|
await stack.restartService(socket, serviceName);
|
||||||
|
callbackResult({
|
||||||
|
ok: true,
|
||||||
|
msg: "Service " + serviceName + " restarted"
|
||||||
|
}, callback);
|
||||||
|
} catch (e) {
|
||||||
|
callbackError(e, callback);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// getExternalNetworkList
|
// getExternalNetworkList
|
||||||
agentSocket.on("getDockerNetworkList", async (callback) => {
|
agentSocket.on("getDockerNetworkList", async (callback) => {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -38,6 +38,11 @@ export class TerminalSocketHandler extends AgentSocketHandler {
|
|||||||
try {
|
try {
|
||||||
checkLogin(socket);
|
checkLogin(socket);
|
||||||
|
|
||||||
|
// Throw an error if console is not enabled
|
||||||
|
if (!server.config.enableConsole) {
|
||||||
|
throw new ValidationError("Console is not enabled.");
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: Reset the name here, force one main terminal for now
|
// TODO: Reset the name here, force one main terminal for now
|
||||||
terminalName = "console";
|
terminalName = "console";
|
||||||
|
|
||||||
@@ -66,6 +71,18 @@ export class TerminalSocketHandler extends AgentSocketHandler {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Check if MainTerminal is enabled
|
||||||
|
agentSocket.on("checkMainTerminal", async (callback) => {
|
||||||
|
try {
|
||||||
|
checkLogin(socket);
|
||||||
|
callbackResult({
|
||||||
|
ok: server.config.enableConsole,
|
||||||
|
}, callback);
|
||||||
|
} catch (e) {
|
||||||
|
callbackError(e, callback);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Interactive Terminal for containers
|
// Interactive Terminal for containers
|
||||||
agentSocket.on("interactiveTerminal", async (stackName : unknown, serviceName : unknown, shell : unknown, callback) => {
|
agentSocket.on("interactiveTerminal", async (stackName : unknown, serviceName : unknown, shell : unknown, callback) => {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -136,6 +136,11 @@ export class DockgeServer {
|
|||||||
stacksDir: {
|
stacksDir: {
|
||||||
type: String,
|
type: String,
|
||||||
optional: true,
|
optional: true,
|
||||||
|
},
|
||||||
|
enableConsole: {
|
||||||
|
type: Boolean,
|
||||||
|
optional: true,
|
||||||
|
defaultValue: false,
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -149,6 +154,7 @@ export class DockgeServer {
|
|||||||
this.config.hostname = args.hostname || process.env.DOCKGE_HOSTNAME || undefined;
|
this.config.hostname = args.hostname || process.env.DOCKGE_HOSTNAME || undefined;
|
||||||
this.config.dataDir = args.dataDir || process.env.DOCKGE_DATA_DIR || "./data/";
|
this.config.dataDir = args.dataDir || process.env.DOCKGE_DATA_DIR || "./data/";
|
||||||
this.config.stacksDir = args.stacksDir || process.env.DOCKGE_STACKS_DIR || defaultStacksDir;
|
this.config.stacksDir = args.stacksDir || process.env.DOCKGE_STACKS_DIR || defaultStacksDir;
|
||||||
|
this.config.enableConsole = args.enableConsole || process.env.DOCKGE_ENABLE_CONSOLE === "true" || false;
|
||||||
this.stacksDir = this.config.stacksDir;
|
this.stacksDir = this.config.stacksDir;
|
||||||
|
|
||||||
log.debug("server", this.config);
|
log.debug("server", this.config);
|
||||||
@@ -631,6 +637,35 @@ export class DockgeServer {
|
|||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getDockerStats() : Promise<Map<string, object>> {
|
||||||
|
let stats = new Map<string, object>();
|
||||||
|
|
||||||
|
try {
|
||||||
|
let res = await childProcessAsync.spawn("docker", [ "stats", "--format", "json", "--no-stream" ], {
|
||||||
|
encoding: "utf-8",
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!res.stdout) {
|
||||||
|
return stats;
|
||||||
|
}
|
||||||
|
|
||||||
|
let lines = res.stdout?.toString().split("\n");
|
||||||
|
|
||||||
|
for (let line of lines) {
|
||||||
|
try {
|
||||||
|
let obj = JSON.parse(line);
|
||||||
|
stats.set(obj.Name, obj);
|
||||||
|
} catch (e) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return stats;
|
||||||
|
} catch (e) {
|
||||||
|
log.error("getDockerStats", e);
|
||||||
|
return stats;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
get stackDirFullPath() {
|
get stackDirFullPath() {
|
||||||
return path.resolve(this.stacksDir);
|
return path.resolve(this.stacksDir);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ export async function up(knex: Knex): Promise<void> {
|
|||||||
table.string("url", 255).notNullable().unique();
|
table.string("url", 255).notNullable().unique();
|
||||||
table.string("username", 255).notNullable();
|
table.string("username", 255).notNullable();
|
||||||
table.string("password", 255).notNullable();
|
table.string("password", 255).notNullable();
|
||||||
|
table.string("name", 255);
|
||||||
table.boolean("active").notNullable().defaultTo(true);
|
table.boolean("active").notNullable().defaultTo(true);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ export class Agent extends BeanModel {
|
|||||||
url: this.url,
|
url: this.url,
|
||||||
username: this.username,
|
username: this.username,
|
||||||
endpoint: this.endpoint,
|
endpoint: this.endpoint,
|
||||||
|
name: this.name,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ import {
|
|||||||
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";
|
||||||
|
import fs, { promises as fsAsync } from "fs";
|
||||||
|
import path from "path";
|
||||||
|
|
||||||
export class MainSocketHandler extends SocketHandler {
|
export class MainSocketHandler extends SocketHandler {
|
||||||
create(socket : DockgeSocket, server : DockgeServer) {
|
create(socket : DockgeSocket, server : DockgeServer) {
|
||||||
@@ -242,6 +244,12 @@ export class MainSocketHandler extends SocketHandler {
|
|||||||
checkLogin(socket);
|
checkLogin(socket);
|
||||||
const data = await Settings.getSettings("general");
|
const data = await Settings.getSettings("general");
|
||||||
|
|
||||||
|
if (fs.existsSync(path.join(server.stacksDir, "global.env"))) {
|
||||||
|
data.globalENV = fs.readFileSync(path.join(server.stacksDir, "global.env"), "utf-8");
|
||||||
|
} else {
|
||||||
|
data.globalENV = "# VARIABLE=value #comment";
|
||||||
|
}
|
||||||
|
|
||||||
callback({
|
callback({
|
||||||
ok: true,
|
ok: true,
|
||||||
data: data,
|
data: data,
|
||||||
@@ -270,6 +278,16 @@ export class MainSocketHandler extends SocketHandler {
|
|||||||
if (!currentDisabledAuth && data.disableAuth) {
|
if (!currentDisabledAuth && data.disableAuth) {
|
||||||
await doubleCheckPassword(socket, currentPassword);
|
await doubleCheckPassword(socket, currentPassword);
|
||||||
}
|
}
|
||||||
|
// Handle global.env
|
||||||
|
if (data.globalENV && data.globalENV != "# VARIABLE=value #comment") {
|
||||||
|
await fsAsync.writeFile(path.join(server.stacksDir, "global.env"), data.globalENV);
|
||||||
|
} else {
|
||||||
|
await fsAsync.rm(path.join(server.stacksDir, "global.env"), {
|
||||||
|
recursive: true,
|
||||||
|
force: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
delete data.globalENV;
|
||||||
|
|
||||||
await Settings.setSettings("general", data);
|
await Settings.setSettings("general", data);
|
||||||
|
|
||||||
@@ -311,7 +329,12 @@ export class MainSocketHandler extends SocketHandler {
|
|||||||
throw new ValidationError("dockerRunCommand must be a string");
|
throw new ValidationError("dockerRunCommand must be a string");
|
||||||
}
|
}
|
||||||
|
|
||||||
const composeTemplate = composerize(dockerRunCommand);
|
// Option: 'latest' | 'v2x' | 'v3x'
|
||||||
|
let composeTemplate = composerize(dockerRunCommand, "", "latest");
|
||||||
|
|
||||||
|
// Remove the first line "name: <your project name>"
|
||||||
|
composeTemplate = composeTemplate.split("\n").slice(1).join("\n");
|
||||||
|
|
||||||
callback({
|
callback({
|
||||||
ok: true,
|
ok: true,
|
||||||
composeTemplate,
|
composeTemplate,
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ export class ManageAgentSocketHandler extends SocketHandler {
|
|||||||
let data = requestData as LooseObject;
|
let data = requestData as LooseObject;
|
||||||
let manager = socket.instanceManager;
|
let manager = socket.instanceManager;
|
||||||
await manager.test(data.url, data.username, data.password);
|
await manager.test(data.url, data.username, data.password);
|
||||||
await manager.add(data.url, data.username, data.password);
|
await manager.add(data.url, data.username, data.password, data.name);
|
||||||
|
|
||||||
// connect to the agent
|
// connect to the agent
|
||||||
manager.connect(data.url, data.username, data.password);
|
manager.connect(data.url, data.username, data.password);
|
||||||
@@ -66,5 +66,27 @@ export class ManageAgentSocketHandler extends SocketHandler {
|
|||||||
callbackError(e, callback);
|
callbackError(e, callback);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// updateAgent
|
||||||
|
socket.on("updateAgent", async (name : string, updatedName : string, callback : unknown) => {
|
||||||
|
try {
|
||||||
|
log.debug("manage-agent-socket-handler", "updateAgent");
|
||||||
|
checkLogin(socket);
|
||||||
|
|
||||||
|
let manager = socket.instanceManager;
|
||||||
|
await manager.update(name, updatedName);
|
||||||
|
|
||||||
|
server.disconnectAllSocketClients(undefined, socket.id);
|
||||||
|
manager.sendAgentList();
|
||||||
|
|
||||||
|
callbackResult({
|
||||||
|
ok: true,
|
||||||
|
msg: "agentUpdatedSuccessfully",
|
||||||
|
msgi18n: true,
|
||||||
|
}, callback);
|
||||||
|
} catch (e) {
|
||||||
|
callbackError(e, callback);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ export class Stack {
|
|||||||
* Get the status of the stack from `docker compose ps --format json`
|
* Get the status of the stack from `docker compose ps --format json`
|
||||||
*/
|
*/
|
||||||
async ps() : Promise<object> {
|
async ps() : Promise<object> {
|
||||||
let res = await childProcessAsync.spawn("docker", [ "compose", "ps", "--format", "json" ], {
|
let res = await childProcessAsync.spawn("docker", this.getComposeOptions("ps", "--format", "json"), {
|
||||||
cwd: this.path,
|
cwd: this.path,
|
||||||
encoding: "utf-8",
|
encoding: "utf-8",
|
||||||
});
|
});
|
||||||
@@ -195,20 +195,18 @@ export class Stack {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Write or overwrite the compose.yaml
|
// Write or overwrite the compose.yaml
|
||||||
await fsAsync.writeFile(path.join(dir, this._composeFileName), this.composeYAML);
|
fs.writeFileSync(path.join(dir, this._composeFileName), this.composeYAML);
|
||||||
|
if (process.env.PUID && process.env.PGID) {
|
||||||
const envPath = path.join(dir, ".env");
|
const uid = Number(process.env.PUID);
|
||||||
|
const gid = Number(process.env.PGID);
|
||||||
// Write or overwrite the .env
|
fs.lchownSync(dir, uid, gid);
|
||||||
// If .env is not existing and the composeENV is empty, we don't need to write it
|
fs.chownSync(path.join(dir, this._composeFileName), uid, gid);
|
||||||
if (await fileExists(envPath) || this.composeENV.trim() !== "") {
|
|
||||||
await fsAsync.writeFile(envPath, this.composeENV);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async deploy(socket : DockgeSocket) : Promise<number> {
|
async deploy(socket : DockgeSocket) : Promise<number> {
|
||||||
const terminalName = getComposeTerminalName(socket.endpoint, this.name);
|
const terminalName = getComposeTerminalName(socket.endpoint, this.name);
|
||||||
let exitCode = await Terminal.exec(this.server, socket, terminalName, "docker", [ "compose", "up", "-d", "--remove-orphans" ], this.path);
|
let exitCode = await Terminal.exec(this.server, socket, terminalName, "docker", this.getComposeOptions("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.");
|
||||||
}
|
}
|
||||||
@@ -217,7 +215,7 @@ export class Stack {
|
|||||||
|
|
||||||
async delete(socket: DockgeSocket) : Promise<number> {
|
async delete(socket: DockgeSocket) : Promise<number> {
|
||||||
const terminalName = getComposeTerminalName(socket.endpoint, this.name);
|
const terminalName = getComposeTerminalName(socket.endpoint, this.name);
|
||||||
let exitCode = await Terminal.exec(this.server, socket, terminalName, "docker", [ "compose", "down", "--remove-orphans" ], this.path);
|
let exitCode = await Terminal.exec(this.server, socket, terminalName, "docker", this.getComposeOptions("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.");
|
||||||
}
|
}
|
||||||
@@ -407,9 +405,22 @@ export class Stack {
|
|||||||
return stack;
|
return stack;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getComposeOptions(command : string, ...extraOptions : string[]) {
|
||||||
|
//--env-file ./../global.env --env-file .env
|
||||||
|
let options = [ "compose", command, ...extraOptions ];
|
||||||
|
if (fs.existsSync(path.join(this.server.stacksDir, "global.env"))) {
|
||||||
|
if (fs.existsSync(path.join(this.path, ".env"))) {
|
||||||
|
options.splice(1, 0, "--env-file", "./.env");
|
||||||
|
}
|
||||||
|
options.splice(1, 0, "--env-file", "../global.env");
|
||||||
|
}
|
||||||
|
console.log(options);
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
|
||||||
async start(socket: DockgeSocket) {
|
async start(socket: DockgeSocket) {
|
||||||
const terminalName = getComposeTerminalName(socket.endpoint, this.name);
|
const terminalName = getComposeTerminalName(socket.endpoint, this.name);
|
||||||
let exitCode = await Terminal.exec(this.server, socket, terminalName, "docker", [ "compose", "up", "-d", "--remove-orphans" ], this.path);
|
let exitCode = await Terminal.exec(this.server, socket, terminalName, "docker", this.getComposeOptions("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.");
|
||||||
}
|
}
|
||||||
@@ -418,7 +429,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(socket.endpoint, this.name);
|
||||||
let exitCode = await Terminal.exec(this.server, socket, terminalName, "docker", [ "compose", "stop" ], this.path);
|
let exitCode = await Terminal.exec(this.server, socket, terminalName, "docker", this.getComposeOptions("stop"), this.path);
|
||||||
if (exitCode !== 0) {
|
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.");
|
||||||
}
|
}
|
||||||
@@ -427,7 +438,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(socket.endpoint, this.name);
|
||||||
let exitCode = await Terminal.exec(this.server, socket, terminalName, "docker", [ "compose", "restart" ], this.path);
|
let exitCode = await Terminal.exec(this.server, socket, terminalName, "docker", this.getComposeOptions("restart"), this.path);
|
||||||
if (exitCode !== 0) {
|
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.");
|
||||||
}
|
}
|
||||||
@@ -436,7 +447,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(socket.endpoint, this.name);
|
||||||
let exitCode = await Terminal.exec(this.server, socket, terminalName, "docker", [ "compose", "down" ], this.path);
|
let exitCode = await Terminal.exec(this.server, socket, terminalName, "docker", this.getComposeOptions("down"), this.path);
|
||||||
if (exitCode !== 0) {
|
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.");
|
||||||
}
|
}
|
||||||
@@ -445,7 +456,7 @@ export class Stack {
|
|||||||
|
|
||||||
async update(socket: DockgeSocket) {
|
async update(socket: DockgeSocket) {
|
||||||
const terminalName = getComposeTerminalName(socket.endpoint, this.name);
|
const terminalName = getComposeTerminalName(socket.endpoint, this.name);
|
||||||
let exitCode = await Terminal.exec(this.server, socket, terminalName, "docker", [ "compose", "pull" ], this.path);
|
let exitCode = await Terminal.exec(this.server, socket, terminalName, "docker", this.getComposeOptions("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.");
|
||||||
}
|
}
|
||||||
@@ -457,7 +468,7 @@ export class Stack {
|
|||||||
return exitCode;
|
return exitCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
exitCode = await Terminal.exec(this.server, socket, terminalName, "docker", [ "compose", "up", "-d", "--remove-orphans" ], this.path);
|
exitCode = await Terminal.exec(this.server, socket, terminalName, "docker", this.getComposeOptions("up", "-d", "--remove-orphans"), this.path);
|
||||||
if (exitCode !== 0) {
|
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.");
|
||||||
}
|
}
|
||||||
@@ -466,7 +477,7 @@ export class Stack {
|
|||||||
|
|
||||||
async joinCombinedTerminal(socket: DockgeSocket) {
|
async joinCombinedTerminal(socket: DockgeSocket) {
|
||||||
const terminalName = getCombinedTerminalName(socket.endpoint, this.name);
|
const terminalName = getCombinedTerminalName(socket.endpoint, this.name);
|
||||||
const terminal = Terminal.getOrCreateTerminal(this.server, terminalName, "docker", [ "compose", "logs", "-f", "--tail", "100" ], this.path);
|
const terminal = Terminal.getOrCreateTerminal(this.server, terminalName, "docker", this.getComposeOptions("logs", "-f", "--tail", "100"), this.path);
|
||||||
terminal.enableKeepAlive = true;
|
terminal.enableKeepAlive = true;
|
||||||
terminal.rows = COMBINED_TERMINAL_ROWS;
|
terminal.rows = COMBINED_TERMINAL_ROWS;
|
||||||
terminal.cols = COMBINED_TERMINAL_COLS;
|
terminal.cols = COMBINED_TERMINAL_COLS;
|
||||||
@@ -487,7 +498,7 @@ export class Stack {
|
|||||||
let terminal = Terminal.getTerminal(terminalName);
|
let terminal = Terminal.getTerminal(terminalName);
|
||||||
|
|
||||||
if (!terminal) {
|
if (!terminal) {
|
||||||
terminal = new InteractiveTerminal(this.server, terminalName, "docker", [ "compose", "exec", serviceName, shell ], this.path);
|
terminal = new InteractiveTerminal(this.server, terminalName, "docker", this.getComposeOptions("exec", serviceName, shell), this.path);
|
||||||
terminal.rows = TERMINAL_ROWS;
|
terminal.rows = TERMINAL_ROWS;
|
||||||
log.debug("joinContainerTerminal", "Terminal created");
|
log.debug("joinContainerTerminal", "Terminal created");
|
||||||
}
|
}
|
||||||
@@ -497,10 +508,10 @@ export class Stack {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async getServiceStatusList() {
|
async getServiceStatusList() {
|
||||||
let statusList = new Map<string, number>();
|
let statusList = new Map<string, Array<object>>();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let res = await childProcessAsync.spawn("docker", [ "compose", "ps", "--format", "json" ], {
|
let res = await childProcessAsync.spawn("docker", this.getComposeOptions("ps", "--format", "json"), {
|
||||||
cwd: this.path,
|
cwd: this.path,
|
||||||
encoding: "utf-8",
|
encoding: "utf-8",
|
||||||
});
|
});
|
||||||
@@ -511,13 +522,23 @@ export class Stack {
|
|||||||
|
|
||||||
let lines = res.stdout?.toString().split("\n");
|
let lines = res.stdout?.toString().split("\n");
|
||||||
|
|
||||||
|
const addLine = (obj: { Service: string, State: string, Name: string, Health: string }) => {
|
||||||
|
if (!statusList.has(obj.Service)) {
|
||||||
|
statusList.set(obj.Service, []);
|
||||||
|
}
|
||||||
|
statusList.get(obj.Service)?.push({
|
||||||
|
status: obj.Health || obj.State,
|
||||||
|
name: obj.Name
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
for (let line of lines) {
|
for (let line of lines) {
|
||||||
try {
|
try {
|
||||||
let obj = JSON.parse(line);
|
let obj = JSON.parse(line);
|
||||||
if (obj.Health === "") {
|
if (obj instanceof Array) {
|
||||||
statusList.set(obj.Service, obj.State);
|
obj.forEach(addLine);
|
||||||
} else {
|
} else {
|
||||||
statusList.set(obj.Service, obj.Health);
|
addLine(obj);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
}
|
}
|
||||||
@@ -528,6 +549,35 @@ export class Stack {
|
|||||||
log.error("getServiceStatusList", e);
|
log.error("getServiceStatusList", e);
|
||||||
return statusList;
|
return statusList;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async startService(socket: DockgeSocket, serviceName: string) {
|
||||||
|
const terminalName = getComposeTerminalName(socket.endpoint, this.name);
|
||||||
|
const exitCode = await Terminal.exec(this.server, socket, terminalName, "docker", ["compose", "up", "-d", serviceName], this.path);
|
||||||
|
if (exitCode !== 0) {
|
||||||
|
throw new Error(`Failed to start service ${serviceName}, please check logs for more information.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return exitCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
async stopService(socket: DockgeSocket, serviceName: string): Promise<number> {
|
||||||
|
const terminalName = getComposeTerminalName(socket.endpoint, this.name);
|
||||||
|
const exitCode = await Terminal.exec(this.server, socket, terminalName, "docker", ["compose", "stop", serviceName], this.path);
|
||||||
|
if (exitCode !== 0) {
|
||||||
|
throw new Error(`Failed to stop service ${serviceName}, please check logs for more information.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return exitCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
async restartService(socket: DockgeSocket, serviceName: string): Promise<number> {
|
||||||
|
const terminalName = getComposeTerminalName(socket.endpoint, this.name);
|
||||||
|
const exitCode = await Terminal.exec(this.server, socket, terminalName, "docker", ["compose", "restart", serviceName], this.path);
|
||||||
|
if (exitCode !== 0) {
|
||||||
|
throw new Error(`Failed to restart service ${serviceName}, please check logs for more information.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return exitCode;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import * as pty from "@homebridge/node-pty-prebuilt-multiarch";
|
|||||||
import { LimitQueue } from "./utils/limit-queue";
|
import { LimitQueue } from "./utils/limit-queue";
|
||||||
import { DockgeSocket } from "./util-server";
|
import { DockgeSocket } from "./util-server";
|
||||||
import {
|
import {
|
||||||
allowedCommandList, allowedRawKeys,
|
|
||||||
PROGRESS_TERMINAL_ROWS,
|
PROGRESS_TERMINAL_ROWS,
|
||||||
TERMINAL_COLS,
|
TERMINAL_COLS,
|
||||||
TERMINAL_ROWS
|
TERMINAL_ROWS
|
||||||
@@ -16,7 +15,6 @@ import { log } from "./log";
|
|||||||
* Terminal for running commands, no user interaction
|
* Terminal for running commands, no user interaction
|
||||||
*/
|
*/
|
||||||
export class Terminal {
|
export class Terminal {
|
||||||
|
|
||||||
protected static terminalMap : Map<string, Terminal> = new Map();
|
protected static terminalMap : Map<string, Terminal> = new Map();
|
||||||
|
|
||||||
protected _ptyProcess? : pty.IPty;
|
protected _ptyProcess? : pty.IPty;
|
||||||
@@ -272,6 +270,11 @@ export class MainTerminal extends InteractiveTerminal {
|
|||||||
constructor(server : DockgeServer, name : string) {
|
constructor(server : DockgeServer, name : string) {
|
||||||
let shell;
|
let shell;
|
||||||
|
|
||||||
|
// Throw an error if console is not enabled
|
||||||
|
if (!server.config.enableConsole) {
|
||||||
|
throw new Error("Console is not enabled.");
|
||||||
|
}
|
||||||
|
|
||||||
if (os.platform() === "win32") {
|
if (os.platform() === "win32") {
|
||||||
if (commandExistsSync("pwsh.exe")) {
|
if (commandExistsSync("pwsh.exe")) {
|
||||||
shell = "pwsh.exe";
|
shell = "pwsh.exe";
|
||||||
@@ -285,21 +288,6 @@ export class MainTerminal extends InteractiveTerminal {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public write(input : string) {
|
public write(input : string) {
|
||||||
// For like Ctrl + C
|
|
||||||
if (allowedRawKeys.includes(input)) {
|
|
||||||
super.write(input);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if the command is allowed
|
|
||||||
const cmdParts = input.split(" ");
|
|
||||||
const executable = cmdParts[0].trim();
|
|
||||||
log.debug("console", "Executable: " + executable);
|
|
||||||
log.debug("console", "Executable length: " + executable.length);
|
|
||||||
|
|
||||||
if (!allowedCommandList.includes(executable)) {
|
|
||||||
throw new Error("Command not allowed.");
|
|
||||||
}
|
|
||||||
super.write(input);
|
super.write(input);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ export interface Arguments {
|
|||||||
hostname? : string;
|
hostname? : string;
|
||||||
dataDir? : string;
|
dataDir? : string;
|
||||||
stacksDir? : string;
|
stacksDir? : string;
|
||||||
|
enableConsole? : boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Some config values are required
|
// Some config values are required
|
||||||
|
|||||||
@@ -107,17 +107,6 @@ export const COMBINED_TERMINAL_ROWS = 20;
|
|||||||
|
|
||||||
export const ERROR_TYPE_VALIDATION = 1;
|
export const ERROR_TYPE_VALIDATION = 1;
|
||||||
|
|
||||||
export const allowedCommandList : string[] = [
|
|
||||||
"docker",
|
|
||||||
"ls",
|
|
||||||
"cd",
|
|
||||||
"dir",
|
|
||||||
];
|
|
||||||
|
|
||||||
export const allowedRawKeys = [
|
|
||||||
"\u0003", // Ctrl + C
|
|
||||||
];
|
|
||||||
|
|
||||||
export const acceptedComposeFileNames = [
|
export const acceptedComposeFileNames = [
|
||||||
"compose.yaml",
|
"compose.yaml",
|
||||||
"docker-compose.yaml",
|
"docker-compose.yaml",
|
||||||
@@ -310,6 +299,7 @@ function copyYAMLCommentsItems(items: any, srcItems: any) {
|
|||||||
* - "8000-9000:80"
|
* - "8000-9000:80"
|
||||||
* - "127.0.0.1:8001:8001"
|
* - "127.0.0.1:8001:8001"
|
||||||
* - "127.0.0.1:5000-5010:5000-5010"
|
* - "127.0.0.1:5000-5010:5000-5010"
|
||||||
|
* - "0.0.0.0:8080->8080/tcp"
|
||||||
* - "6060:6060/udp"
|
* - "6060:6060/udp"
|
||||||
* @param input
|
* @param input
|
||||||
* @param hostname
|
* @param hostname
|
||||||
@@ -319,9 +309,19 @@ export function parseDockerPort(input : string, hostname : string) {
|
|||||||
let display;
|
let display;
|
||||||
|
|
||||||
const parts = input.split("/");
|
const parts = input.split("/");
|
||||||
const part1 = parts[0];
|
let part1 = parts[0];
|
||||||
let protocol = parts[1] || "tcp";
|
let protocol = parts[1] || "tcp";
|
||||||
|
|
||||||
|
// coming from docker ps, split host part
|
||||||
|
const arrow = part1.indexOf("->");
|
||||||
|
if (arrow >= 0) {
|
||||||
|
part1 = part1.split("->")[0];
|
||||||
|
const colon = part1.indexOf(":");
|
||||||
|
if (colon >= 0) {
|
||||||
|
part1 = part1.split(":")[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Split the last ":"
|
// Split the last ":"
|
||||||
const lastColon = part1.lastIndexOf(":");
|
const lastColon = part1.lastIndexOf(":");
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ FROM louislam/dockge:base AS build
|
|||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY --chown=node:node ./package.json ./package.json
|
COPY --chown=node:node ./package.json ./package.json
|
||||||
COPY --chown=node:node ./package-lock.json ./package-lock.json
|
COPY --chown=node:node ./package-lock.json ./package-lock.json
|
||||||
RUN npm ci
|
RUN npm ci --omit=dev
|
||||||
|
|
||||||
############################################
|
############################################
|
||||||
# ⭐ Main Image
|
# ⭐ Main Image
|
||||||
|
|||||||
8
frontend/components.d.ts
vendored
8
frontend/components.d.ts
vendored
@@ -11,12 +11,17 @@ declare module 'vue' {
|
|||||||
Appearance: typeof import('./src/components/settings/Appearance.vue')['default']
|
Appearance: typeof import('./src/components/settings/Appearance.vue')['default']
|
||||||
ArrayInput: typeof import('./src/components/ArrayInput.vue')['default']
|
ArrayInput: typeof import('./src/components/ArrayInput.vue')['default']
|
||||||
ArraySelect: typeof import('./src/components/ArraySelect.vue')['default']
|
ArraySelect: typeof import('./src/components/ArraySelect.vue')['default']
|
||||||
|
BButton: typeof import('bootstrap-vue-next')['BButton']
|
||||||
BDropdown: typeof import('bootstrap-vue-next')['BDropdown']
|
BDropdown: typeof import('bootstrap-vue-next')['BDropdown']
|
||||||
BDropdownItem: typeof import('bootstrap-vue-next')['BDropdownItem']
|
BDropdownItem: typeof import('bootstrap-vue-next')['BDropdownItem']
|
||||||
|
BFormGroup: typeof import('bootstrap-vue-next')['BFormGroup']
|
||||||
|
BFormInput: typeof import('bootstrap-vue-next')['BFormInput']
|
||||||
BModal: typeof import('bootstrap-vue-next')['BModal']
|
BModal: typeof import('bootstrap-vue-next')['BModal']
|
||||||
Confirm: typeof import('./src/components/Confirm.vue')['default']
|
Confirm: typeof import('./src/components/Confirm.vue')['default']
|
||||||
Container: typeof import('./src/components/Container.vue')['default']
|
Container: typeof import('./src/components/Container.vue')['default']
|
||||||
|
DockerStat: typeof import('./src/components/DockerStat.vue')['default']
|
||||||
General: typeof import('./src/components/settings/General.vue')['default']
|
General: typeof import('./src/components/settings/General.vue')['default']
|
||||||
|
GlobalEnv: typeof import('./src/components/settings/GlobalEnv.vue')['default']
|
||||||
HiddenInput: typeof import('./src/components/HiddenInput.vue')['default']
|
HiddenInput: typeof import('./src/components/HiddenInput.vue')['default']
|
||||||
Login: typeof import('./src/components/Login.vue')['default']
|
Login: typeof import('./src/components/Login.vue')['default']
|
||||||
NetworkInput: typeof import('./src/components/NetworkInput.vue')['default']
|
NetworkInput: typeof import('./src/components/NetworkInput.vue')['default']
|
||||||
@@ -29,4 +34,7 @@ declare module 'vue' {
|
|||||||
TwoFADialog: typeof import('./src/components/TwoFADialog.vue')['default']
|
TwoFADialog: typeof import('./src/components/TwoFADialog.vue')['default']
|
||||||
Uptime: typeof import('./src/components/Uptime.vue')['default']
|
Uptime: typeof import('./src/components/Uptime.vue')['default']
|
||||||
}
|
}
|
||||||
|
export interface ComponentCustomProperties {
|
||||||
|
vBModal: typeof import('bootstrap-vue-next')['vBModal']
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="shadow-box big-padding mb-3 container">
|
<div class="shadow-box big-padding mb-3 container">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-7">
|
<div class="col-5">
|
||||||
<h4>{{ name }}</h4>
|
<h4>{{ name }}</h4>
|
||||||
<div class="image mb-2">
|
<div class="image mb-2">
|
||||||
<span class="me-1">{{ imageName }}:</span><span class="tag">{{ imageTag }}</span>
|
<span class="me-1">{{ imageName }}:</span><span class="tag">{{ imageTag }}</span>
|
||||||
@@ -9,17 +9,40 @@
|
|||||||
<div v-if="!isEditMode">
|
<div v-if="!isEditMode">
|
||||||
<span class="badge me-1" :class="bgStyle">{{ status }}</span>
|
<span class="badge me-1" :class="bgStyle">{{ status }}</span>
|
||||||
|
|
||||||
<a v-for="port in envsubstService.ports" :key="port" :href="parsePort(port).url" target="_blank">
|
<a v-for="port in (ports ?? envsubstService.ports)" :key="port" :href="parsePort(port).url" target="_blank">
|
||||||
<span class="badge me-1 bg-secondary">{{ parsePort(port).display }}</span>
|
<span class="badge me-1 bg-secondary">{{ parsePort(port).display }}</span>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-5">
|
<div class="col-7">
|
||||||
<div class="function">
|
<div class="function">
|
||||||
<router-link v-if="!isEditMode" class="btn btn-normal" :to="terminalRouteLink" disabled="">
|
<div class="btn-group me-2" role="group">
|
||||||
<font-awesome-icon icon="terminal" />
|
<router-link v-if="!isEditMode && (status === 'running' || status === 'healthy')" class="btn btn-normal" :to="terminalRouteLink" disabled="">
|
||||||
Bash
|
<font-awesome-icon icon="terminal" />
|
||||||
</router-link>
|
Bash
|
||||||
|
</router-link>
|
||||||
|
<button v-if="this.serviceCount > 1 && !isEditMode && status !== 'running' && status !== 'healthy'"
|
||||||
|
class="btn btn-primary"
|
||||||
|
:disabled="processing"
|
||||||
|
@click="startService">
|
||||||
|
<font-awesome-icon icon="play" class="me-1" />
|
||||||
|
{{ $t("startStack") }}
|
||||||
|
</button>
|
||||||
|
<button v-if="this.serviceCount > 1 && !isEditMode && (status === 'running' || status === 'healthy' || status === 'unhealthy')"
|
||||||
|
class="btn btn-normal"
|
||||||
|
:disabled="processing"
|
||||||
|
@click="restartService">
|
||||||
|
<font-awesome-icon icon="rotate" class="me-1" />
|
||||||
|
{{ $t("restartStack") }}
|
||||||
|
</button>
|
||||||
|
<button v-if="this.serviceCount > 1 && !isEditMode && (status === 'running' || status === 'healthy' || status === 'unhealthy')"
|
||||||
|
class="btn btn-normal"
|
||||||
|
:disabled="processing"
|
||||||
|
@click="stopService">
|
||||||
|
<font-awesome-icon icon="stop" class="me-1" />
|
||||||
|
{{ $t("stopStack") }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -35,6 +58,32 @@
|
|||||||
{{ $t("deleteContainer") }}
|
{{ $t("deleteContainer") }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
<div v-else-if="statsInstances.length > 0" class="mt-2">
|
||||||
|
<div class="d-flex align-items-center gap-3">
|
||||||
|
<template v-if="!expandedStats">
|
||||||
|
<div class="stats">
|
||||||
|
{{ $t('CPU') }}: {{ statsInstances[0].CPUPerc }}
|
||||||
|
</div>
|
||||||
|
<div class="stats">
|
||||||
|
{{ $t('memoryAbbreviated') }}: {{ statsInstances[0].MemUsage }}
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<div class="d-flex flex-grow-1 justify-content-end">
|
||||||
|
<button class="btn btn-sm btn-normal" @click="expandedStats = !expandedStats">
|
||||||
|
<font-awesome-icon :icon="expandedStats ? 'chevron-up' : 'chevron-down'" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<transition name="slide-fade" appear>
|
||||||
|
<div v-if="expandedStats" class="d-flex flex-column gap-3 mt-2">
|
||||||
|
<DockerStat
|
||||||
|
v-for="stat in statsInstances"
|
||||||
|
:key="stat.Name"
|
||||||
|
:stat="stat"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</transition>
|
||||||
|
</div>
|
||||||
|
|
||||||
<transition name="slide-fade" appear>
|
<transition name="slide-fade" appear>
|
||||||
<div v-if="isEditMode && showConfig" class="config mt-3">
|
<div v-if="isEditMode && showConfig" class="config mt-3">
|
||||||
@@ -138,10 +187,12 @@
|
|||||||
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 "../../../common/util-common";
|
||||||
|
import DockerStat from "./DockerStat.vue";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: {
|
components: {
|
||||||
FontAwesomeIcon,
|
FontAwesomeIcon,
|
||||||
|
DockerStat
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
name: {
|
name: {
|
||||||
@@ -156,16 +207,24 @@ export default defineComponent({
|
|||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
status: {
|
serviceStatus: {
|
||||||
type: String,
|
type: Object,
|
||||||
default: "N/A",
|
default: null,
|
||||||
|
},
|
||||||
|
dockerStats: {
|
||||||
|
type: Object,
|
||||||
|
default: null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
emits: [
|
emits: [
|
||||||
|
"start-service",
|
||||||
|
"stop-service",
|
||||||
|
"restart-service"
|
||||||
],
|
],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
showConfig: false,
|
showConfig: false,
|
||||||
|
expandedStats: false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@@ -230,6 +289,10 @@ export default defineComponent({
|
|||||||
return this.jsonObject.services[this.name];
|
return this.jsonObject.services[this.name];
|
||||||
},
|
},
|
||||||
|
|
||||||
|
serviceCount() {
|
||||||
|
return Object.keys(this.jsonObject.services).length;
|
||||||
|
},
|
||||||
|
|
||||||
jsonObject() {
|
jsonObject() {
|
||||||
return this.$parent.$parent.jsonConfig;
|
return this.$parent.$parent.jsonConfig;
|
||||||
},
|
},
|
||||||
@@ -266,6 +329,22 @@ export default defineComponent({
|
|||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
statsInstances() {
|
||||||
|
if (!this.serviceStatus) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.serviceStatus
|
||||||
|
.map(s => this.dockerStats[s.name])
|
||||||
|
.filter(s => !!s)
|
||||||
|
.sort((a, b) => a.Name.localeCompare(b.Name));
|
||||||
|
},
|
||||||
|
status() {
|
||||||
|
if (!this.serviceStatus) {
|
||||||
|
return "N/A";
|
||||||
|
}
|
||||||
|
return this.serviceStatus[0].status;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
if (this.first) {
|
if (this.first) {
|
||||||
@@ -284,6 +363,16 @@ export default defineComponent({
|
|||||||
remove() {
|
remove() {
|
||||||
delete this.jsonObject.services[this.name];
|
delete this.jsonObject.services[this.name];
|
||||||
},
|
},
|
||||||
|
startService() {
|
||||||
|
this.$emit("start-service", this.name);
|
||||||
|
},
|
||||||
|
stopService() {
|
||||||
|
this.$emit("stop-service", this.name);
|
||||||
|
},
|
||||||
|
restartService() {
|
||||||
|
this.$emit("restart-service", this.name);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
@@ -308,5 +397,10 @@ export default defineComponent({
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: end;
|
justify-content: end;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.stats {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
color: #6c757d;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
94
frontend/src/components/DockerStat.vue
Normal file
94
frontend/src/components/DockerStat.vue
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
<template>
|
||||||
|
<div class="stats-container">
|
||||||
|
<div class="stats-title">
|
||||||
|
{{ stat.Name }}
|
||||||
|
</div>
|
||||||
|
<div class="d-flex justify-content-between stats gap-2 mt-1">
|
||||||
|
<div class="stat">
|
||||||
|
<div class="stat-label">
|
||||||
|
{{ $t('CPU') }}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{{ stat.CPUPerc }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat">
|
||||||
|
<div class="stat-label">
|
||||||
|
{{ $t('memory') }}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{{ stat.MemUsage }} ({{ stat.MemPerc }})
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat">
|
||||||
|
<div class="stat-label">
|
||||||
|
{{ $t('networkIO') }}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{{ stat.NetIO }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat">
|
||||||
|
<div class="stat-label">
|
||||||
|
{{ $t('blockIO') }}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{{ stat.BlockIO }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
stat: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.stats-container {
|
||||||
|
container-type: inline-size;
|
||||||
|
|
||||||
|
.stats {
|
||||||
|
container-type: inline-size;
|
||||||
|
|
||||||
|
.stat {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@container (width < 420px) {
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
.stat {
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-label::after {
|
||||||
|
content: ':'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.stats {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
color: #6c757d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-label {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stats-title {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
color: var(--bs-heading-color);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -3,7 +3,8 @@
|
|||||||
<div class="list-header">
|
<div class="list-header">
|
||||||
<div class="header-top">
|
<div class="header-top">
|
||||||
<!-- TODO -->
|
<!-- TODO -->
|
||||||
<button v-if="false" class="btn btn-outline-normal ms-2" :class="{ 'active': selectMode }" type="button" @click="selectMode = !selectMode">
|
<button v-if="false" class="btn btn-outline-normal ms-2" :class="{ 'active': selectMode }" type="button"
|
||||||
|
@click="selectMode = !selectMode">
|
||||||
{{ $t("Select") }}
|
{{ $t("Select") }}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
@@ -28,34 +29,36 @@
|
|||||||
|
|
||||||
<!-- TODO: Selection Controls -->
|
<!-- TODO: Selection Controls -->
|
||||||
<div v-if="selectMode && false" class="selection-controls px-2 pt-2">
|
<div v-if="selectMode && false" class="selection-controls px-2 pt-2">
|
||||||
<input
|
<input v-model="selectAll" class="form-check-input select-input" type="checkbox" />
|
||||||
v-model="selectAll"
|
|
||||||
class="form-check-input select-input"
|
|
||||||
type="checkbox"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<button class="btn-outline-normal" @click="pauseDialog"><font-awesome-icon icon="pause" size="sm" /> {{ $t("Pause") }}</button>
|
<button class="btn-outline-normal" @click="pauseDialog"><font-awesome-icon icon="pause" size="sm" /> {{
|
||||||
<button class="btn-outline-normal" @click="resumeSelected"><font-awesome-icon icon="play" size="sm" /> {{ $t("Resume") }}</button>
|
$t("Pause") }}</button>
|
||||||
|
<button class="btn-outline-normal" @click="resumeSelected"><font-awesome-icon icon="play" size="sm" />
|
||||||
|
{{ $t("Resume") }}</button>
|
||||||
|
|
||||||
<span v-if="selectedStackCount > 0">
|
<span v-if="selectedStackCount > 0">
|
||||||
{{ $t("selectedStackCount", [ selectedStackCount ]) }}
|
{{ $t("selectedStackCount", [selectedStackCount]) }}
|
||||||
</span>
|
</span>
|
||||||
</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="agentStackList[0] && agentStackList[0].stacks.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>
|
||||||
|
<div class="stack-list-inner" v-for="(agent, index) in agentStackList" :key="index">
|
||||||
<StackListItem
|
<div v-if="$root.agentCount > 1" class="p-2 agent-select"
|
||||||
v-for="(item, index) in sortedStackList"
|
@click="closedAgents.set(agent.endpoint, !closedAgents.get(agent.endpoint))">
|
||||||
:key="index"
|
<span class="me-1">
|
||||||
:stack="item"
|
<font-awesome-icon v-show="closedAgents.get(agent.endpoint)" icon="chevron-circle-right" />
|
||||||
:isSelectMode="selectMode"
|
<font-awesome-icon v-show="!closedAgents.get(agent.endpoint)" icon="chevron-circle-down" />
|
||||||
:isSelected="isSelected"
|
</span>
|
||||||
:select="select"
|
<span v-if="agent.endpoint === 'current'">{{ $t("currentEndpoint") }}</span>
|
||||||
:deselect="deselect"
|
<span v-else>{{ agent.endpoint }}</span>
|
||||||
/>
|
</div>
|
||||||
|
<StackListItem v-show="$root.agentCount === 1 || !closedAgents.get(agent.endpoint)"
|
||||||
|
v-for="(item, index) in agent.stacks" :key="index" :stack="item" :isSelectMode="selectMode"
|
||||||
|
:isSelected="isSelected" :select="select" :deselect="deselect" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -92,7 +95,8 @@ export default {
|
|||||||
status: null,
|
status: null,
|
||||||
active: null,
|
active: null,
|
||||||
tags: null,
|
tags: null,
|
||||||
}
|
},
|
||||||
|
closedAgents: new Map(),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@@ -119,7 +123,7 @@ export default {
|
|||||||
* Returns a sorted list of stacks based on the applied filters and search text.
|
* Returns a sorted list of stacks based on the applied filters and search text.
|
||||||
* @returns {Array} The sorted list of stacks.
|
* @returns {Array} The sorted list of stacks.
|
||||||
*/
|
*/
|
||||||
sortedStackList() {
|
agentStackList() {
|
||||||
let result = Object.values(this.$root.completeStackList);
|
let result = Object.values(this.$root.completeStackList);
|
||||||
|
|
||||||
result = result.filter(stack => {
|
result = result.filter(stack => {
|
||||||
@@ -187,6 +191,29 @@ export default {
|
|||||||
return m1.name.localeCompare(m2.name);
|
return m1.name.localeCompare(m2.name);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Group stacks by endpoint, sorting them so the local endpoint is first
|
||||||
|
// and the rest are sorted alphabetically
|
||||||
|
result = [
|
||||||
|
...result.reduce((acc, stack) => {
|
||||||
|
const endpoint = stack.endpoint || 'current';
|
||||||
|
if (!acc.has(endpoint)) {
|
||||||
|
acc.set(endpoint, []);
|
||||||
|
}
|
||||||
|
acc.get(endpoint).push(stack);
|
||||||
|
return acc;
|
||||||
|
}, new Map()).entries()
|
||||||
|
].map(([endpoint, stacks]) => ({
|
||||||
|
endpoint,
|
||||||
|
stacks
|
||||||
|
})).sort((a, b) => {
|
||||||
|
if (a.endpoint === 'current' && b.endpoint !== 'current') {
|
||||||
|
return -1;
|
||||||
|
} else if (a.endpoint !== 'current' && b.endpoint === 'current') {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return a.endpoint.localeCompare(b.endpoint);
|
||||||
|
});
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -221,7 +248,7 @@ export default {
|
|||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
searchText() {
|
searchText() {
|
||||||
for (let stack of this.sortedStackList) {
|
for (let stack of this.agentStackList) {
|
||||||
if (!this.selectedStacks[stack.id]) {
|
if (!this.selectedStacks[stack.id]) {
|
||||||
if (this.selectAll) {
|
if (this.selectAll) {
|
||||||
this.disableSelectAllWatcher = true;
|
this.disableSelectAllWatcher = true;
|
||||||
@@ -236,7 +263,7 @@ export default {
|
|||||||
this.selectedStacks = {};
|
this.selectedStacks = {};
|
||||||
|
|
||||||
if (this.selectAll) {
|
if (this.selectAll) {
|
||||||
this.sortedStackList.forEach((item) => {
|
this.agentStackList.forEach((item) => {
|
||||||
this.selectedStacks[item.id] = true;
|
this.selectedStacks[item.id] = true;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -331,7 +358,7 @@ export default {
|
|||||||
pauseSelected() {
|
pauseSelected() {
|
||||||
Object.keys(this.selectedStacks)
|
Object.keys(this.selectedStacks)
|
||||||
.filter(id => this.$root.stackList[id].active)
|
.filter(id => this.$root.stackList[id].active)
|
||||||
.forEach(id => this.$root.getSocket().emit("pauseStack", id, () => {}));
|
.forEach(id => this.$root.getSocket().emit("pauseStack", id, () => { }));
|
||||||
|
|
||||||
this.cancelSelectMode();
|
this.cancelSelectMode();
|
||||||
},
|
},
|
||||||
@@ -342,7 +369,7 @@ export default {
|
|||||||
resumeSelected() {
|
resumeSelected() {
|
||||||
Object.keys(this.selectedStacks)
|
Object.keys(this.selectedStacks)
|
||||||
.filter(id => !this.$root.stackList[id].active)
|
.filter(id => !this.$root.stackList[id].active)
|
||||||
.forEach(id => this.$root.getSocket().emit("resumeStack", id, () => {}));
|
.forEach(id => this.$root.getSocket().emit("resumeStack", id, () => { }));
|
||||||
|
|
||||||
this.cancelSelectMode();
|
this.cancelSelectMode();
|
||||||
},
|
},
|
||||||
@@ -444,4 +471,15 @@ export default {
|
|||||||
gap: 10px;
|
gap: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.agent-select {
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: $dark-font-color3;
|
||||||
|
padding-left: 10px;
|
||||||
|
padding-right: 10px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
<Uptime :stack="stack" :fixed-width="true" class="me-2" />
|
<Uptime :stack="stack" :fixed-width="true" class="me-2" />
|
||||||
<div class="title">
|
<div class="title">
|
||||||
<span>{{ stackName }}</span>
|
<span>{{ stackName }}</span>
|
||||||
<div v-if="$root.agentCount > 1" class="endpoint">{{ endpointDisplay }}</div>
|
|
||||||
</div>
|
</div>
|
||||||
</router-link>
|
</router-link>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -101,6 +101,14 @@ export default {
|
|||||||
this.terminal.open(this.$refs.terminal);
|
this.terminal.open(this.$refs.terminal);
|
||||||
this.terminal.focus();
|
this.terminal.focus();
|
||||||
|
|
||||||
|
// Add right-click context menu handler for paste
|
||||||
|
this.$refs.terminal.addEventListener('contextmenu', this.handleContextMenu);
|
||||||
|
|
||||||
|
// Add selection handler for copy to clipboard
|
||||||
|
this.terminal.onSelectionChange(() => {
|
||||||
|
this.handleSelection();
|
||||||
|
});
|
||||||
|
|
||||||
// Notify parent component when data is received
|
// Notify parent component when data is received
|
||||||
this.terminal.onCursorMove(() => {
|
this.terminal.onCursorMove(() => {
|
||||||
console.debug("onData triggered");
|
console.debug("onData triggered");
|
||||||
@@ -135,6 +143,7 @@ export default {
|
|||||||
window.removeEventListener("resize", this.onResizeEvent); // Remove the resize event listener from the window object.
|
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();
|
||||||
|
this.$refs.terminal?.removeEventListener('contextmenu', this.handleContextMenu);
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
@@ -154,17 +163,27 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
removeInput() {
|
removeInput() {
|
||||||
|
const textAfterCursorLength = this.terminalInputBuffer.length - this.cursorPosition;
|
||||||
|
const spaces = " ".repeat(textAfterCursorLength);
|
||||||
const backspaceCount = this.terminalInputBuffer.length;
|
const backspaceCount = this.terminalInputBuffer.length;
|
||||||
const backspaces = "\b \b".repeat(backspaceCount);
|
const backspaces = "\b \b".repeat(backspaceCount);
|
||||||
this.cursorPosition = 0;
|
this.cursorPosition = 0;
|
||||||
this.terminal.write(backspaces);
|
this.terminal.write(spaces + backspaces);
|
||||||
this.terminalInputBuffer = "";
|
this.terminalInputBuffer = "";
|
||||||
},
|
},
|
||||||
|
|
||||||
|
clearCurrentLine() {
|
||||||
|
// Move cursor to the beginning of the input and clear it
|
||||||
|
const backspaces = "\b".repeat(this.cursorPosition);
|
||||||
|
const spaces = " ".repeat(this.terminalInputBuffer.length);
|
||||||
|
const moreBackspaces = "\b".repeat(this.terminalInputBuffer.length);
|
||||||
|
this.terminal.write(backspaces + spaces + moreBackspaces);
|
||||||
|
},
|
||||||
|
|
||||||
mainTerminalConfig() {
|
mainTerminalConfig() {
|
||||||
this.terminal.onKey(e => {
|
this.terminal.onKey(e => {
|
||||||
const code = e.key.charCodeAt(0);
|
// Optional: keep for debugging
|
||||||
console.debug("Encode: " + JSON.stringify(e.key));
|
// console.debug("Encode: " + JSON.stringify(e.key));
|
||||||
|
|
||||||
if (e.key === "\r") {
|
if (e.key === "\r") {
|
||||||
// Return if no input
|
// Return if no input
|
||||||
@@ -180,35 +199,65 @@ export default {
|
|||||||
this.$root.emitAgent(this.endpoint, "terminalInput", this.name, buffer + e.key, (err) => {
|
this.$root.emitAgent(this.endpoint, "terminalInput", this.name, buffer + e.key, (err) => {
|
||||||
this.$root.toastError(err.msg);
|
this.$root.toastError(err.msg);
|
||||||
});
|
});
|
||||||
|
} else if (e.key === "\u007F") { // Backspace
|
||||||
} else if (code === 127) { // Backspace
|
|
||||||
if (this.cursorPosition > 0) {
|
if (this.cursorPosition > 0) {
|
||||||
this.terminal.write("\b \b");
|
// Remove character to the left of cursor
|
||||||
|
const beforeCursor = this.terminalInputBuffer.slice(0, this.cursorPosition - 1);
|
||||||
|
const afterCursor = this.terminalInputBuffer.slice(this.cursorPosition);
|
||||||
|
this.terminalInputBuffer = beforeCursor + afterCursor;
|
||||||
this.cursorPosition--;
|
this.cursorPosition--;
|
||||||
this.terminalInputBuffer = this.terminalInputBuffer.slice(0, -1);
|
|
||||||
|
// Redraw the line
|
||||||
|
this.terminal.write("\b" + afterCursor + " \b".repeat(afterCursor.length + 1));
|
||||||
|
}
|
||||||
|
} else if (e.key === "\u001B\u005B\u0033\u007E") { // Delete key
|
||||||
|
if (this.cursorPosition < this.terminalInputBuffer.length) {
|
||||||
|
// Remove character to the right of cursor
|
||||||
|
const beforeCursor = this.terminalInputBuffer.slice(0, this.cursorPosition);
|
||||||
|
const afterCursor = this.terminalInputBuffer.slice(this.cursorPosition + 1);
|
||||||
|
this.terminalInputBuffer = beforeCursor + afterCursor;
|
||||||
|
|
||||||
|
// Redraw the line from cursor position
|
||||||
|
this.terminal.write(afterCursor + " \b".repeat(afterCursor.length + 1));
|
||||||
}
|
}
|
||||||
} else if (e.key === "\u001B\u005B\u0041" || e.key === "\u001B\u005B\u0042") { // UP OR DOWN
|
} else if (e.key === "\u001B\u005B\u0041" || e.key === "\u001B\u005B\u0042") { // UP OR DOWN
|
||||||
// Do nothing
|
// Do nothing
|
||||||
|
|
||||||
} else if (e.key === "\u001B\u005B\u0043") { // RIGHT
|
} else if (e.key === "\u001B\u005B\u0043") { // RIGHT
|
||||||
// TODO
|
if (this.cursorPosition < this.terminalInputBuffer.length) {
|
||||||
|
this.terminal.write(this.terminalInputBuffer[this.cursorPosition]);
|
||||||
|
this.cursorPosition++;
|
||||||
|
}
|
||||||
} else if (e.key === "\u001B\u005B\u0044") { // LEFT
|
} else if (e.key === "\u001B\u005B\u0044") { // LEFT
|
||||||
// TODO
|
if (this.cursorPosition > 0) {
|
||||||
|
this.terminal.write("\b");
|
||||||
|
this.cursorPosition--;
|
||||||
|
}
|
||||||
} 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.emitAgent(this.endpoint, "terminalInput", this.name, e.key);
|
||||||
this.removeInput();
|
this.removeInput();
|
||||||
|
} else if (e.key === "\u0016" || (e.domEvent?.ctrlKey && e.key.toLowerCase() === "v")) { // Ctrl + V
|
||||||
|
this.handlePaste();
|
||||||
|
} else if (e.key === "\u0009" || e.key.startsWith("\u001B")) { // TAB or other special keys
|
||||||
|
// Do nothing
|
||||||
} else {
|
} else {
|
||||||
|
const textBeforeCursor = this.terminalInputBuffer.slice(0, this.cursorPosition);
|
||||||
|
const textAfterCursor = this.terminalInputBuffer.slice(this.cursorPosition);
|
||||||
|
this.terminalInputBuffer = textBeforeCursor + e.key + textAfterCursor;
|
||||||
|
this.terminal.write(e.key + textAfterCursor + "\b".repeat(textAfterCursor.length));
|
||||||
this.cursorPosition++;
|
this.cursorPosition++;
|
||||||
this.terminalInputBuffer += e.key;
|
|
||||||
console.log(this.terminalInputBuffer);
|
|
||||||
this.terminal.write(e.key);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
interactiveTerminalConfig() {
|
interactiveTerminalConfig() {
|
||||||
this.terminal.onKey(e => {
|
this.terminal.onKey(e => {
|
||||||
|
// Handle Ctrl+V for paste
|
||||||
|
if (e.key === "\u0016" || (e.domEvent?.ctrlKey && e.key.toLowerCase() === "v")) {
|
||||||
|
this.handlePaste();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.$root.emitAgent(this.endpoint, "terminalInput", this.name, e.key, (res) => {
|
this.$root.emitAgent(this.endpoint, "terminalInput", this.name, e.key, (res) => {
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
this.$root.toastRes(res);
|
this.$root.toastRes(res);
|
||||||
@@ -239,7 +288,87 @@ export default {
|
|||||||
let rows = this.terminal.rows;
|
let rows = this.terminal.rows;
|
||||||
let cols = this.terminal.cols;
|
let cols = this.terminal.cols;
|
||||||
this.$root.emitAgent(this.endpoint, "terminalResize", this.name, rows, cols);
|
this.$root.emitAgent(this.endpoint, "terminalResize", this.name, rows, cols);
|
||||||
}
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle clipboard paste operation
|
||||||
|
*/
|
||||||
|
async handlePaste() {
|
||||||
|
try {
|
||||||
|
const text = await navigator.clipboard.readText();
|
||||||
|
if (text) {
|
||||||
|
this.pasteText(text);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to read from clipboard:", error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Paste text into the terminal based on current mode
|
||||||
|
*/
|
||||||
|
pasteText(text) {
|
||||||
|
if (this.mode === "mainTerminal") {
|
||||||
|
// For main terminal, insert text at current cursor position
|
||||||
|
const beforeCursor = this.terminalInputBuffer.slice(0, this.cursorPosition);
|
||||||
|
const afterCursor = this.terminalInputBuffer.slice(this.cursorPosition);
|
||||||
|
|
||||||
|
// Update the buffer with inserted text
|
||||||
|
this.terminalInputBuffer = beforeCursor + text + afterCursor;
|
||||||
|
|
||||||
|
// Clear the current line and rewrite it
|
||||||
|
this.clearCurrentLine();
|
||||||
|
this.terminal.write(this.terminalInputBuffer);
|
||||||
|
|
||||||
|
// Move cursor to the correct position (after the pasted text)
|
||||||
|
this.cursorPosition += text.length;
|
||||||
|
const backspaces = "\b".repeat(afterCursor.length);
|
||||||
|
this.terminal.write(backspaces);
|
||||||
|
|
||||||
|
} else if (this.mode === "interactive") {
|
||||||
|
// For interactive terminal, send directly to server
|
||||||
|
this.$root.emitAgent(this.endpoint, "terminalInput", this.name, text, (res) => {
|
||||||
|
if (!res.ok) {
|
||||||
|
this.$root.toastRes(res);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle right-click context menu for paste operation
|
||||||
|
*/
|
||||||
|
handleContextMenu(event) {
|
||||||
|
// Prevent default context menu
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
// Only handle paste for modes that support input
|
||||||
|
if (this.mode === "mainTerminal" || this.mode === "interactive") {
|
||||||
|
this.handlePaste();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle text selection in terminal - copy to clipboard
|
||||||
|
*/
|
||||||
|
handleSelection() {
|
||||||
|
const selectedText = this.terminal.getSelection();
|
||||||
|
if (selectedText && selectedText.length > 0) {
|
||||||
|
this.copyToClipboard(selectedText);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copy text to clipboard
|
||||||
|
*/
|
||||||
|
async copyToClipboard(text) {
|
||||||
|
try {
|
||||||
|
await navigator.clipboard.writeText(text);
|
||||||
|
console.debug("Text copied to clipboard:", text);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to copy to clipboard:", error);
|
||||||
|
}
|
||||||
|
},
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
</option>
|
</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div v-show="false" class="my-4">
|
<div v-show="true" class="my-4">
|
||||||
<label for="timezone" class="form-label">{{ $t("Theme") }}</label>
|
<label for="timezone" class="form-label">{{ $t("Theme") }}</label>
|
||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
|
|||||||
97
frontend/src/components/settings/GlobalEnv.vue
Normal file
97
frontend/src/components/settings/GlobalEnv.vue
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div v-if="settingsLoaded" class="my-4">
|
||||||
|
<form class="my-4" autocomplete="off" @submit.prevent="saveGeneral">
|
||||||
|
<div class="shadow-box mb-3 editor-box edit-mode">
|
||||||
|
<code-mirror
|
||||||
|
ref="editor"
|
||||||
|
v-model="settings.globalENV"
|
||||||
|
:extensions="extensionsEnv"
|
||||||
|
minimal
|
||||||
|
wrap="true"
|
||||||
|
dark="true"
|
||||||
|
tab="true"
|
||||||
|
:hasFocus="editorFocus"
|
||||||
|
@change="onChange"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="my-4">
|
||||||
|
<!-- Save Button -->
|
||||||
|
<div>
|
||||||
|
<button class="btn btn-primary" type="submit">
|
||||||
|
{{ $t("Save") }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import CodeMirror from "vue-codemirror6";
|
||||||
|
import { python } from "@codemirror/lang-python"; // good enough for .env key=value highlighting
|
||||||
|
import { dracula as editorTheme } from "thememirror";
|
||||||
|
import { lineNumbers, EditorView } from "@codemirror/view";
|
||||||
|
import { ref } from "vue";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "GlobalEnv",
|
||||||
|
components: {
|
||||||
|
CodeMirror,
|
||||||
|
},
|
||||||
|
|
||||||
|
setup() {
|
||||||
|
const editorFocus = ref(false);
|
||||||
|
|
||||||
|
const focusEffectHandler = (state, focusing) => {
|
||||||
|
editorFocus.value = focusing;
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const extensionsEnv = [
|
||||||
|
editorTheme,
|
||||||
|
python(),
|
||||||
|
lineNumbers(),
|
||||||
|
EditorView.focusChangeEffect.of(focusEffectHandler),
|
||||||
|
];
|
||||||
|
|
||||||
|
return { editorFocus, extensionsEnv };
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
settings() {
|
||||||
|
return this.$parent.$parent.$parent.settings;
|
||||||
|
},
|
||||||
|
saveSettings() {
|
||||||
|
return this.$parent.$parent.$parent.saveSettings;
|
||||||
|
},
|
||||||
|
settingsLoaded() {
|
||||||
|
return this.$parent.$parent.$parent.settingsLoaded;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
/** Save the settings */
|
||||||
|
saveGeneral() {
|
||||||
|
this.saveSettings();
|
||||||
|
},
|
||||||
|
|
||||||
|
onChange() {
|
||||||
|
// hook for future live validation if desired
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.editor-box {
|
||||||
|
font-family: 'JetBrains Mono', monospace;
|
||||||
|
font-size: 14px;
|
||||||
|
|
||||||
|
&.edit-mode {
|
||||||
|
background-color: #2c2f38 !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -32,6 +32,9 @@ const languageList = {
|
|||||||
"hu": "Magyar",
|
"hu": "Magyar",
|
||||||
"ca": "Català",
|
"ca": "Català",
|
||||||
"ga": "Gaeilge",
|
"ga": "Gaeilge",
|
||||||
|
"de-CH": "Schwiizerdütsch",
|
||||||
|
"mag": "मगही",
|
||||||
|
"mai": "मैथिली",
|
||||||
};
|
};
|
||||||
|
|
||||||
let messages = {
|
let messages = {
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ import {
|
|||||||
faAward,
|
faAward,
|
||||||
faLink,
|
faLink,
|
||||||
faChevronDown,
|
faChevronDown,
|
||||||
|
faChevronUp,
|
||||||
faSignOutAlt,
|
faSignOutAlt,
|
||||||
faPen,
|
faPen,
|
||||||
faExternalLinkSquareAlt,
|
faExternalLinkSquareAlt,
|
||||||
@@ -54,6 +55,8 @@ import {
|
|||||||
faTerminal, faWarehouse, faHome, faRocket,
|
faTerminal, faWarehouse, faHome, faRocket,
|
||||||
faRotate,
|
faRotate,
|
||||||
faCloudArrowDown, faArrowsRotate,
|
faCloudArrowDown, faArrowsRotate,
|
||||||
|
faChevronCircleRight,
|
||||||
|
faChevronCircleDown,
|
||||||
} from "@fortawesome/free-solid-svg-icons";
|
} from "@fortawesome/free-solid-svg-icons";
|
||||||
|
|
||||||
library.add(
|
library.add(
|
||||||
@@ -88,6 +91,7 @@ library.add(
|
|||||||
faAward,
|
faAward,
|
||||||
faLink,
|
faLink,
|
||||||
faChevronDown,
|
faChevronDown,
|
||||||
|
faChevronUp,
|
||||||
faSignOutAlt,
|
faSignOutAlt,
|
||||||
faPen,
|
faPen,
|
||||||
faExternalLinkSquareAlt,
|
faExternalLinkSquareAlt,
|
||||||
@@ -109,6 +113,8 @@ library.add(
|
|||||||
faRotate,
|
faRotate,
|
||||||
faCloudArrowDown,
|
faCloudArrowDown,
|
||||||
faArrowsRotate,
|
faArrowsRotate,
|
||||||
|
faChevronCircleRight,
|
||||||
|
faChevronCircleDown,
|
||||||
);
|
);
|
||||||
|
|
||||||
export { FontAwesomeIcon };
|
export { FontAwesomeIcon };
|
||||||
|
|||||||
132
frontend/src/lang/de-CH.json
Normal file
132
frontend/src/lang/de-CH.json
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
{
|
||||||
|
"languageName": "Schwiizerdütsch",
|
||||||
|
"Create your admin account": "Erstell dis Admin-Konto",
|
||||||
|
"authIncorrectCreds": "Falsche Benutzername oder falsches Passwort.",
|
||||||
|
"PasswordsDoNotMatch": "Passwörter stimmed nöd überein.",
|
||||||
|
"Repeat Password": "Passwort wiederhole",
|
||||||
|
"Create": "Erstelle",
|
||||||
|
"signedInDisp": "Agmeldet als {0}",
|
||||||
|
"signedInDispDisabled": "Ameldig deaktiviert.",
|
||||||
|
"home": "Startsiite",
|
||||||
|
"console": "Konsole",
|
||||||
|
"registry": "Container Registry",
|
||||||
|
"compose": "Compose",
|
||||||
|
"addFirstStackMsg": "Stell din erste Stack zämme!",
|
||||||
|
"stackName": "Stack-Name",
|
||||||
|
"deployStack": "Deploye",
|
||||||
|
"deleteStack": "Lösche",
|
||||||
|
"stopStack": "Ahalte",
|
||||||
|
"restartStack": "Neustarte",
|
||||||
|
"updateStack": "Aktualisiere",
|
||||||
|
"startStack": "Starte",
|
||||||
|
"editStack": "Bearbeite",
|
||||||
|
"discardStack": "Verwerfe",
|
||||||
|
"saveStackDraft": "Speicher",
|
||||||
|
"notAvailableShort": "N/V",
|
||||||
|
"deleteStackMsg": "Wotsch de Stack würklich lösche?",
|
||||||
|
"stackNotManagedByDockgeMsg": "De Stack wird nöd vo Dockge verwaltet.",
|
||||||
|
"primaryHostname": "Primäre Hostname",
|
||||||
|
"general": "Allgemein",
|
||||||
|
"container": "Container",
|
||||||
|
"scanFolder": "Stacks-Ordner durchsueche",
|
||||||
|
"dockerImage": "Image",
|
||||||
|
"restartPolicyUnlessStopped": "Falls nöd gstoppt",
|
||||||
|
"restartPolicyAlways": "Immer",
|
||||||
|
"restartPolicyOnFailure": "Bimene Fehler",
|
||||||
|
"restartPolicyNo": "Kein Neustart",
|
||||||
|
"environmentVariable": "Umgebigsvariable",
|
||||||
|
"restartPolicy": "Neustart Richtlinie",
|
||||||
|
"containerName": "Container-Name",
|
||||||
|
"port": "Port / Ports",
|
||||||
|
"volume": "Volume / Volumes",
|
||||||
|
"network": "Netzwerk | Netzwerke",
|
||||||
|
"dependsOn": "Container-Abhängigkeit/e",
|
||||||
|
"addListItem": "{0} hinzuefüege",
|
||||||
|
"deleteContainer": "Lösche",
|
||||||
|
"addContainer": "Container hinzuefüege",
|
||||||
|
"addNetwork": "Netzwerk hinzuefüege",
|
||||||
|
"disableauth.message1": "Bisch der sicher, dass du d'<strong>Ameldung deaktiviere</strong> wotsch?",
|
||||||
|
"disableauth.message2": "Es isch für Szenarien vorgseh, <strong>in dene du beabsichtigsch, e Drittabüter-Authentifizierig</strong> vor Dockge z'implementiere, wie zum Bispiel Cloudflare Access, Authelia oder anderi Authentifizierigsmechanisme.",
|
||||||
|
"passwordNotMatchMsg": "s'wiederholte Passwort stimmt nöd überein.",
|
||||||
|
"autoGet": "Automatisch lade",
|
||||||
|
"add": "Hinzuefüege",
|
||||||
|
"Edit": "Bearbeite",
|
||||||
|
"applyToYAML": "Uf s'YAML awende",
|
||||||
|
"createExternalNetwork": "Erstelle",
|
||||||
|
"addInternalNetwork": "Hinzuefüege",
|
||||||
|
"Save": "Speichere",
|
||||||
|
"Language": "Sprach",
|
||||||
|
"Current User": "Aktuelle Benutzer",
|
||||||
|
"Change Password": "Passwort ändere",
|
||||||
|
"Current Password": "Aktuells Passwort",
|
||||||
|
"New Password": "Neus Passwort",
|
||||||
|
"Repeat New Password": "Neus Passwort wiederhole",
|
||||||
|
"Update Password": "Passwort aktualisiere",
|
||||||
|
"Advanced": "Erwiitert",
|
||||||
|
"Please use this option carefully!": "Bitte verwend die Option sorgfältig!",
|
||||||
|
"Enable Auth": "Ameldig aktiviere",
|
||||||
|
"Disable Auth": "Ameldig deaktiviere",
|
||||||
|
"I understand, please disable": "Ich verstah, bitte deaktiviere",
|
||||||
|
"Leave": "Verlah",
|
||||||
|
"Frontend Version": "Frontend Version",
|
||||||
|
"Check Update On GitHub": "Update uf GitHub überprüefe",
|
||||||
|
"Show update if available": "Update azeige, wenn verfüegbar",
|
||||||
|
"Also check beta release": "Au Beta-Version überprüefe",
|
||||||
|
"Remember me": "Agmeldet blibe",
|
||||||
|
"Login": "Amelde",
|
||||||
|
"Username": "Benutzername",
|
||||||
|
"Password": "Passwort",
|
||||||
|
"Settings": "Istellige",
|
||||||
|
"Logout": "Abmelde",
|
||||||
|
"Lowercase only": "Nur Chliibuechstabe",
|
||||||
|
"Convert to Compose": "In Compose-Syntax umwandle",
|
||||||
|
"Docker Run": "Docker Run",
|
||||||
|
"active": "aktiv",
|
||||||
|
"exited": "beendet",
|
||||||
|
"inactive": "inaktiv",
|
||||||
|
"Appearance": "Erschiinigsbild",
|
||||||
|
"Security": "Sicherheit",
|
||||||
|
"About": "Über",
|
||||||
|
"Allowed commands:": "Zueglasseni Befehl:",
|
||||||
|
"Internal Networks": "Interni Netzwerk",
|
||||||
|
"External Networks": "Externi Netzwerk",
|
||||||
|
"No External Networks": "Kei externi Netzwerk",
|
||||||
|
"Cannot connect to the socket server.": "Kei Verbindig zum Socket Server.",
|
||||||
|
"reverseProxyMsg1": "Wird en Reverse Proxy benutzt?",
|
||||||
|
"reconnecting...": "Erneute Verbindigsufbau…",
|
||||||
|
"downStack": "Stoppe & Deaktiviere",
|
||||||
|
"extra": "Extra",
|
||||||
|
"url": "URL / URLs",
|
||||||
|
"reverseProxyMsg2": "Lern wie er für WebSockets z'konfiguriere isch.",
|
||||||
|
"connecting...": "Verbindigsufbau zum Socket Server…",
|
||||||
|
"newUpdate": "Neues Update",
|
||||||
|
"dockgeAgent": "Dockge Agent | Dockge Agente",
|
||||||
|
"currentEndpoint": "Aktuell",
|
||||||
|
"dockgeURL": "Dockge URL (z. B. http://127.0.0.1:5001)",
|
||||||
|
"agentOnline": "Online",
|
||||||
|
"agentOffline": "Offline",
|
||||||
|
"connecting": "Verbinde",
|
||||||
|
"connect": "Verbinde",
|
||||||
|
"addAgent": "Agent Hinzuefüege",
|
||||||
|
"agentAddedSuccessfully": "Agent erfolgriich hinzuegfüegt.",
|
||||||
|
"agentRemovedSuccessfully": "Agent erfolgriich entfernt.",
|
||||||
|
"removeAgent": "Agent Entferne",
|
||||||
|
"removeAgentMsg": "Bisch der sicher, dass du de Agent entferne wotsch?",
|
||||||
|
"LongSyntaxNotSupported": "Lange Syntax wird nöd unterstützt. Bitte verwend de YAML-Editor.",
|
||||||
|
"Lost connection to the socket server. Reconnecting...": "Verbindig zum Socket Server verlore. Verbinde...",
|
||||||
|
"Saved": "Gspeicheret",
|
||||||
|
"Deleted": "Glöscht",
|
||||||
|
"Started": "Gstartet",
|
||||||
|
"Stopped": "Gstoppt",
|
||||||
|
"Restarted": "Neugstartet",
|
||||||
|
"New Container Name...": "Neue Container Name...",
|
||||||
|
"Network name...": "Netzwerkname...",
|
||||||
|
"Select a network...": "Netzwerk uswähle...",
|
||||||
|
"Updated": "Aktualisiert",
|
||||||
|
"Deployed": "Deployed",
|
||||||
|
"Switch to sh": "Zu sh wechsle",
|
||||||
|
"terminal": "Terminal",
|
||||||
|
"CurrentHostname": "(nöd gsetzt: verwendet aktuelli Hostname)",
|
||||||
|
"Downed": "Abegfahre",
|
||||||
|
"NoNetworksAvailable": "Kei Netzwerk verfüegbar. Du muesch zersch interni Netzwerk hinzuefüege oder externi Netzwerk uf de rechte Siite aktiviere."
|
||||||
|
}
|
||||||
@@ -25,6 +25,7 @@
|
|||||||
"saveStackDraft": "Save",
|
"saveStackDraft": "Save",
|
||||||
"notAvailableShort": "N/A",
|
"notAvailableShort": "N/A",
|
||||||
"deleteStackMsg": "Are you sure you want to delete this stack?",
|
"deleteStackMsg": "Are you sure you want to delete this stack?",
|
||||||
|
"cancel": "Cancel",
|
||||||
"stackNotManagedByDockgeMsg": "This stack is not managed by Dockge.",
|
"stackNotManagedByDockgeMsg": "This stack is not managed by Dockge.",
|
||||||
"primaryHostname": "Primary Hostname",
|
"primaryHostname": "Primary Hostname",
|
||||||
"general": "General",
|
"general": "General",
|
||||||
@@ -113,7 +114,10 @@
|
|||||||
"agentRemovedSuccessfully": "Agent removed successfully.",
|
"agentRemovedSuccessfully": "Agent removed successfully.",
|
||||||
"removeAgent": "Remove Agent",
|
"removeAgent": "Remove Agent",
|
||||||
"removeAgentMsg": "Are you sure you want to remove this agent?",
|
"removeAgentMsg": "Are you sure you want to remove this agent?",
|
||||||
|
"GlobalEnv": "Global .env",
|
||||||
"LongSyntaxNotSupported": "Long syntax is not supported here. Please use the YAML editor.",
|
"LongSyntaxNotSupported": "Long syntax is not supported here. Please use the YAML editor.",
|
||||||
|
"name": "Dockge Agent Display name",
|
||||||
|
"updatedName": "New Dockge Agent Display name",
|
||||||
"Saved": "Saved",
|
"Saved": "Saved",
|
||||||
"Deployed": "Deployed",
|
"Deployed": "Deployed",
|
||||||
"Deleted": "Deleted",
|
"Deleted": "Deleted",
|
||||||
@@ -128,5 +132,15 @@
|
|||||||
"New Container Name...": "New Container Name...",
|
"New Container Name...": "New Container Name...",
|
||||||
"Network name...": "Network name...",
|
"Network name...": "Network name...",
|
||||||
"Select a network...": "Select a network...",
|
"Select a network...": "Select a network...",
|
||||||
"NoNetworksAvailable": "No networks available. You need to add internal networks or enable external networks in the right side first."
|
"NoNetworksAvailable": "No networks available. You need to add internal networks or enable external networks in the right side first.",
|
||||||
|
"CPU": "CPU",
|
||||||
|
"memory": "Memory",
|
||||||
|
"memoryAbbreviated": "Mem",
|
||||||
|
"networkIO": "Network I/O",
|
||||||
|
"blockIO": "Block I/O",
|
||||||
|
"Console is not enabled": "Console is not enabled",
|
||||||
|
"ConsoleNotEnabledMSG1": "Console is a powerful tool that allows you to execute any commands such as <code>docker</code>, <code>rm</code> within the Dockge's container in this Web UI.",
|
||||||
|
"ConsoleNotEnabledMSG2": "It might be dangerous since this Dockge container is connecting to the host's Docker daemon. Also Dockge could be possibly taken down by commands like <code>rm -rf</code>" ,
|
||||||
|
"ConsoleNotEnabledMSG3": "If you understand the risk, you can enable it by setting <code>DOCKGE_ENABLE_CONSOLE=true</code> in the environment variables.",
|
||||||
|
"confirmLeaveStack": "You are currently editing a stack. Are you sure you want to leave?"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -131,10 +131,15 @@ export default defineComponent({
|
|||||||
methods: {
|
methods: {
|
||||||
|
|
||||||
endpointDisplayFunction(endpoint : string) {
|
endpointDisplayFunction(endpoint : string) {
|
||||||
if (endpoint) {
|
for (const [k, v] of Object.entries(this.$data.agentList)) {
|
||||||
return endpoint;
|
if (endpoint) {
|
||||||
} else {
|
if (endpoint === v["endpoint"] && v["name"] !== "") {
|
||||||
return this.$t("currentEndpoint");
|
return v["name"];
|
||||||
|
}
|
||||||
|
if (endpoint === v["endpoint"] && v["name"] === "" ) {
|
||||||
|
return endpoint;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -279,7 +284,6 @@ export default defineComponent({
|
|||||||
});
|
});
|
||||||
|
|
||||||
socket.on("agentList", (res) => {
|
socket.on("agentList", (res) => {
|
||||||
console.log(res);
|
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
this.agentList = res.agentList;
|
this.agentList = res.agentList;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
<template>
|
<template>
|
||||||
<transition name="slide-fade" appear>
|
<transition name="slide-fade" appear>
|
||||||
<div>
|
<div>
|
||||||
<h1 v-if="isAdd" class="mb-3">{{$t("compose")}}</h1>
|
<h1 v-if="isAdd" class="mb-3">{{ $t("compose") }}</h1>
|
||||||
<h1 v-else class="mb-3">
|
<h1 v-else class="mb-3">
|
||||||
<Uptime :stack="globalStack" :pill="true" /> {{ stack.name }}
|
<Uptime :stack="globalStack" :pill="true" /> {{ stack.name }}
|
||||||
<span v-if="$root.agentCount > 1" class="agent-name">
|
<span v-if="$root.agentCount > 1 && endpoint !== ''" class="agent-name">
|
||||||
({{ endpointDisplay }})
|
({{ endpointDisplay }})
|
||||||
</span>
|
</span>
|
||||||
</h1>
|
</h1>
|
||||||
@@ -63,8 +63,8 @@
|
|||||||
|
|
||||||
<!-- URLs -->
|
<!-- URLs -->
|
||||||
<div v-if="urls.length > 0" class="mb-3">
|
<div v-if="urls.length > 0" class="mb-3">
|
||||||
<a v-for="(url, index) in urls" :key="index" target="_blank" :href="url.url">
|
<a v-for="(urlItem, index) in urls" :key="index" target="_blank" :href="urlItem.url">
|
||||||
<span class="badge bg-secondary me-2">{{ url.display }}</span>
|
<span class="badge bg-secondary me-2">{{ urlItem.display }}</span>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -98,8 +98,8 @@
|
|||||||
<div class="mt-3">
|
<div class="mt-3">
|
||||||
<label for="name" class="form-label">{{ $t("dockgeAgent") }}</label>
|
<label for="name" class="form-label">{{ $t("dockgeAgent") }}</label>
|
||||||
<select v-model="stack.endpoint" class="form-select">
|
<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'">
|
<option v-for="(agent, agentEndpoint) in $root.agentList" :key="agentEndpoint" :value="agentEndpoint" :disabled="$root.agentStatusList[agentEndpoint] != 'online'">
|
||||||
({{ $root.agentStatusList[endpoint] }}) {{ (endpoint) ? endpoint : $t("currentEndpoint") }}
|
({{ $root.agentStatusList[agentEndpoint] }}) {{ (agent.name !== '') ? agent.name : agent.url || $t("Current") }}
|
||||||
</option>
|
</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
@@ -128,7 +128,11 @@
|
|||||||
:name="name"
|
:name="name"
|
||||||
:is-edit-mode="isEditMode"
|
:is-edit-mode="isEditMode"
|
||||||
:first="name === Object.keys(jsonConfig.services)[0]"
|
:first="name === Object.keys(jsonConfig.services)[0]"
|
||||||
:status="serviceStatusList[name]"
|
:serviceStatus="serviceStatusList[name]"
|
||||||
|
:dockerStats="dockerStats"
|
||||||
|
@start-service="startService"
|
||||||
|
@stop-service="stopService"
|
||||||
|
@restart-service="restartService"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -150,7 +154,7 @@
|
|||||||
|
|
||||||
<!-- Combined Terminal Output -->
|
<!-- Combined Terminal Output -->
|
||||||
<div v-show="!isEditMode">
|
<div v-show="!isEditMode">
|
||||||
<h4 class="mb-3">{{$t("terminal")}}</h4>
|
<h4 class="mb-3">{{ $t("terminal") }}</h4>
|
||||||
<Terminal
|
<Terminal
|
||||||
ref="combinedTerminal"
|
ref="combinedTerminal"
|
||||||
class="mb-3 terminal"
|
class="mb-3 terminal"
|
||||||
@@ -167,16 +171,18 @@
|
|||||||
|
|
||||||
<!-- YAML editor -->
|
<!-- YAML editor -->
|
||||||
<div class="shadow-box mb-3 editor-box" :class="{'edit-mode' : isEditMode}">
|
<div class="shadow-box mb-3 editor-box" :class="{'edit-mode' : isEditMode}">
|
||||||
<prism-editor
|
<code-mirror
|
||||||
ref="editor"
|
ref="editor"
|
||||||
v-model="stack.composeYAML"
|
v-model="stack.composeYAML"
|
||||||
class="yaml-editor"
|
:extensions="extensions"
|
||||||
:highlight="highlighterYAML"
|
minimal
|
||||||
line-numbers :readonly="!isEditMode"
|
wrap="true"
|
||||||
@input="yamlCodeChange"
|
dark="true"
|
||||||
@focus="editorFocus = true"
|
tab="true"
|
||||||
@blur="editorFocus = false"
|
:disabled="!isEditMode"
|
||||||
></prism-editor>
|
:hasFocus="editorFocus"
|
||||||
|
@change="yamlCodeChange"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="isEditMode" class="mb-3">
|
<div v-if="isEditMode" class="mb-3">
|
||||||
{{ yamlError }}
|
{{ yamlError }}
|
||||||
@@ -186,15 +192,18 @@
|
|||||||
<div v-if="isEditMode">
|
<div v-if="isEditMode">
|
||||||
<h4 class="mb-3">.env</h4>
|
<h4 class="mb-3">.env</h4>
|
||||||
<div class="shadow-box mb-3 editor-box" :class="{'edit-mode' : isEditMode}">
|
<div class="shadow-box mb-3 editor-box" :class="{'edit-mode' : isEditMode}">
|
||||||
<prism-editor
|
<code-mirror
|
||||||
ref="editor"
|
ref="editor"
|
||||||
v-model="stack.composeENV"
|
v-model="stack.composeENV"
|
||||||
class="env-editor"
|
:extensions="extensionsEnv"
|
||||||
:highlight="highlighterENV"
|
minimal
|
||||||
line-numbers :readonly="!isEditMode"
|
wrap="true"
|
||||||
@focus="editorFocus = true"
|
dark="true"
|
||||||
@blur="editorFocus = false"
|
tab="true"
|
||||||
></prism-editor>
|
:disabled="!isEditMode"
|
||||||
|
:hasFocus="editorFocus"
|
||||||
|
@change="yamlCodeChange"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -212,15 +221,6 @@
|
|||||||
<NetworkInput />
|
<NetworkInput />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- <div class="shadow-box big-padding mb-3">
|
|
||||||
<div class="mb-3">
|
|
||||||
<label for="name" class="form-label"> Search Templates</label>
|
|
||||||
<input id="name" v-model="name" type="text" class="form-control" placeholder="Search..." required>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<prism-editor v-if="false" v-model="yamlConfig" class="yaml-editor" :highlight="highlighter" line-numbers @input="yamlCodeChange"></prism-editor>
|
|
||||||
</div>-->
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -229,7 +229,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Delete Dialog -->
|
<!-- Delete Dialog -->
|
||||||
<BModal v-model="showDeleteDialog" :okTitle="$t('deleteStack')" okVariant="danger" @ok="deleteDialog">
|
<BModal v-model="showDeleteDialog" :cancelTitle="$t('cancel')" :okTitle="$t('deleteStack')" okVariant="danger" @ok="deleteDialog">
|
||||||
{{ $t("deleteStackMsg") }}
|
{{ $t("deleteStackMsg") }}
|
||||||
</BModal>
|
</BModal>
|
||||||
</div>
|
</div>
|
||||||
@@ -237,13 +237,13 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { highlight, languages } from "prismjs/components/prism-core";
|
import CodeMirror from "vue-codemirror6";
|
||||||
import { PrismEditor } from "vue-prism-editor";
|
import { yaml } from "@codemirror/lang-yaml";
|
||||||
import "prismjs/components/prism-yaml";
|
import { python } from "@codemirror/lang-python";
|
||||||
|
import { dracula as editorTheme } from "thememirror";
|
||||||
|
import { lineNumbers, EditorView, Decoration, ViewPlugin } from "@codemirror/view";
|
||||||
import { parseDocument, Document } from "yaml";
|
import { parseDocument, Document } from "yaml";
|
||||||
|
import { RangeSetBuilder } from "@codemirror/state";
|
||||||
import "prismjs/themes/prism-tomorrow.css";
|
|
||||||
import "vue-prism-editor/dist/prismeditor.min.css";
|
|
||||||
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
|
||||||
import {
|
import {
|
||||||
COMBINED_TERMINAL_COLS,
|
COMBINED_TERMINAL_COLS,
|
||||||
@@ -257,6 +257,7 @@ import {
|
|||||||
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";
|
||||||
|
import { ref } from "vue";
|
||||||
|
|
||||||
const template = `
|
const template = `
|
||||||
services:
|
services:
|
||||||
@@ -271,17 +272,50 @@ const envDefault = "# VARIABLE=value #comment";
|
|||||||
let yamlErrorTimeout = null;
|
let yamlErrorTimeout = null;
|
||||||
|
|
||||||
let serviceStatusTimeout = null;
|
let serviceStatusTimeout = null;
|
||||||
let prismjsSymbolDefinition = {
|
let dockerStatsTimeout = null;
|
||||||
"symbol": {
|
|
||||||
pattern: /(?<!\$)\$(\{[^{}]*\}|\w+)/,
|
// Highlight $VAR and ${VAR}
|
||||||
|
const variableHighlight = ViewPlugin.fromClass(class {
|
||||||
|
constructor(view) {
|
||||||
|
this.decorations = this.buildDecorations(view);
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
update(update) {
|
||||||
|
if (update.docChanged || update.viewportChanged) {
|
||||||
|
this.decorations = this.buildDecorations(update.view);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
buildDecorations(view) {
|
||||||
|
const builder = new RangeSetBuilder();
|
||||||
|
|
||||||
|
for (const { from, to } of view.visibleRanges) {
|
||||||
|
const text = view.state.doc.sliceString(from, to);
|
||||||
|
const variableRegex = /\$\{?[A-Za-z0-9_]+\}?/g;
|
||||||
|
let match;
|
||||||
|
while ((match = variableRegex.exec(text)) !== null) {
|
||||||
|
const start = from + match.index;
|
||||||
|
const end = start + match[0].length;
|
||||||
|
|
||||||
|
builder.add(
|
||||||
|
start,
|
||||||
|
end,
|
||||||
|
Decoration.mark({ class: "cm-variable-highlight" })
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.finish();
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
decorations: v => v.decorations
|
||||||
|
});
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
NetworkInput,
|
NetworkInput,
|
||||||
FontAwesomeIcon,
|
FontAwesomeIcon,
|
||||||
PrismEditor,
|
CodeMirror,
|
||||||
BModal,
|
BModal,
|
||||||
},
|
},
|
||||||
beforeRouteUpdate(to, from, next) {
|
beforeRouteUpdate(to, from, next) {
|
||||||
@@ -290,10 +324,38 @@ export default {
|
|||||||
beforeRouteLeave(to, from, next) {
|
beforeRouteLeave(to, from, next) {
|
||||||
this.exitConfirm(next);
|
this.exitConfirm(next);
|
||||||
},
|
},
|
||||||
|
setup() {
|
||||||
|
const editorFocus = ref(false);
|
||||||
|
|
||||||
|
const focusEffectHandler = (state, focusing) => {
|
||||||
|
editorFocus.value = focusing;
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const extensions = [
|
||||||
|
editorTheme,
|
||||||
|
yaml(),
|
||||||
|
variableHighlight,
|
||||||
|
lineNumbers(),
|
||||||
|
EditorView.focusChangeEffect.of(focusEffectHandler)
|
||||||
|
];
|
||||||
|
|
||||||
|
const extensionsEnv = [
|
||||||
|
editorTheme,
|
||||||
|
python(),
|
||||||
|
variableHighlight,
|
||||||
|
lineNumbers(),
|
||||||
|
EditorView.focusChangeEffect.of(focusEffectHandler)
|
||||||
|
];
|
||||||
|
|
||||||
|
return {
|
||||||
|
extensions,
|
||||||
|
extensionsEnv,
|
||||||
|
editorFocus };
|
||||||
|
},
|
||||||
yamlDoc: null, // For keeping the yaml comments
|
yamlDoc: null, // For keeping the yaml comments
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
editorFocus: false,
|
|
||||||
jsonConfig: {},
|
jsonConfig: {},
|
||||||
envsubstJSONConfig: {},
|
envsubstJSONConfig: {},
|
||||||
yamlError: "",
|
yamlError: "",
|
||||||
@@ -306,15 +368,16 @@ export default {
|
|||||||
|
|
||||||
},
|
},
|
||||||
serviceStatusList: {},
|
serviceStatusList: {},
|
||||||
|
dockerStats: {},
|
||||||
isEditMode: false,
|
isEditMode: false,
|
||||||
submitted: false,
|
submitted: false,
|
||||||
showDeleteDialog: false,
|
showDeleteDialog: false,
|
||||||
newContainerName: "",
|
newContainerName: "",
|
||||||
stopServiceStatusTimeout: false,
|
stopServiceStatusTimeout: false,
|
||||||
|
stopDockerStatsTimeout: false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
|
||||||
endpointDisplay() {
|
endpointDisplay() {
|
||||||
return this.$root.endpointDisplayFunction(this.endpoint);
|
return this.$root.endpointDisplayFunction(this.endpoint);
|
||||||
},
|
},
|
||||||
@@ -478,6 +541,7 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.requestServiceStatus();
|
this.requestServiceStatus();
|
||||||
|
this.requestDockerStats();
|
||||||
},
|
},
|
||||||
unmounted() {
|
unmounted() {
|
||||||
|
|
||||||
@@ -490,7 +554,19 @@ export default {
|
|||||||
}, 5000);
|
}, 5000);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
startDockerStatsTimeout() {
|
||||||
|
clearTimeout(dockerStatsTimeout);
|
||||||
|
dockerStatsTimeout = setTimeout(async () => {
|
||||||
|
this.requestDockerStats();
|
||||||
|
}, 5000);
|
||||||
|
},
|
||||||
|
|
||||||
requestServiceStatus() {
|
requestServiceStatus() {
|
||||||
|
// Do not request if it is add mode
|
||||||
|
if (this.isAdd) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.$root.emitAgent(this.endpoint, "serviceStatusList", this.stack.name, (res) => {
|
this.$root.emitAgent(this.endpoint, "serviceStatusList", this.stack.name, (res) => {
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
this.serviceStatusList = res.serviceStatusList;
|
this.serviceStatusList = res.serviceStatusList;
|
||||||
@@ -501,9 +577,20 @@ export default {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
requestDockerStats() {
|
||||||
|
this.$root.emitAgent(this.endpoint, "dockerStats", (res) => {
|
||||||
|
if (res.ok) {
|
||||||
|
this.dockerStats = res.dockerStats;
|
||||||
|
}
|
||||||
|
if (!this.stopDockerStatsTimeout) {
|
||||||
|
this.startDockerStatsTimeout();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
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(this.$t("confirmLeaveStack"))) {
|
||||||
this.exitAction();
|
this.exitAction();
|
||||||
next();
|
next();
|
||||||
} else {
|
} else {
|
||||||
@@ -518,7 +605,9 @@ export default {
|
|||||||
exitAction() {
|
exitAction() {
|
||||||
console.log("exitAction");
|
console.log("exitAction");
|
||||||
this.stopServiceStatusTimeout = true;
|
this.stopServiceStatusTimeout = true;
|
||||||
|
this.stopDockerStatsTimeout = true;
|
||||||
clearTimeout(serviceStatusTimeout);
|
clearTimeout(serviceStatusTimeout);
|
||||||
|
clearTimeout(dockerStatsTimeout);
|
||||||
|
|
||||||
// Leave Combined Terminal
|
// Leave Combined Terminal
|
||||||
console.debug("leaveCombinedTerminal", this.endpoint, this.stack.name);
|
console.debug("leaveCombinedTerminal", this.endpoint, this.stack.name);
|
||||||
@@ -659,46 +748,6 @@ export default {
|
|||||||
this.isEditMode = false;
|
this.isEditMode = false;
|
||||||
},
|
},
|
||||||
|
|
||||||
highlighterYAML(code) {
|
|
||||||
if (!languages.yaml_with_symbols) {
|
|
||||||
languages.yaml_with_symbols = languages.insertBefore("yaml", "punctuation", {
|
|
||||||
"symbol": prismjsSymbolDefinition["symbol"]
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return highlight(code, languages.yaml_with_symbols);
|
|
||||||
},
|
|
||||||
|
|
||||||
highlighterENV(code) {
|
|
||||||
if (!languages.docker_env) {
|
|
||||||
languages.docker_env = {
|
|
||||||
"comment": {
|
|
||||||
pattern: /(^#| #).*$/m,
|
|
||||||
greedy: true
|
|
||||||
},
|
|
||||||
"keyword": {
|
|
||||||
pattern: /^\w*(?=[:=])/m,
|
|
||||||
greedy: true
|
|
||||||
},
|
|
||||||
"value": {
|
|
||||||
pattern: /(?<=[:=]).*?((?= #)|$)/m,
|
|
||||||
greedy: true,
|
|
||||||
inside: {
|
|
||||||
"string": [
|
|
||||||
{
|
|
||||||
pattern: /^ *'.*?(?<!\\)'/m,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
pattern: /^ *".*?(?<!\\)"|^.*$/m,
|
|
||||||
inside: prismjsSymbolDefinition
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return highlight(code, languages.docker_env);
|
|
||||||
},
|
|
||||||
|
|
||||||
yamlToJSON(yaml) {
|
yamlToJSON(yaml) {
|
||||||
let doc = parseDocument(yaml);
|
let doc = parseDocument(yaml);
|
||||||
if (doc.errors.length > 0) {
|
if (doc.errors.length > 0) {
|
||||||
@@ -755,7 +804,7 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
checkYAML() {
|
checkYAML() {
|
||||||
|
// TODO: implement validation
|
||||||
},
|
},
|
||||||
|
|
||||||
addContainer() {
|
addContainer() {
|
||||||
@@ -786,6 +835,44 @@ export default {
|
|||||||
this.stack.name = this.stack?.name?.toLowerCase();
|
this.stack.name = this.stack?.name?.toLowerCase();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
startService(serviceName) {
|
||||||
|
this.processing = true;
|
||||||
|
|
||||||
|
this.$root.emitAgent(this.endpoint, "startService", this.stack.name, serviceName, (res) => {
|
||||||
|
this.processing = false;
|
||||||
|
this.$root.toastRes(res);
|
||||||
|
|
||||||
|
if (res.ok) {
|
||||||
|
this.requestServiceStatus(); // Refresh service status
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
stopService(serviceName) {
|
||||||
|
this.processing = true;
|
||||||
|
|
||||||
|
this.$root.emitAgent(this.endpoint, "stopService", this.stack.name, serviceName, (res) => {
|
||||||
|
this.processing = false;
|
||||||
|
this.$root.toastRes(res);
|
||||||
|
|
||||||
|
if (res.ok) {
|
||||||
|
this.requestServiceStatus(); // Refresh service status
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
restartService(serviceName) {
|
||||||
|
this.processing = true;
|
||||||
|
|
||||||
|
this.$root.emitAgent(this.endpoint, "restartService", this.stack.name, serviceName, (res) => {
|
||||||
|
this.processing = false;
|
||||||
|
this.$root.toastRes(res);
|
||||||
|
|
||||||
|
if (res.ok) {
|
||||||
|
this.requestServiceStatus(); // Refresh service status
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
@@ -797,12 +884,14 @@ export default {
|
|||||||
height: 200px;
|
height: 200px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:deep(.cm-variable-highlight) {
|
||||||
|
color: #fe6000;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
.editor-box {
|
.editor-box {
|
||||||
font-family: 'JetBrains Mono', monospace;
|
font-family: 'JetBrains Mono', monospace;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
&.edit-mode {
|
|
||||||
background-color: #2c2f38 !important;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.agent-name {
|
.agent-name {
|
||||||
|
|||||||
@@ -1,35 +1,28 @@
|
|||||||
<template>
|
<template>
|
||||||
<transition name="slide-fade" appear>
|
<transition name="slide-fade" appear>
|
||||||
<div>
|
<div v-if="!processing">
|
||||||
<h1 class="mb-3">Console</h1>
|
<h1 class="mb-3">{{ $t("console") }}</h1>
|
||||||
|
|
||||||
<div>
|
<Terminal v-if="enableConsole" class="terminal" :rows="20" mode="mainTerminal" name="console" :endpoint="endpoint"></Terminal>
|
||||||
<p>
|
|
||||||
{{ $t("Allowed commands:") }}
|
|
||||||
<template v-for="(command, index) in allowedCommandList" :key="command">
|
|
||||||
<code>{{ command }}</code>
|
|
||||||
|
|
||||||
<!-- No comma at the end -->
|
<div v-else class="alert alert-warning shadow-box" role="alert">
|
||||||
<span v-if="index !== allowedCommandList.length - 1">, </span>
|
<h4 class="alert-heading">{{ $t("Console is not enabled") }}</h4>
|
||||||
</template>
|
<p v-html="$t('ConsoleNotEnabledMSG1')"></p>
|
||||||
</p>
|
<p v-html="$t('ConsoleNotEnabledMSG2')"></p>
|
||||||
|
<p v-html="$t('ConsoleNotEnabledMSG3')"></p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Terminal class="terminal" :rows="20" mode="mainTerminal" name="console" :endpoint="endpoint"></Terminal>
|
|
||||||
</div>
|
</div>
|
||||||
</transition>
|
</transition>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
import { allowedCommandList } from "../../../common/util-common";
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
allowedCommandList,
|
processing: true,
|
||||||
|
enableConsole: false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@@ -38,10 +31,13 @@ export default {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
|
this.$root.emitAgent(this.endpoint, "checkMainTerminal", (res) => {
|
||||||
|
this.enableConsole = res.ok;
|
||||||
|
this.processing = false;
|
||||||
|
});
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -29,7 +29,7 @@
|
|||||||
<!-- Docker Run -->
|
<!-- Docker Run -->
|
||||||
<h2 class="mb-3">{{ $t("Docker Run") }}</h2>
|
<h2 class="mb-3">{{ $t("Docker Run") }}</h2>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<textarea id="name" v-model="dockerRunCommand" type="text" class="form-control docker-run" required placeholder="docker run ..."></textarea>
|
<textarea id="name" v-model="dockerRunCommand" type="text" class="form-control docker-run shadow-box" required placeholder="docker run ..."></textarea>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button class="btn-normal btn mb-4" @click="convertDockerRun">{{ $t("Convert to Compose") }}</button>
|
<button class="btn-normal btn mb-4" @click="convertDockerRun">{{ $t("Convert to Compose") }}</button>
|
||||||
@@ -49,13 +49,25 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<!-- Agent Display Name -->
|
<!-- Agent Display Name -->
|
||||||
<span v-if="endpoint === ''">{{ $t("currentEndpoint") }}</span>
|
<template v-if="$root.agentStatusList[endpoint]">
|
||||||
<a v-else :href="agent.url" target="_blank">{{ endpoint }}</a>
|
<span v-if="endpoint === '' && agent.name === ''" class="badge bg-secondary me-2">Current</span>
|
||||||
|
<span v-else-if="agent.name === ''" :href="agent.url" class="me-2">{{ endpoint }}</span>
|
||||||
|
<span v-else :href="agent.url" class="me-2">{{ agent.name }}</span>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- Edit Name -->
|
||||||
|
<font-awesome-icon v-if="agent.name !== ''" icon="pen-to-square" @click="showEditAgentNameDialog[agent.name] = !showEditAgentNameDialog[agent.Name]" />
|
||||||
|
|
||||||
|
<!-- Edit Dialog -->
|
||||||
|
<BModal v-model="showEditAgentNameDialog[agent.name]" :no-close-on-backdrop="true" :close-on-esc="true" :okTitle="$t('Update Name')" okVariant="info" @ok="updateName(agent.url, agent.updatedName)">
|
||||||
|
<label for="Update Name" class="form-label">Current value: {{ $t(agent.name) }}</label>
|
||||||
|
<input id="updatedName" v-model="agent.updatedName" type="text" class="form-control" optional>
|
||||||
|
</BModal>
|
||||||
|
|
||||||
<!-- Remove Button -->
|
<!-- Remove Button -->
|
||||||
<font-awesome-icon v-if="endpoint !== ''" class="ms-2 remove-agent" icon="trash" @click="showRemoveAgentDialog[agent.url] = !showRemoveAgentDialog[agent.url]" />
|
<font-awesome-icon v-if="endpoint !== ''" class="ms-2 remove-agent" icon="trash" @click="showRemoveAgentDialog[agent.url] = !showRemoveAgentDialog[agent.url]" />
|
||||||
|
|
||||||
<!-- Remoe Agent Dialog -->
|
<!-- Remove Agent Dialog -->
|
||||||
<BModal v-model="showRemoveAgentDialog[agent.url]" :okTitle="$t('removeAgent')" okVariant="danger" @ok="removeAgent(agent.url)">
|
<BModal v-model="showRemoveAgentDialog[agent.url]" :okTitle="$t('removeAgent')" okVariant="danger" @ok="removeAgent(agent.url)">
|
||||||
<p>{{ agent.url }}</p>
|
<p>{{ agent.url }}</p>
|
||||||
{{ $t("removeAgentMsg") }}
|
{{ $t("removeAgentMsg") }}
|
||||||
@@ -81,6 +93,11 @@
|
|||||||
<input id="password" v-model="agent.password" type="password" class="form-control" required autocomplete="new-password">
|
<input id="password" v-model="agent.password" type="password" class="form-control" required autocomplete="new-password">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="name" class="form-label">{{ $t("Friendly Name") }}</label>
|
||||||
|
<input id="name" v-model="agent.name" type="text" class="form-control" optional>
|
||||||
|
</div>
|
||||||
|
|
||||||
<button type="submit" class="btn btn-primary" :disabled="connectingAgent">
|
<button type="submit" class="btn btn-primary" :disabled="connectingAgent">
|
||||||
<template v-if="connectingAgent">{{ $t("connecting") }}</template>
|
<template v-if="connectingAgent">{{ $t("connecting") }}</template>
|
||||||
<template v-else>{{ $t("connect") }}</template>
|
<template v-else>{{ $t("connect") }}</template>
|
||||||
@@ -121,11 +138,14 @@ export default {
|
|||||||
dockerRunCommand: "",
|
dockerRunCommand: "",
|
||||||
showAgentForm: false,
|
showAgentForm: false,
|
||||||
showRemoveAgentDialog: {},
|
showRemoveAgentDialog: {},
|
||||||
|
showEditAgentNameDialog: {},
|
||||||
connectingAgent: false,
|
connectingAgent: false,
|
||||||
agent: {
|
agent: {
|
||||||
url: "http://",
|
url: "http://",
|
||||||
username: "",
|
username: "",
|
||||||
password: "",
|
password: "",
|
||||||
|
name: "",
|
||||||
|
updatedName: "",
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
@@ -199,6 +219,19 @@ export default {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
updateName(url, updatedName) {
|
||||||
|
this.$root.getSocket().emit("updateAgent", url, updatedName, (res) => {
|
||||||
|
this.$root.toastRes(res);
|
||||||
|
|
||||||
|
if (res.ok) {
|
||||||
|
this.showAgentForm = false;
|
||||||
|
this.agent = {
|
||||||
|
updatedName: "",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
getStatusNum(statusName) {
|
getStatusNum(statusName) {
|
||||||
let num = 0;
|
let num = 0;
|
||||||
|
|
||||||
@@ -286,7 +319,7 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -326,7 +359,6 @@ table {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.docker-run {
|
.docker-run {
|
||||||
background-color: $dark-bg !important;
|
|
||||||
border: none;
|
border: none;
|
||||||
font-family: 'JetBrains Mono', monospace;
|
font-family: 'JetBrains Mono', monospace;
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
|
|||||||
@@ -83,6 +83,9 @@ export default {
|
|||||||
security: {
|
security: {
|
||||||
title: this.$t("Security"),
|
title: this.$t("Security"),
|
||||||
},
|
},
|
||||||
|
globalEnv: {
|
||||||
|
title: this.$t("GlobalEnv"),
|
||||||
|
},
|
||||||
about: {
|
about: {
|
||||||
title: this.$t("About"),
|
title: this.$t("About"),
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ const Settings = () => import("./pages/Settings.vue");
|
|||||||
import Appearance from "./components/settings/Appearance.vue";
|
import Appearance from "./components/settings/Appearance.vue";
|
||||||
import General from "./components/settings/General.vue";
|
import General from "./components/settings/General.vue";
|
||||||
const Security = () => import("./components/settings/Security.vue");
|
const Security = () => import("./components/settings/Security.vue");
|
||||||
|
const GlobalEnv = () => import("./components/settings/GlobalEnv.vue");
|
||||||
import About from "./components/settings/About.vue";
|
import About from "./components/settings/About.vue";
|
||||||
|
|
||||||
const routes = [
|
const routes = [
|
||||||
@@ -78,6 +79,10 @@ const routes = [
|
|||||||
path: "security",
|
path: "security",
|
||||||
component: Security,
|
component: Security,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "globalEnv",
|
||||||
|
component: GlobalEnv,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: "about",
|
path: "about",
|
||||||
component: About,
|
component: About,
|
||||||
|
|||||||
@@ -593,9 +593,6 @@ optgroup {
|
|||||||
color: $primary;
|
color: $primary;
|
||||||
}
|
}
|
||||||
|
|
||||||
.prism-editor__textarea {
|
|
||||||
outline: none !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
h5.settings-subheading::after {
|
h5.settings-subheading::after {
|
||||||
content: "";
|
content: "";
|
||||||
@@ -676,18 +673,25 @@ code {
|
|||||||
color: $dark-font-color3;
|
color: $dark-font-color3;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Vue Prism Editor bug - workaround
|
|
||||||
// https://github.com/koca/vue-prism-editor/issues/87
|
.cm-gutters {
|
||||||
/*
|
background-color: transparent !important;
|
||||||
.prism-editor__textarea {
|
|
||||||
width: 999999px !important;
|
|
||||||
}
|
}
|
||||||
.prism-editor__editor {
|
.dark [contenteditable="true"] {
|
||||||
white-space: pre !important;
|
background-color: transparent !important;
|
||||||
|
}
|
||||||
|
.cm-editor {
|
||||||
|
background-color: transparent !important;
|
||||||
|
}
|
||||||
|
.cm-activeLine, .cm-activeLineGutter {
|
||||||
|
background-color: transparent !important;
|
||||||
|
}
|
||||||
|
.cm-selectionBackground {
|
||||||
|
background-color: #74c2ff3d !important;
|
||||||
|
}
|
||||||
|
.cm-focused {
|
||||||
|
outline: none !important;
|
||||||
}
|
}
|
||||||
.prism-editor__container {
|
|
||||||
overflow-x: scroll !important;
|
|
||||||
}*/
|
|
||||||
|
|
||||||
// Localization
|
// Localization
|
||||||
@import "localization.scss";
|
@import "localization.scss";
|
||||||
|
|||||||
405
package-lock.json
generated
405
package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "dockge",
|
"name": "dockge",
|
||||||
"version": "1.4.2",
|
"version": "1.5.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "dockge",
|
"name": "dockge",
|
||||||
"version": "1.4.2",
|
"version": "1.5.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@homebridge/node-pty-prebuilt-multiarch": "0.11.14",
|
"@homebridge/node-pty-prebuilt-multiarch": "0.11.14",
|
||||||
"@inventage/envsubst": "^0.16.0",
|
"@inventage/envsubst": "^0.16.0",
|
||||||
@@ -40,6 +40,8 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@actions/github": "^6.0.0",
|
"@actions/github": "^6.0.0",
|
||||||
|
"@codemirror/lang-python": "^6.1.7",
|
||||||
|
"@codemirror/lang-yaml": "^6.1.2",
|
||||||
"@fontsource/jetbrains-mono": "^5.2.5",
|
"@fontsource/jetbrains-mono": "^5.2.5",
|
||||||
"@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",
|
||||||
@@ -53,26 +55,27 @@
|
|||||||
"@types/semver": "^7.7.0",
|
"@types/semver": "^7.7.0",
|
||||||
"@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": "~5.2.3",
|
||||||
"@xterm/addon-fit": "beta",
|
"@xterm/addon-fit": "beta",
|
||||||
"@xterm/xterm": "beta",
|
"@xterm/xterm": "beta",
|
||||||
"bootstrap": "5.3.2",
|
"bootstrap": "5.3.2",
|
||||||
"bootstrap-vue-next": "~0.14.10",
|
"bootstrap-vue-next": "~0.14.10",
|
||||||
|
"codemirror": "^6.0.1",
|
||||||
"concurrently": "^8.2.2",
|
"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",
|
||||||
"eslint-plugin-vue": "~9.32.0",
|
"eslint-plugin-vue": "~9.32.0",
|
||||||
"prismjs": "~1.30.0",
|
|
||||||
"sass": "~1.68.0",
|
"sass": "~1.68.0",
|
||||||
|
"thememirror": "^2.0.1",
|
||||||
"typescript": "~5.2.2",
|
"typescript": "~5.2.2",
|
||||||
"unplugin-vue-components": "~0.25.2",
|
"unplugin-vue-components": "~0.25.2",
|
||||||
"vite": "~5.4.15",
|
"vite": "~5.4.15",
|
||||||
"vite-plugin-compression": "~0.5.1",
|
"vite-plugin-compression": "~0.5.1",
|
||||||
"vue": "~3.5.13",
|
"vue": "~3.5.13",
|
||||||
|
"vue-codemirror6": "^1.3.13",
|
||||||
"vue-eslint-parser": "~9.3.2",
|
"vue-eslint-parser": "~9.3.2",
|
||||||
"vue-i18n": "~10.0.6",
|
"vue-i18n": "~10.0.6",
|
||||||
"vue-prism-editor": "2.0.0-alpha.2",
|
|
||||||
"vue-qrcode": "~2.2.2",
|
"vue-qrcode": "~2.2.2",
|
||||||
"vue-router": "~4.5.0",
|
"vue-router": "~4.5.0",
|
||||||
"vue-toastification": "2.0.0-rc.5",
|
"vue-toastification": "2.0.0-rc.5",
|
||||||
@@ -180,6 +183,124 @@
|
|||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@codemirror/autocomplete": {
|
||||||
|
"version": "6.20.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.20.0.tgz",
|
||||||
|
"integrity": "sha512-bOwvTOIJcG5FVo5gUUupiwYh8MioPLQ4UcqbcRf7UQ98X90tCa9E1kZ3Z7tqwpZxYyOvh1YTYbmZE9RTfTp5hg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@codemirror/language": "^6.0.0",
|
||||||
|
"@codemirror/state": "^6.0.0",
|
||||||
|
"@codemirror/view": "^6.17.0",
|
||||||
|
"@lezer/common": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@codemirror/commands": {
|
||||||
|
"version": "6.10.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.10.1.tgz",
|
||||||
|
"integrity": "sha512-uWDWFypNdQmz2y1LaNJzK7fL7TYKLeUAU0npEC685OKTF3KcQ2Vu3klIM78D7I6wGhktme0lh3CuQLv0ZCrD9Q==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@codemirror/language": "^6.0.0",
|
||||||
|
"@codemirror/state": "^6.4.0",
|
||||||
|
"@codemirror/view": "^6.27.0",
|
||||||
|
"@lezer/common": "^1.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@codemirror/lang-python": {
|
||||||
|
"version": "6.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@codemirror/lang-python/-/lang-python-6.2.1.tgz",
|
||||||
|
"integrity": "sha512-IRjC8RUBhn9mGR9ywecNhB51yePWCGgvHfY1lWN/Mrp3cKuHr0isDKia+9HnvhiWNnMpbGhWrkhuWOc09exRyw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@codemirror/autocomplete": "^6.3.2",
|
||||||
|
"@codemirror/language": "^6.8.0",
|
||||||
|
"@codemirror/state": "^6.0.0",
|
||||||
|
"@lezer/common": "^1.2.1",
|
||||||
|
"@lezer/python": "^1.1.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@codemirror/lang-yaml": {
|
||||||
|
"version": "6.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@codemirror/lang-yaml/-/lang-yaml-6.1.2.tgz",
|
||||||
|
"integrity": "sha512-dxrfG8w5Ce/QbT7YID7mWZFKhdhsaTNOYjOkSIMt1qmC4VQnXSDSYVHHHn8k6kJUfIhtLo8t1JJgltlxWdsITw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@codemirror/autocomplete": "^6.0.0",
|
||||||
|
"@codemirror/language": "^6.0.0",
|
||||||
|
"@codemirror/state": "^6.0.0",
|
||||||
|
"@lezer/common": "^1.2.0",
|
||||||
|
"@lezer/highlight": "^1.2.0",
|
||||||
|
"@lezer/lr": "^1.0.0",
|
||||||
|
"@lezer/yaml": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@codemirror/language": {
|
||||||
|
"version": "6.12.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.12.1.tgz",
|
||||||
|
"integrity": "sha512-Fa6xkSiuGKc8XC8Cn96T+TQHYj4ZZ7RdFmXA3i9xe/3hLHfwPZdM+dqfX0Cp0zQklBKhVD8Yzc8LS45rkqcwpQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@codemirror/state": "^6.0.0",
|
||||||
|
"@codemirror/view": "^6.23.0",
|
||||||
|
"@lezer/common": "^1.5.0",
|
||||||
|
"@lezer/highlight": "^1.0.0",
|
||||||
|
"@lezer/lr": "^1.0.0",
|
||||||
|
"style-mod": "^4.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@codemirror/lint": {
|
||||||
|
"version": "6.9.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.9.2.tgz",
|
||||||
|
"integrity": "sha512-sv3DylBiIyi+xKwRCJAAsBZZZWo82shJ/RTMymLabAdtbkV5cSKwWDeCgtUq3v8flTaXS2y1kKkICuRYtUswyQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@codemirror/state": "^6.0.0",
|
||||||
|
"@codemirror/view": "^6.35.0",
|
||||||
|
"crelt": "^1.0.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@codemirror/search": {
|
||||||
|
"version": "6.5.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.5.11.tgz",
|
||||||
|
"integrity": "sha512-KmWepDE6jUdL6n8cAAqIpRmLPBZ5ZKnicE8oGU/s3QrAVID+0VhLFrzUucVKHG5035/BSykhExDL/Xm7dHthiA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@codemirror/state": "^6.0.0",
|
||||||
|
"@codemirror/view": "^6.0.0",
|
||||||
|
"crelt": "^1.0.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@codemirror/state": {
|
||||||
|
"version": "6.5.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.5.3.tgz",
|
||||||
|
"integrity": "sha512-MerMzJzlXogk2fxWFU1nKp36bY5orBG59HnPiz0G9nLRebWa0zXuv2siH6PLIHBvv5TH8CkQRqjBs0MlxCZu+A==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@marijn/find-cluster-break": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@codemirror/view": {
|
||||||
|
"version": "6.39.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.39.6.tgz",
|
||||||
|
"integrity": "sha512-/N+SoP5NndJjkGInp3BwlUa3KQKD6bDo0TV6ep37ueAdQ7BVu/PqlZNywmgjCq0MQoZadZd8T+MZucSr7fktyQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@codemirror/state": "^6.5.0",
|
||||||
|
"crelt": "^1.0.6",
|
||||||
|
"style-mod": "^4.1.0",
|
||||||
|
"w3c-keyname": "^2.2.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@colors/colors": {
|
"node_modules/@colors/colors": {
|
||||||
"version": "1.5.0",
|
"version": "1.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz",
|
||||||
@@ -742,33 +863,6 @@
|
|||||||
"vue-demi": ">=0.13.0"
|
"vue-demi": ">=0.13.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@floating-ui/vue/node_modules/vue-demi": {
|
|
||||||
"version": "0.14.10",
|
|
||||||
"resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz",
|
|
||||||
"integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==",
|
|
||||||
"dev": true,
|
|
||||||
"hasInstallScript": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"bin": {
|
|
||||||
"vue-demi-fix": "bin/vue-demi-fix.js",
|
|
||||||
"vue-demi-switch": "bin/vue-demi-switch.js"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=12"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/antfu"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"@vue/composition-api": "^1.0.0-rc.1",
|
|
||||||
"vue": "^3.0.0-0 || ^2.6.0"
|
|
||||||
},
|
|
||||||
"peerDependenciesMeta": {
|
|
||||||
"@vue/composition-api": {
|
|
||||||
"optional": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@fontsource/jetbrains-mono": {
|
"node_modules/@fontsource/jetbrains-mono": {
|
||||||
"version": "5.2.5",
|
"version": "5.2.5",
|
||||||
"resolved": "https://registry.npmjs.org/@fontsource/jetbrains-mono/-/jetbrains-mono-5.2.5.tgz",
|
"resolved": "https://registry.npmjs.org/@fontsource/jetbrains-mono/-/jetbrains-mono-5.2.5.tgz",
|
||||||
@@ -1085,6 +1179,57 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@lezer/common": {
|
||||||
|
"version": "1.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.5.0.tgz",
|
||||||
|
"integrity": "sha512-PNGcolp9hr4PJdXR4ix7XtixDrClScvtSCYW3rQG106oVMOOI+jFb+0+J3mbeL/53g1Zd6s0kJzaw6Ri68GmAA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@lezer/highlight": {
|
||||||
|
"version": "1.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.3.tgz",
|
||||||
|
"integrity": "sha512-qXdH7UqTvGfdVBINrgKhDsVTJTxactNNxLk7+UMwZhU13lMHaOBlJe9Vqp907ya56Y3+ed2tlqzys7jDkTmW0g==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@lezer/common": "^1.3.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@lezer/lr": {
|
||||||
|
"version": "1.4.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.5.tgz",
|
||||||
|
"integrity": "sha512-/YTRKP5yPPSo1xImYQk7AZZMAgap0kegzqCSYHjAL9x1AZ0ZQW+IpcEzMKagCsbTsLnVeWkxYrCNeXG8xEPrjg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@lezer/common": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@lezer/python": {
|
||||||
|
"version": "1.1.18",
|
||||||
|
"resolved": "https://registry.npmjs.org/@lezer/python/-/python-1.1.18.tgz",
|
||||||
|
"integrity": "sha512-31FiUrU7z9+d/ElGQLJFXl+dKOdx0jALlP3KEOsGTex8mvj+SoE1FgItcHWK/axkxCHGUSpqIHt6JAWfWu9Rhg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@lezer/common": "^1.2.0",
|
||||||
|
"@lezer/highlight": "^1.0.0",
|
||||||
|
"@lezer/lr": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@lezer/yaml": {
|
||||||
|
"version": "1.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@lezer/yaml/-/yaml-1.0.3.tgz",
|
||||||
|
"integrity": "sha512-GuBLekbw9jDBDhGur82nuwkxKQ+a3W5H0GfaAthDXcAu+XdpS43VlnxA9E9hllkpSP5ellRDKjLLj7Lu9Wr6xA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@lezer/common": "^1.2.0",
|
||||||
|
"@lezer/highlight": "^1.0.0",
|
||||||
|
"@lezer/lr": "^1.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@louislam/sqlite3": {
|
"node_modules/@louislam/sqlite3": {
|
||||||
"version": "15.1.6",
|
"version": "15.1.6",
|
||||||
"resolved": "https://registry.npmjs.org/@louislam/sqlite3/-/sqlite3-15.1.6.tgz",
|
"resolved": "https://registry.npmjs.org/@louislam/sqlite3/-/sqlite3-15.1.6.tgz",
|
||||||
@@ -1128,6 +1273,13 @@
|
|||||||
"node-pre-gyp": "bin/node-pre-gyp"
|
"node-pre-gyp": "bin/node-pre-gyp"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@marijn/find-cluster-break": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@marijn/find-cluster-break/-/find-cluster-break-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@nodelib/fs.scandir": {
|
"node_modules/@nodelib/fs.scandir": {
|
||||||
"version": "2.1.5",
|
"version": "2.1.5",
|
||||||
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
||||||
@@ -2153,16 +2305,16 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@vitejs/plugin-vue": {
|
"node_modules/@vitejs/plugin-vue": {
|
||||||
"version": "4.5.2",
|
"version": "5.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-4.5.2.tgz",
|
"resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.2.3.tgz",
|
||||||
"integrity": "sha512-UGR3DlzLi/SaVBPX0cnSyE37vqxU3O6chn8l0HJNzQzDia6/Au2A4xKv+iIJW8w2daf80G7TYHhi1pAUjdZ0bQ==",
|
"integrity": "sha512-IYSLEQj4LgZZuoVpdSUCw3dIynTWQgPlaRP6iAvMle4My0HdYwr5g5wQAfwOeHQBmYwEkqF70nRpSilr6PoUDg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^14.18.0 || >=16.0.0"
|
"node": "^18.0.0 || >=20.0.0"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"vite": "^4.0.0 || ^5.0.0",
|
"vite": "^5.0.0 || ^6.0.0",
|
||||||
"vue": "^3.2.25"
|
"vue": "^3.2.25"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -2298,33 +2450,6 @@
|
|||||||
"url": "https://github.com/sponsors/antfu"
|
"url": "https://github.com/sponsors/antfu"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@vueuse/core/node_modules/vue-demi": {
|
|
||||||
"version": "0.14.10",
|
|
||||||
"resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz",
|
|
||||||
"integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==",
|
|
||||||
"dev": true,
|
|
||||||
"hasInstallScript": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"bin": {
|
|
||||||
"vue-demi-fix": "bin/vue-demi-fix.js",
|
|
||||||
"vue-demi-switch": "bin/vue-demi-switch.js"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=12"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/antfu"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"@vue/composition-api": "^1.0.0-rc.1",
|
|
||||||
"vue": "^3.0.0-0 || ^2.6.0"
|
|
||||||
},
|
|
||||||
"peerDependenciesMeta": {
|
|
||||||
"@vue/composition-api": {
|
|
||||||
"optional": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@vueuse/metadata": {
|
"node_modules/@vueuse/metadata": {
|
||||||
"version": "10.11.1",
|
"version": "10.11.1",
|
||||||
"resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-10.11.1.tgz",
|
"resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-10.11.1.tgz",
|
||||||
@@ -2348,33 +2473,6 @@
|
|||||||
"url": "https://github.com/sponsors/antfu"
|
"url": "https://github.com/sponsors/antfu"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@vueuse/shared/node_modules/vue-demi": {
|
|
||||||
"version": "0.14.10",
|
|
||||||
"resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz",
|
|
||||||
"integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==",
|
|
||||||
"dev": true,
|
|
||||||
"hasInstallScript": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"bin": {
|
|
||||||
"vue-demi-fix": "bin/vue-demi-fix.js",
|
|
||||||
"vue-demi-switch": "bin/vue-demi-switch.js"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=12"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/antfu"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"@vue/composition-api": "^1.0.0-rc.1",
|
|
||||||
"vue": "^3.0.0-0 || ^2.6.0"
|
|
||||||
},
|
|
||||||
"peerDependenciesMeta": {
|
|
||||||
"@vue/composition-api": {
|
|
||||||
"optional": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@xterm/addon-fit": {
|
"node_modules/@xterm/addon-fit": {
|
||||||
"version": "0.11.0-beta.99",
|
"version": "0.11.0-beta.99",
|
||||||
"resolved": "https://registry.npmjs.org/@xterm/addon-fit/-/addon-fit-0.11.0-beta.99.tgz",
|
"resolved": "https://registry.npmjs.org/@xterm/addon-fit/-/addon-fit-0.11.0-beta.99.tgz",
|
||||||
@@ -3153,6 +3251,22 @@
|
|||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/codemirror": {
|
||||||
|
"version": "6.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/codemirror/-/codemirror-6.0.2.tgz",
|
||||||
|
"integrity": "sha512-VhydHotNW5w1UGK0Qj96BwSk/Zqbp9WbnyK2W/eVMv4QyF41INRGpjUhFJY7/uDNuudSc33a/PKr4iDqRduvHw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@codemirror/autocomplete": "^6.0.0",
|
||||||
|
"@codemirror/commands": "^6.0.0",
|
||||||
|
"@codemirror/language": "^6.0.0",
|
||||||
|
"@codemirror/lint": "^6.0.0",
|
||||||
|
"@codemirror/search": "^6.0.0",
|
||||||
|
"@codemirror/state": "^6.0.0",
|
||||||
|
"@codemirror/view": "^6.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/color-convert": {
|
"node_modules/color-convert": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||||
@@ -3474,6 +3588,13 @@
|
|||||||
"node": ">= 0.10"
|
"node": ">= 0.10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/crelt": {
|
||||||
|
"version": "1.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz",
|
||||||
|
"integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/croner": {
|
"node_modules/croner": {
|
||||||
"version": "8.1.2",
|
"version": "8.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/croner/-/croner-8.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/croner/-/croner-8.1.2.tgz",
|
||||||
@@ -7347,16 +7468,6 @@
|
|||||||
"node": ">= 0.8.0"
|
"node": ">= 0.8.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/prismjs": {
|
|
||||||
"version": "1.30.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.30.0.tgz",
|
|
||||||
"integrity": "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/promise-inflight": {
|
"node_modules/promise-inflight": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz",
|
||||||
@@ -8922,6 +9033,13 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/style-mod": {
|
||||||
|
"version": "4.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.3.tgz",
|
||||||
|
"integrity": "sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/supports-color": {
|
"node_modules/supports-color": {
|
||||||
"version": "8.1.1",
|
"version": "8.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
|
||||||
@@ -9059,6 +9177,18 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/thememirror": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/thememirror/-/thememirror-2.0.1.tgz",
|
||||||
|
"integrity": "sha512-d5i6FVvWWPkwrm4cHLI3t9AT1OrkAt7Ig8dtdYSofgF7C/eiyNuq6zQzSTusWTde3jpW9WLvA9J/fzNKMUsd0w==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"@codemirror/language": "^6.0.0",
|
||||||
|
"@codemirror/state": "^6.0.0",
|
||||||
|
"@codemirror/view": "^6.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/tildify": {
|
"node_modules/tildify": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/tildify/-/tildify-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/tildify/-/tildify-2.0.0.tgz",
|
||||||
@@ -10044,6 +10174,59 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/vue-codemirror6": {
|
||||||
|
"version": "1.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/vue-codemirror6/-/vue-codemirror6-1.4.1.tgz",
|
||||||
|
"integrity": "sha512-mokK4q89TvxtGXzdEv3YyvfX3RJJs7VxyNjBNpdNbP+jpum/ttLvWB+TEh/ziVZq5mFrQazVPCfUV8TVI/Ji2A==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"vue-demi": "latest"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18",
|
||||||
|
"pnpm": ">=10.3.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@codemirror/autocomplete": "^6.0.0",
|
||||||
|
"@codemirror/commands": "^6.0.0",
|
||||||
|
"@codemirror/language": "^6.0.0",
|
||||||
|
"@codemirror/lint": "^6.0.0",
|
||||||
|
"@codemirror/search": "^6.0.0",
|
||||||
|
"@codemirror/state": "^6.0.0",
|
||||||
|
"@codemirror/view": "^6.0.0",
|
||||||
|
"codemirror": "^6.0.0",
|
||||||
|
"style-mod": "^4.0.0",
|
||||||
|
"vue": "^2.7.14 || ^3.3.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/vue-demi": {
|
||||||
|
"version": "0.14.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz",
|
||||||
|
"integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==",
|
||||||
|
"dev": true,
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"bin": {
|
||||||
|
"vue-demi-fix": "bin/vue-demi-fix.js",
|
||||||
|
"vue-demi-switch": "bin/vue-demi-switch.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/antfu"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@vue/composition-api": "^1.0.0-rc.1",
|
||||||
|
"vue": "^3.0.0-0 || ^2.6.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@vue/composition-api": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/vue-eslint-parser": {
|
"node_modules/vue-eslint-parser": {
|
||||||
"version": "9.3.2",
|
"version": "9.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-9.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-9.3.2.tgz",
|
||||||
@@ -10090,19 +10273,6 @@
|
|||||||
"vue": "^3.0.0"
|
"vue": "^3.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/vue-prism-editor": {
|
|
||||||
"version": "2.0.0-alpha.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/vue-prism-editor/-/vue-prism-editor-2.0.0-alpha.2.tgz",
|
|
||||||
"integrity": "sha512-Gu42ba9nosrE+gJpnAEuEkDMqG9zSUysIR8SdXUw8MQKDjBnnNR9lHC18uOr/ICz7yrA/5c7jHJr9lpElODC7w==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"vue": "^3.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/vue-qrcode": {
|
"node_modules/vue-qrcode": {
|
||||||
"version": "2.2.2",
|
"version": "2.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/vue-qrcode/-/vue-qrcode-2.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/vue-qrcode/-/vue-qrcode-2.2.2.tgz",
|
||||||
@@ -10146,6 +10316,13 @@
|
|||||||
"vue": "^3.0.2"
|
"vue": "^3.0.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/w3c-keyname": {
|
||||||
|
"version": "2.2.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz",
|
||||||
|
"integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/wait-on": {
|
"node_modules/wait-on": {
|
||||||
"version": "7.2.0",
|
"version": "7.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/wait-on/-/wait-on-7.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/wait-on/-/wait-on-7.2.0.tgz",
|
||||||
|
|||||||
13
package.json
13
package.json
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "dockge",
|
"name": "dockge",
|
||||||
"version": "1.4.2",
|
"version": "1.5.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 22.14.0"
|
"node": ">= 22.14.0"
|
||||||
@@ -19,8 +19,8 @@
|
|||||||
"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 -t louislam/dockge:beta -t louislam/dockge:nightly --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 -t louislam/dockge:beta -t louislam/dockge:nightly --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-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": "npm 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",
|
||||||
|
"release-nightly": "npm run build:frontend && docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/dockge:nightly -t ghcr.io/louislam/dockge:nightly --target nightly -f ./docker/Dockerfile . --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",
|
||||||
"mark-as-nightly": "tsx ./extra/mark-as-nightly.ts",
|
"mark-as-nightly": "tsx ./extra/mark-as-nightly.ts",
|
||||||
"reformat-changelog": "tsx ./extra/reformat-changelog.ts",
|
"reformat-changelog": "tsx ./extra/reformat-changelog.ts",
|
||||||
@@ -59,6 +59,8 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@actions/github": "^6.0.0",
|
"@actions/github": "^6.0.0",
|
||||||
|
"@codemirror/lang-python": "^6.1.7",
|
||||||
|
"@codemirror/lang-yaml": "^6.1.2",
|
||||||
"@fontsource/jetbrains-mono": "^5.2.5",
|
"@fontsource/jetbrains-mono": "^5.2.5",
|
||||||
"@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",
|
||||||
@@ -72,26 +74,27 @@
|
|||||||
"@types/semver": "^7.7.0",
|
"@types/semver": "^7.7.0",
|
||||||
"@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": "~5.2.3",
|
||||||
"@xterm/addon-fit": "beta",
|
"@xterm/addon-fit": "beta",
|
||||||
"@xterm/xterm": "beta",
|
"@xterm/xterm": "beta",
|
||||||
"bootstrap": "5.3.2",
|
"bootstrap": "5.3.2",
|
||||||
"bootstrap-vue-next": "~0.14.10",
|
"bootstrap-vue-next": "~0.14.10",
|
||||||
|
"codemirror": "^6.0.1",
|
||||||
"concurrently": "^8.2.2",
|
"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",
|
||||||
"eslint-plugin-vue": "~9.32.0",
|
"eslint-plugin-vue": "~9.32.0",
|
||||||
"prismjs": "~1.30.0",
|
|
||||||
"sass": "~1.68.0",
|
"sass": "~1.68.0",
|
||||||
|
"thememirror": "^2.0.1",
|
||||||
"typescript": "~5.2.2",
|
"typescript": "~5.2.2",
|
||||||
"unplugin-vue-components": "~0.25.2",
|
"unplugin-vue-components": "~0.25.2",
|
||||||
"vite": "~5.4.15",
|
"vite": "~5.4.15",
|
||||||
"vite-plugin-compression": "~0.5.1",
|
"vite-plugin-compression": "~0.5.1",
|
||||||
"vue": "~3.5.13",
|
"vue": "~3.5.13",
|
||||||
|
"vue-codemirror6": "^1.3.13",
|
||||||
"vue-eslint-parser": "~9.3.2",
|
"vue-eslint-parser": "~9.3.2",
|
||||||
"vue-i18n": "~10.0.6",
|
"vue-i18n": "~10.0.6",
|
||||||
"vue-prism-editor": "2.0.0-alpha.2",
|
|
||||||
"vue-qrcode": "~2.2.2",
|
"vue-qrcode": "~2.2.2",
|
||||||
"vue-router": "~4.5.0",
|
"vue-router": "~4.5.0",
|
||||||
"vue-toastification": "2.0.0-rc.5",
|
"vue-toastification": "2.0.0-rc.5",
|
||||||
|
|||||||
Reference in New Issue
Block a user