Compare commits

..

3 Commits

Author SHA1 Message Date
34af500b66 Force WebSocket 2023-11-25 21:18:26 +08:00
56eb46150f Force WebSocket 2023-11-25 20:55:05 +08:00
f6fffcc064 Always show Down button 2023-11-25 17:11:20 +08:00
60 changed files with 808 additions and 2737 deletions

View File

@ -1,4 +1,5 @@
labels: [help] title: "❓ Ask for help"
labels: [help]
body: body:
- type: checkboxes - type: checkboxes
id: no-duplicate-issues id: no-duplicate-issues

View File

@ -1,3 +1,4 @@
title: 🚀 Feature Request
labels: [feature-request] labels: [feature-request]
body: body:
- type: checkboxes - type: checkboxes
@ -51,4 +52,4 @@ body:
attributes: attributes:
label: "📝 Additional Context" label: "📝 Additional Context"
description: "Add any other context or screenshots about the feature request here." description: "Add any other context or screenshots about the feature request here."
placeholder: "..." placeholder: "..."

View File

@ -1,14 +1,14 @@
name: "⚠️ Ask for help (Please go to the \"Discussions\" tab to submit a Help Request)" name: " Ask for help"
description: "⚠️ Please go to the \"Discussions\" tab to submit a Help Request" description: "Please go to the Discussions tab to submit a Help Request"
body: body:
- type: markdown - type: markdown
attributes: attributes:
value: | value: |
⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ Please go to https://github.com/louislam/dockge/discussions/new?category=ask-for-help Please go to https://github.com/louislam/dockge/discussions/new?category=ask-for-help
- type: checkboxes - type: checkboxes
id: no-duplicate-issues id: no-duplicate-issues
attributes: attributes:
label: "Issues are for bug reports only, please go to the \"Discussions\" tab to submit a Feature Request" label: "Issues are for bug reports only"
options: options:
- label: "I understand" - label: "I understand"
required: true required: true

View File

@ -1,14 +1,14 @@
name: 🚀 Feature Request (Please go to the "Discussions" tab to submit a Feature Request) name: 🚀 Feature Request
description: "⚠️ Please go to the \"Discussions\" tab to submit a Feature Request" description: "Please go to the Discussions tab to submit a Feature Request"
body: body:
- type: markdown - type: markdown
attributes: attributes:
value: | value: |
⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ Please go to https://github.com/louislam/dockge/discussions/new?category=ask-for-help Please go to https://github.com/louislam/dockge/discussions/new?category=feature-request
- type: checkboxes - type: checkboxes
id: no-duplicate-issues id: no-duplicate-issues
attributes: attributes:
label: "Issues are for bug reports only, please go to the \"Discussions\" tab to submit a Feature Request" label: "Issues are for bug reports only"
options: options:
- label: "I understand" - label: "I understand"
required: true required: true

View File

@ -14,8 +14,8 @@ jobs:
ci: ci:
strategy: strategy:
matrix: matrix:
os: [ubuntu-latest, windows-latest, macos-latest, ARM64] os: [ubuntu-latest, windows-latest, macos-latest]
node: [18.17.1] # Can be changed node: [20.x] # Can be changed
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
steps: steps:
- name: Checkout Code - name: Checkout Code

View File

@ -1,16 +0,0 @@
name: Prevent File Change
on:
pull_request:
jobs:
check-file-changes:
runs-on: ubuntu-latest
steps:
- name: Prevent file change
uses: xalvarez/prevent-file-change-action@v1
with:
githubToken: ${{ secrets.GITHUB_TOKEN }}
# Regex, /src/lang/*.json is not allowed to be changed, except for /src/lang/en.json
pattern: '^(?!frontend/src/lang/en\.json$)frontend/src/lang/.*\.json$'
trustedAuthors: UptimeKumaBot

View File

@ -7,15 +7,13 @@ Here are some references:
### ✅ Usually accepted: ### ✅ Usually accepted:
- Bug fix - Bug fix
- Security fix - Security fix
- Adding new language files (see [these instructions](https://github.com/louislam/dockge/blob/master/frontend/src/lang/README.md)) - Translation
- Adding new language keys: `$t("...")`
### ⚠️ Discussion required: ### ⚠️ Discussion required:
- Large pull requests - Large pull requests
- New features - New features
### ❌ Won't be merged: ### ❌ Won't be merged:
- A dedicated PR for translating existing languages (see [these instructions](https://github.com/louislam/dockge/blob/master/frontend/src/lang/README.md))
- Do not pass the auto-test - Do not pass the auto-test
- Any breaking changes - Any breaking changes
- Duplicated pull requests - Duplicated pull requests

View File

@ -6,7 +6,7 @@
A fancy, easy-to-use and reactive self-hosted docker compose.yaml stack-oriented manager. A fancy, easy-to-use and reactive self-hosted docker compose.yaml stack-oriented manager.
![GitHub Repo stars](https://img.shields.io/github/stars/louislam/dockge?logo=github) ![Docker Pulls](https://img.shields.io/docker/pulls/louislam/dockge?logo=docker) ![Docker Image Version (latest semver)](https://img.shields.io/docker/v/louislam/dockge/latest?label=docker%20image%20ver.) ![GitHub last commit (branch)](https://img.shields.io/github/last-commit/louislam/dockge/master?logo=github) ![GitHub Repo stars](https://img.shields.io/github/stars/louislam/dockge?logo=github) ![GitHub issues](https://img.shields.io/github/issues/louislam/dockge?logo=github) ![GitHub pull requests](https://img.shields.io/github/issues-pr/louislam/dockge?logo=github) ![Docker Pulls](https://img.shields.io/docker/pulls/louislam/dockge?logo=docker) ![Docker Image Version (latest semver)](https://img.shields.io/docker/v/louislam/dockge/latest?label=docker%20image%20ver.) ![GitHub last commit (branch)](https://img.shields.io/github/last-commit/louislam/dockge/master?logo=github) ![GitHub](https://img.shields.io/github/license/louislam/dockge?logo=github)
<img src="https://github.com/louislam/dockge/assets/1336778/26a583e1-ecb1-4a8d-aedf-76157d714ad7" width="900" alt="" /> <img src="https://github.com/louislam/dockge/assets/1336778/26a583e1-ecb1-4a8d-aedf-76157d714ad7" width="900" alt="" />
@ -34,18 +34,12 @@ View Video: https://youtu.be/AWAlOQeNpgU?t=48
## 🔧 How to Install ## 🔧 How to Install
Requirements: Requirements:
- [Docker](https://docs.docker.com/engine/install/) 20+ / Podman - [Docker CE](https://docs.docker.com/engine/install/) 20+ is recommended / Podman
- (Docker only) [Docker Compose Plugin](https://docs.docker.com/compose/install/linux/)
- (Podman only) podman-docker (Debian: `apt install podman-docker`) - (Podman only) podman-docker (Debian: `apt install podman-docker`)
- OS: - OS:
- Major Linux distros that can run Docker/Podman such as: - As long as you can run Docker CE / Podman, it should be fine, but:
- ✅ Ubuntu - Debian/Raspbian Buster or lower is not supported, please upgrade to Bullseye or higher
- ✅ Debian (Bullseye or newer)
- ✅ Raspbian (Bullseye or newer)
- ✅ CentOS
- ✅ Fedora
- ✅ ArchLinux
- ❌ Debian/Raspbian Buster or lower is not supported
- ❌ Windows (Will be supported later)
- Arch: armv7, arm64, amd64 (a.k.a x86_64) - Arch: armv7, arm64, amd64 (a.k.a x86_64)
### Basic ### Basic
@ -54,14 +48,14 @@ Requirements:
- Default Port: 5001 - Default Port: 5001
``` ```
# Create directories that store your stacks and stores Dockge's stack # Create a directory that stores your stacks and stores dockge's compose.yaml
mkdir -p /opt/stacks /opt/dockge mkdir -p /opt/stacks /opt/dockge
cd /opt/dockge cd /opt/dockge
# Download the compose.yaml # Download the compose.yaml
curl https://raw.githubusercontent.com/louislam/dockge/master/compose.yaml --output compose.yaml curl https://raw.githubusercontent.com/louislam/dockge/master/compose.yaml --output compose.yaml
# Start the server # Start the Server
docker compose up -d docker compose up -d
# If you are using docker-compose V1 or Podman # If you are using docker-compose V1 or Podman
@ -72,24 +66,40 @@ Dockge is now running on http://localhost:5001
### Advanced ### Advanced
If you want to store your stacks in another directory, you can generate your compose.yaml file by using the following URL with custom query strings. If you want to store your stacks in another directory, you can change the `DOCKGE_STACKS_DIR` environment variable and volumes.
```yaml
version: "3.8"
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
# 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: - /my-stacks:/my-stacks (Both paths match)
# ❌❌❌❌ WRONG: - /docker:/my-stacks (Both paths do not match)
- /opt/stacks:/opt/stacks
environment:
# Tell Dockge where is your stacks directory
- DOCKGE_STACKS_DIR=/opt/stacks
``` ```
# Download your compose.yaml
curl "https://dockge.kuma.pet/compose.yaml?port=5001&stacksPath=/opt/stacks" --output compose.yaml
```
- port=`5001`
- stacksPath=`/opt/stacks`
Interactive compose.yaml generator is available on:
https://dockge.kuma.pet
## How to Update ## How to Update
```bash ```bash
cd /opt/dockge cd /opt/dockge
docker compose pull && docker compose up -d docker compose pull
docker compose up -d
``` ```
## Screenshots ## Screenshots
@ -112,7 +122,7 @@ docker compose pull && docker compose up -d
If you love this project, please consider giving it a ⭐. If you love this project, please consider giving it a ⭐.
## 🗣️ Community and Contribution ## 🗣️
### Bug Report ### Bug Report
https://github.com/louislam/dockge/issues https://github.com/louislam/dockge/issues
@ -120,18 +130,15 @@ https://github.com/louislam/dockge/issues
### Ask for Help / Discussions ### Ask for Help / Discussions
https://github.com/louislam/dockge/discussions https://github.com/louislam/dockge/discussions
### Translation ## Translation
If you want to translate Dockge into your language, please read [Translation Guide](https://github.com/louislam/dockge/blob/master/frontend/src/lang/README.md) If you want to translate Dockge into your language, please read [Translation Guide](https://github.com/louislam/dockge/blob/master/frontend/src/lang/README.md)
### Create a Pull Request
Be sure to read the [guide](https://github.com/louislam/dockge/blob/master/CONTRIBUTING.md), as we don't accept all types of pull requests and don't want to waste your time.
## FAQ ## FAQ
#### "Dockge"? #### "Dockge"?
"Dockge" is a coinage word which is created by myself. I originally hoped it sounds like `Dodge`, but apparently many people called it `Dockage`, it is also acceptable. "Dockge" is a coinage word which is created by myself. I hope it sounds like `Dodge`.
The naming idea came from Twitch emotes like `sadge`, `bedge` or `wokege`. They all end in `-ge`. The naming idea came from Twitch emotes like `sadge`, `bedge` or `wokege`. They all end in `-ge`.
@ -148,18 +155,17 @@ Yes, you can. However, you need to move your compose file into the stacks direct
3. In Dockge, click the " Scan Stacks Folder" button in the top-right corner's dropdown menu 3. In Dockge, click the " Scan Stacks Folder" button in the top-right corner's dropdown menu
4. Now you should see your stack in the list 4. Now you should see your stack in the list
#### Is Dockge a Portainer replacement? ## More Ideas?
Yes or no. Portainer provides a lot of Docker features. While Dockge is currently only focusing on docker-compose with a better user interface and better user experience. - Stats
- File manager
- App store for yaml templates
- Get app icons
- Switch Docker context
- Support Dockerfile and build
- Support Docker swarm
If you want to manage your container with docker-compose only, the answer may be yes.
If you still need to manage something like docker networks, single containers, the answer may be no. # Others
#### Can I install both Dockge and Portainer?
Yes, you can.
## Others
Dockge is built on top of [Compose V2](https://docs.docker.com/compose/migrate/). `compose.yaml` also known as `docker-compose.yml`. Dockge is built on top of [Compose V2](https://docs.docker.com/compose/migrate/). `compose.yaml` also known as `docker-compose.yml`.

View File

@ -3,55 +3,69 @@ import compareVersions from "compare-versions";
import packageJSON from "../package.json"; import packageJSON from "../package.json";
import { Settings } from "./settings"; import { Settings } from "./settings";
export const obj = {
version: packageJSON.version,
latestVersion: null,
};
export default obj;
// How much time in ms to wait between update checks // How much time in ms to wait between update checks
const UPDATE_CHECKER_INTERVAL_MS = 1000 * 60 * 60 * 48; const UPDATE_CHECKER_INTERVAL_MS = 1000 * 60 * 60 * 48;
const CHECK_URL = "https://dockge.kuma.pet/version"; const CHECK_URL = "https://dockge.kuma.pet/version";
class CheckVersion { let interval : NodeJS.Timeout;
version = packageJSON.version;
latestVersion? : string;
interval? : NodeJS.Timeout;
async startInterval() { export function startInterval() {
const check = async () => { const check = async () => {
if (await Settings.get("checkUpdate") === false) { if (await Settings.get("checkUpdate") === false) {
return; return;
}
log.debug("update-checker", "Retrieving latest versions");
try {
const res = await fetch(CHECK_URL);
const data = await res.json();
// For debug
if (process.env.TEST_CHECK_VERSION === "1") {
data.slow = "1000.0.0";
} }
log.debug("update-checker", "Retrieving latest versions"); const checkBeta = await Settings.get("checkBeta");
try { if (checkBeta && data.beta) {
const res = await fetch(CHECK_URL); if (compareVersions.compare(data.beta, data.slow, ">")) {
const data = await res.json(); obj.latestVersion = data.beta;
return;
// For debug
if (process.env.TEST_CHECK_VERSION === "1") {
data.slow = "1000.0.0";
} }
const checkBeta = await Settings.get("checkBeta");
if (checkBeta && data.beta) {
if (compareVersions.compare(data.beta, data.slow, ">")) {
this.latestVersion = data.beta;
return;
}
}
if (data.slow) {
this.latestVersion = data.slow;
}
} catch (_) {
log.info("update-checker", "Failed to check for new versions");
} }
}; if (data.slow) {
obj.latestVersion = data.slow;
}
await check(); } catch (_) {
this.interval = setInterval(check, UPDATE_CHECKER_INTERVAL_MS); log.info("update-checker", "Failed to check for new versions");
} }
};
check();
interval = setInterval(check, UPDATE_CHECKER_INTERVAL_MS);
} }
const checkVersion = new CheckVersion(); /**
export default checkVersion; * Enable the check update feature
* @param value Should the check update feature be enabled?
* @returns
*/
export async function enableCheckUpdate(value : boolean) {
await Settings.set("checkUpdate", value);
clearInterval(interval);
if (value) {
startInterval();
}
}

3
backend/docker.ts Normal file
View File

@ -0,0 +1,3 @@
export class Docker {
}

View File

@ -29,11 +29,9 @@ import { Stack } from "./stack";
import { Cron } from "croner"; import { Cron } from "croner";
import gracefulShutdown from "http-graceful-shutdown"; import gracefulShutdown from "http-graceful-shutdown";
import User from "./models/user"; import User from "./models/user";
import childProcessAsync from "promisify-child-process"; import childProcess from "child_process";
import { Terminal } from "./terminal"; import { Terminal } from "./terminal";
import "dotenv/config";
export class DockgeServer { export class DockgeServer {
app : Express; app : Express;
httpServer : http.Server; httpServer : http.Server;
@ -152,6 +150,9 @@ export class DockgeServer {
} }
} }
// Create all the necessary directories
this.initDataDir();
// Create express // Create express
this.app = express(); this.app = express();
@ -194,39 +195,7 @@ export class DockgeServer {
// Create Socket.io // Create Socket.io
this.io = new socketIO.Server(this.httpServer, { this.io = new socketIO.Server(this.httpServer, {
cors, cors,
allowRequest: (req, callback) => { transports: [ "websocket" ],
let isOriginValid = true;
const bypass = isDev;
if (!bypass) {
let host = req.headers.host;
// If this is set, it means the request is from the browser
let origin = req.headers.origin;
// If this is from the browser, check if the origin is allowed
if (origin) {
try {
let originURL = new URL(origin);
if (host !== originURL.host) {
isOriginValid = false;
log.error("auth", `Origin (${origin}) does not match host (${host}), IP: ${req.socket.remoteAddress}`);
}
} catch (e) {
// Invalid origin url, probably not from browser
isOriginValid = false;
log.error("auth", `Invalid origin url (${origin}), IP: ${req.socket.remoteAddress}`);
}
} else {
log.info("auth", `Origin is not set, IP: ${req.socket.remoteAddress}`);
}
} else {
log.debug("auth", "Origin check is bypassed");
}
callback(null, isOriginValid);
}
}); });
this.io.on("connection", async (socket: Socket) => { this.io.on("connection", async (socket: Socket) => {
@ -287,9 +256,6 @@ export class DockgeServer {
* *
*/ */
async serve() { async serve() {
// Create all the necessary directories
this.initDataDir();
// Connect to database // Connect to database
try { try {
await Database.init(this); await Database.init(this);
@ -341,7 +307,6 @@ export class DockgeServer {
this.sendStackList(true); this.sendStackList(true);
}); });
checkVersion.startInterval();
}); });
gracefulShutdown(this.httpServer, { gracefulShutdown(this.httpServer, {
@ -519,7 +484,7 @@ export class DockgeServer {
return jwtSecretBean; return jwtSecretBean;
} }
async sendStackList(useCache = false) { sendStackList(useCache = false) {
let roomList = this.io.sockets.adapter.rooms.keys(); let roomList = this.io.sockets.adapter.rooms.keys();
let map : Map<string, object> | undefined; let map : Map<string, object> | undefined;
@ -530,7 +495,7 @@ export class DockgeServer {
// Get the list only if there is a room // Get the list only if there is a room
if (!map) { if (!map) {
map = new Map(); map = new Map();
let stackList = await Stack.getStackList(this, useCache); let stackList = Stack.getStackList(this, useCache);
for (let [ stackName, stack ] of stackList) { for (let [ stackName, stack ] of stackList) {
map.set(stackName, stack.toSimpleJSON()); map.set(stackName, stack.toSimpleJSON());
@ -546,8 +511,8 @@ export class DockgeServer {
} }
} }
async sendStackStatusList() { sendStackStatusList() {
let statusList = await Stack.getStatusList(); let statusList = Stack.getStatusList();
let roomList = this.io.sockets.adapter.rooms.keys(); let roomList = this.io.sockets.adapter.rooms.keys();
@ -565,15 +530,8 @@ export class DockgeServer {
} }
} }
async getDockerNetworkList() : Promise<string[]> { getDockerNetworkList() : string[] {
let res = await childProcessAsync.spawn("docker", [ "network", "ls", "--format", "{{.Name}}" ], { let res = childProcess.spawnSync("docker", [ "network", "ls", "--format", "{{.Name}}" ]);
encoding: "utf-8",
});
if (!res.stdout) {
return [];
}
let list = res.stdout.toString().split("\n"); let list = res.stdout.toString().split("\n");
// Remove empty string item // Remove empty string item
@ -611,35 +569,4 @@ export class DockgeServer {
finalFunction() { finalFunction() {
log.info("server", "Graceful shutdown successful!"); log.info("server", "Graceful shutdown successful!");
} }
/**
* Force connected sockets of a user to refresh and disconnect.
* Used for resetting password.
* @param {string} userID
* @param {string?} currentSocketID
*/
disconnectAllSocketClients(userID: number, currentSocketID? : string) {
for (const rawSocket of this.io.sockets.sockets.values()) {
let socket = rawSocket as DockgeSocket;
if (socket.userID === userID && socket.id !== currentSocketID) {
try {
socket.emit("refresh");
socket.disconnect();
} catch (e) {
}
}
}
}
isSSL() {
return this.config.sslKey && this.config.sslCert;
}
getLocalWebSocketURL() {
const protocol = this.isSSL() ? "wss" : "ws";
const host = this.config.hostname || "localhost";
return `${protocol}://${host}:${this.config.port}`;
}
} }

View File

@ -9,10 +9,10 @@ import composerize from "composerize";
export class DockerSocketHandler extends SocketHandler { export class DockerSocketHandler extends SocketHandler {
create(socket : DockgeSocket, server : DockgeServer) { create(socket : DockgeSocket, server : DockgeServer) {
socket.on("deployStack", async (name : unknown, composeYAML : unknown, composeENV : unknown, isAdd : unknown, callback) => { socket.on("deployStack", async (name : unknown, composeYAML : unknown, isAdd : unknown, callback) => {
try { try {
checkLogin(socket); checkLogin(socket);
const stack = await this.saveStack(socket, server, name, composeYAML, composeENV, isAdd); const stack = this.saveStack(socket, server, name, composeYAML, isAdd);
await stack.deploy(socket); await stack.deploy(socket);
server.sendStackList(); server.sendStackList();
callback({ callback({
@ -25,10 +25,10 @@ export class DockerSocketHandler extends SocketHandler {
} }
}); });
socket.on("saveStack", async (name : unknown, composeYAML : unknown, composeENV : unknown, isAdd : unknown, callback) => { socket.on("saveStack", async (name : unknown, composeYAML : unknown, isAdd : unknown, callback) => {
try { try {
checkLogin(socket); checkLogin(socket);
this.saveStack(socket, server, name, composeYAML, composeENV, isAdd); this.saveStack(socket, server, name, composeYAML, isAdd);
callback({ callback({
ok: true, ok: true,
"msg": "Saved" "msg": "Saved"
@ -45,7 +45,7 @@ export class DockerSocketHandler extends SocketHandler {
if (typeof(name) !== "string") { if (typeof(name) !== "string") {
throw new ValidationError("Name must be a string"); throw new ValidationError("Name must be a string");
} }
const stack = await Stack.getStack(server, name); const stack = Stack.getStack(server, name);
try { try {
await stack.delete(socket); await stack.delete(socket);
@ -65,7 +65,7 @@ export class DockerSocketHandler extends SocketHandler {
} }
}); });
socket.on("getStack", async (stackName : unknown, callback) => { socket.on("getStack", (stackName : unknown, callback) => {
try { try {
checkLogin(socket); checkLogin(socket);
@ -73,7 +73,7 @@ export class DockerSocketHandler extends SocketHandler {
throw new ValidationError("Stack name must be a string"); throw new ValidationError("Stack name must be a string");
} }
const stack = await Stack.getStack(server, stackName); const stack = Stack.getStack(server, stackName);
if (stack.isManagedByDockge) { if (stack.isManagedByDockge) {
stack.joinCombinedTerminal(socket); stack.joinCombinedTerminal(socket);
@ -111,7 +111,7 @@ export class DockerSocketHandler extends SocketHandler {
throw new ValidationError("Stack name must be a string"); throw new ValidationError("Stack name must be a string");
} }
const stack = await Stack.getStack(server, stackName); const stack = Stack.getStack(server, stackName);
await stack.start(socket); await stack.start(socket);
callback({ callback({
ok: true, ok: true,
@ -135,7 +135,7 @@ export class DockerSocketHandler extends SocketHandler {
throw new ValidationError("Stack name must be a string"); throw new ValidationError("Stack name must be a string");
} }
const stack = await Stack.getStack(server, stackName); const stack = Stack.getStack(server, stackName);
await stack.stop(socket); await stack.stop(socket);
callback({ callback({
ok: true, ok: true,
@ -156,7 +156,7 @@ export class DockerSocketHandler extends SocketHandler {
throw new ValidationError("Stack name must be a string"); throw new ValidationError("Stack name must be a string");
} }
const stack = await Stack.getStack(server, stackName); const stack = Stack.getStack(server, stackName);
await stack.restart(socket); await stack.restart(socket);
callback({ callback({
ok: true, ok: true,
@ -177,7 +177,7 @@ export class DockerSocketHandler extends SocketHandler {
throw new ValidationError("Stack name must be a string"); throw new ValidationError("Stack name must be a string");
} }
const stack = await Stack.getStack(server, stackName); const stack = Stack.getStack(server, stackName);
await stack.update(socket); await stack.update(socket);
callback({ callback({
ok: true, ok: true,
@ -198,7 +198,7 @@ export class DockerSocketHandler extends SocketHandler {
throw new ValidationError("Stack name must be a string"); throw new ValidationError("Stack name must be a string");
} }
const stack = await Stack.getStack(server, stackName); const stack = Stack.getStack(server, stackName);
await stack.down(socket); await stack.down(socket);
callback({ callback({
ok: true, ok: true,
@ -219,7 +219,7 @@ export class DockerSocketHandler extends SocketHandler {
throw new ValidationError("Stack name must be a string"); throw new ValidationError("Stack name must be a string");
} }
const stack = await Stack.getStack(server, stackName, true); const stack = Stack.getStack(server, stackName, true);
const serviceStatusList = Object.fromEntries(await stack.getServiceStatusList()); const serviceStatusList = Object.fromEntries(await stack.getServiceStatusList());
callback({ callback({
ok: true, ok: true,
@ -234,7 +234,7 @@ export class DockerSocketHandler extends SocketHandler {
socket.on("getDockerNetworkList", async (callback) => { socket.on("getDockerNetworkList", async (callback) => {
try { try {
checkLogin(socket); checkLogin(socket);
const dockerNetworkList = await server.getDockerNetworkList(); const dockerNetworkList = server.getDockerNetworkList();
callback({ callback({
ok: true, ok: true,
dockerNetworkList, dockerNetworkList,
@ -264,7 +264,7 @@ export class DockerSocketHandler extends SocketHandler {
}); });
} }
async saveStack(socket : DockgeSocket, server : DockgeServer, name : unknown, composeYAML : unknown, composeENV : unknown, isAdd : unknown) : Promise<Stack> { saveStack(socket : DockgeSocket, server : DockgeServer, name : unknown, composeYAML : unknown, isAdd : unknown) : Stack {
// Check types // Check types
if (typeof(name) !== "string") { if (typeof(name) !== "string") {
throw new ValidationError("Name must be a string"); throw new ValidationError("Name must be a string");
@ -272,15 +272,12 @@ export class DockerSocketHandler extends SocketHandler {
if (typeof(composeYAML) !== "string") { if (typeof(composeYAML) !== "string") {
throw new ValidationError("Compose YAML must be a string"); throw new ValidationError("Compose YAML must be a string");
} }
if (typeof(composeENV) !== "string") {
throw new ValidationError("Compose ENV must be a string");
}
if (typeof(isAdd) !== "boolean") { if (typeof(isAdd) !== "boolean") {
throw new ValidationError("isAdd must be a boolean"); throw new ValidationError("isAdd must be a boolean");
} }
const stack = new Stack(server, name, composeYAML, composeENV, false); const stack = new Stack(server, name, composeYAML);
await stack.save(isAdd); stack.save(isAdd);
return stack; return stack;
} }

View File

@ -211,8 +211,6 @@ export class MainSocketHandler extends SocketHandler {
let user = await doubleCheckPassword(socket, password.currentPassword); let user = await doubleCheckPassword(socket, password.currentPassword);
await user.resetPassword(password.newPassword); await user.resetPassword(password.newPassword);
server.disconnectAllSocketClients(user.id, socket.id);
callback({ callback({
ok: true, ok: true,
msg: "Password has been updated successfully.", msg: "Password has been updated successfully.",
@ -282,18 +280,6 @@ export class MainSocketHandler extends SocketHandler {
} }
} }
}); });
// Disconnect all other socket clients of the user
socket.on("disconnectOtherSocketClients", async () => {
try {
checkLogin(socket);
server.disconnectAllSocketClients(socket.userID, socket.id);
} catch (e) {
if (e instanceof Error) {
log.warn("disconnectOtherSocketClients", e.message);
}
}
});
} }
async login(username : string, password : string) : Promise<User | null> { async login(username : string, password : string) : Promise<User | null> {

View File

@ -101,7 +101,7 @@ export class TerminalSocketHandler extends SocketHandler {
log.debug("interactiveTerminal", "Service name: " + serviceName); log.debug("interactiveTerminal", "Service name: " + serviceName);
// Get stack // Get stack
const stack = await Stack.getStack(server, stackName); const stack = Stack.getStack(server, stackName);
stack.joinContainerTerminal(socket, serviceName, shell); stack.joinContainerTerminal(socket, serviceName, shell);
callback({ callback({
@ -151,7 +151,7 @@ export class TerminalSocketHandler extends SocketHandler {
throw new ValidationError("Stack name must be a string."); throw new ValidationError("Stack name must be a string.");
} }
const stack = await Stack.getStack(server, stackName); const stack = Stack.getStack(server, stackName);
await stack.leaveCombinedTerminal(socket); await stack.leaveCombinedTerminal(socket);
callback({ callback({
@ -162,44 +162,9 @@ export class TerminalSocketHandler extends SocketHandler {
} }
}); });
// Resize Terminal // TODO: Resize Terminal
socket.on( socket.on("terminalResize", async (rows : unknown) => {
"terminalResize",
async (terminalName: unknown, rows: unknown, cols: unknown) => {
log.info("terminalResize", `Terminal: ${terminalName}`);
try {
checkLogin(socket);
if (typeof terminalName !== "string") {
throw new Error("Terminal name must be a string.");
}
if (typeof rows !== "number") { });
throw new Error("Command must be a number.");
}
if (typeof cols !== "number") {
throw new Error("Command must be a number.");
}
let terminal = Terminal.getTerminal(terminalName);
// log.info("terminal", terminal);
if (terminal instanceof Terminal) {
//log.debug("terminalInput", "Terminal found, writing to terminal.");
terminal.rows = rows;
terminal.cols = cols;
} else {
throw new Error(`${terminalName} Terminal not found.`);
}
} catch (e) {
log.debug(
"terminalResize",
// Added to prevent the lint error when adding the type
// and ts type checker saying type is unknown.
// @ts-ignore
`Error on ${terminalName}: ${e.message}`
);
}
}
);
} }
} }

View File

@ -1,11 +1,10 @@
import { DockgeServer } from "./dockge-server"; import { DockgeServer } from "./dockge-server";
import fs, { promises as fsAsync } from "fs"; import fs from "fs";
import { log } from "./log"; import { log } from "./log";
import yaml from "yaml"; import yaml from "yaml";
import { DockgeSocket, fileExists, ValidationError } from "./util-server"; import { DockgeSocket, ValidationError } from "./util-server";
import path from "path"; import path from "path";
import { import {
acceptedComposeFileNames,
COMBINED_TERMINAL_COLS, COMBINED_TERMINAL_COLS,
COMBINED_TERMINAL_ROWS, COMBINED_TERMINAL_ROWS,
CREATED_FILE, CREATED_FILE,
@ -17,14 +16,13 @@ import {
UNKNOWN UNKNOWN
} from "./util-common"; } from "./util-common";
import { InteractiveTerminal, Terminal } from "./terminal"; import { InteractiveTerminal, Terminal } from "./terminal";
import childProcessAsync from "promisify-child-process"; import childProcess from "child_process";
export class Stack { export class Stack {
name: string; name: string;
protected _status: number = UNKNOWN; protected _status: number = UNKNOWN;
protected _composeYAML?: string; protected _composeYAML?: string;
protected _composeENV?: string;
protected _configFilePath?: string; protected _configFilePath?: string;
protected _composeFileName: string = "compose.yaml"; protected _composeFileName: string = "compose.yaml";
protected server: DockgeServer; protected server: DockgeServer;
@ -33,15 +31,15 @@ export class Stack {
protected static managedStackList: Map<string, Stack> = new Map(); protected static managedStackList: Map<string, Stack> = new Map();
constructor(server : DockgeServer, name : string, composeYAML? : string, composeENV? : string, skipFSOperations = false) { constructor(server : DockgeServer, name : string, composeYAML? : string, skipFSOperations = false) {
this.name = name; this.name = name;
this.server = server; this.server = server;
this._composeYAML = composeYAML; this._composeYAML = composeYAML;
this._composeENV = composeENV;
if (!skipFSOperations) { if (!skipFSOperations) {
// Check if compose file name is different from compose.yaml // Check if compose file name is different from compose.yaml
for (const filename of acceptedComposeFileNames) { const supportedFileNames = [ "compose.yaml", "compose.yml", "docker-compose.yml", "docker-compose.yaml" ];
for (const filename of supportedFileNames) {
if (fs.existsSync(path.join(this.path, filename))) { if (fs.existsSync(path.join(this.path, filename))) {
this._composeFileName = filename; this._composeFileName = filename;
break; break;
@ -55,7 +53,6 @@ export class Stack {
return { return {
...obj, ...obj,
composeYAML: this.composeYAML, composeYAML: this.composeYAML,
composeENV: this.composeENV,
}; };
} }
@ -72,15 +69,11 @@ 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> { ps() : object {
let res = await childProcessAsync.spawn("docker", [ "compose", "ps", "--format", "json" ], { let res = childProcess.execSync("docker compose ps --format json", {
cwd: this.path, cwd: this.path
encoding: "utf-8",
}); });
if (!res.stdout) { return JSON.parse(res.toString());
return {};
}
return JSON.parse(res.stdout.toString());
} }
get isManagedByDockge() : boolean { get isManagedByDockge() : boolean {
@ -99,15 +92,6 @@ export class Stack {
// Check YAML format // Check YAML format
yaml.parse(this.composeYAML); yaml.parse(this.composeYAML);
let lines = this.composeENV.split("\n");
// Check if the .env is able to pass docker-compose
// Prevent "setenv: The parameter is incorrect"
// It only happens when there is one line and it doesn't contain "="
if (lines.length === 1 && !lines[0].includes("=") && lines[0].length > 0) {
throw new ValidationError("Invalid .env format");
}
} }
get composeYAML() : string { get composeYAML() : string {
@ -121,17 +105,6 @@ export class Stack {
return this._composeYAML; return this._composeYAML;
} }
get composeENV() : string {
if (this._composeENV === undefined) {
try {
this._composeENV = fs.readFileSync(path.join(this.path, ".env"), "utf-8");
} catch (e) {
this._composeENV = "";
}
}
return this._composeENV;
}
get path() : string { get path() : string {
return path.join(this.server.stacksDir, this.name); return path.join(this.server.stacksDir, this.name);
} }
@ -155,35 +128,27 @@ export class Stack {
* Save the stack to the disk * Save the stack to the disk
* @param isAdd * @param isAdd
*/ */
async save(isAdd : boolean) { save(isAdd : boolean) {
this.validate(); this.validate();
let dir = this.path; let dir = this.path;
// Check if the name is used if isAdd // Check if the name is used if isAdd
if (isAdd) { if (isAdd) {
if (await fileExists(dir)) { if (fs.existsSync(dir)) {
throw new ValidationError("Stack name already exists"); throw new ValidationError("Stack name already exists");
} }
// Create the stack folder // Create the stack folder
await fsAsync.mkdir(dir); fs.mkdirSync(dir);
} else { } else {
if (!await fileExists(dir)) { if (!fs.existsSync(dir)) {
throw new ValidationError("Stack not found"); throw new ValidationError("Stack not found");
} }
} }
// 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);
const envPath = path.join(dir, ".env");
// Write or overwrite the .env
// If .env is not existing and the composeENV is empty, we don't need to write it
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> {
@ -203,7 +168,7 @@ export class Stack {
} }
// Remove the stack folder // Remove the stack folder
await fsAsync.rm(this.path, { fs.rmSync(this.path, {
recursive: true, recursive: true,
force: true force: true
}); });
@ -211,8 +176,8 @@ export class Stack {
return exitCode; return exitCode;
} }
async updateStatus() { updateStatus() {
let statusList = await Stack.getStatusList(); let statusList = Stack.getStatusList();
let status = statusList.get(this.name); let status = statusList.get(this.name);
if (status) { if (status) {
@ -222,27 +187,7 @@ export class Stack {
} }
} }
/** static getStackList(server : DockgeServer, useCacheForManaged = false) : Map<string, Stack> {
* Checks if a compose file exists in the specified directory.
* @async
* @static
* @param {string} stacksDir - The directory of the stack.
* @param {string} filename - The name of the directory to check for the compose file.
* @returns {Promise<boolean>} A promise that resolves to a boolean indicating whether any compose file exists.
*/
static async composeFileExists(stacksDir : string, filename : string) : Promise<boolean> {
let filenamePath = path.join(stacksDir, filename);
// Check if any compose file exists
for (const filename of acceptedComposeFileNames) {
let composeFile = path.join(filenamePath, filename);
if (await fileExists(composeFile)) {
return true;
}
}
return false;
}
static async getStackList(server : DockgeServer, useCacheForManaged = false) : Promise<Map<string, Stack>> {
let stacksDir = server.stacksDir; let stacksDir = server.stacksDir;
let stackList : Map<string, Stack>; let stackList : Map<string, Stack>;
@ -253,20 +198,16 @@ export class Stack {
stackList = new Map<string, Stack>(); stackList = new Map<string, Stack>();
// Scan the stacks directory, and get the stack list // Scan the stacks directory, and get the stack list
let filenameList = await fsAsync.readdir(stacksDir); let filenameList = fs.readdirSync(stacksDir);
for (let filename of filenameList) { for (let filename of filenameList) {
try { try {
// Check if it is a directory // Check if it is a directory
let stat = await fsAsync.stat(path.join(stacksDir, filename)); let stat = fs.statSync(path.join(stacksDir, filename));
if (!stat.isDirectory()) { if (!stat.isDirectory()) {
continue; continue;
} }
// If no compose file exists, skip it let stack = this.getStack(server, filename);
if (!await Stack.composeFileExists(stacksDir, filename)) {
continue;
}
let stack = await this.getStack(server, filename);
stack._status = CREATED_FILE; stack._status = CREATED_FILE;
stackList.set(filename, stack); stackList.set(filename, stack);
} catch (e) { } catch (e) {
@ -281,15 +222,8 @@ export class Stack {
} }
// Get status from docker compose ls // Get status from docker compose ls
let res = await childProcessAsync.spawn("docker", [ "compose", "ls", "--all", "--format", "json" ], { let res = childProcess.execSync("docker compose ls --all --format json");
encoding: "utf-8", let composeList = JSON.parse(res.toString());
});
if (!res.stdout) {
return stackList;
}
let composeList = JSON.parse(res.stdout.toString());
for (let composeStack of composeList) { for (let composeStack of composeList) {
let stack = stackList.get(composeStack.Name); let stack = stackList.get(composeStack.Name);
@ -315,18 +249,11 @@ export class Stack {
* Get the status list, it will be used to update the status of the stacks * Get the status list, it will be used to update the status of the stacks
* Not all status will be returned, only the stack that is deployed or created to `docker compose` will be returned * Not all status will be returned, only the stack that is deployed or created to `docker compose` will be returned
*/ */
static async getStatusList() : Promise<Map<string, number>> { static getStatusList() : Map<string, number> {
let statusList = new Map<string, number>(); let statusList = new Map<string, number>();
let res = await childProcessAsync.spawn("docker", [ "compose", "ls", "--all", "--format", "json" ], { let res = childProcess.execSync("docker compose ls --all --format json");
encoding: "utf-8", let composeList = JSON.parse(res.toString());
});
if (!res.stdout) {
return statusList;
}
let composeList = JSON.parse(res.stdout.toString());
for (let composeStack of composeList) { for (let composeStack of composeList) {
statusList.set(composeStack.Name, this.statusConvert(composeStack.Status)); statusList.set(composeStack.Name, this.statusConvert(composeStack.Status));
@ -354,13 +281,13 @@ export class Stack {
} }
} }
static async getStack(server: DockgeServer, stackName: string, skipFSOperations = false) : Promise<Stack> { static getStack(server: DockgeServer, stackName: string, skipFSOperations = false) : Stack {
let dir = path.join(server.stacksDir, stackName); let dir = path.join(server.stacksDir, stackName);
if (!skipFSOperations) { if (!skipFSOperations) {
if (!await fileExists(dir) || !(await fsAsync.stat(dir)).isDirectory()) { if (!fs.existsSync(dir) || !fs.statSync(dir).isDirectory()) {
// Maybe it is a stack managed by docker compose directly // Maybe it is a stack managed by docker compose directly
let stackList = await this.getStackList(server, true); let stackList = this.getStackList(server, true);
let stack = stackList.get(stackName); let stack = stackList.get(stackName);
if (stack) { if (stack) {
@ -379,7 +306,7 @@ export class Stack {
if (!skipFSOperations) { if (!skipFSOperations) {
stack = new Stack(server, stackName); stack = new Stack(server, stackName);
} else { } else {
stack = new Stack(server, stackName, undefined, undefined, true); stack = new Stack(server, stackName, undefined, true);
} }
stack._status = UNKNOWN; stack._status = UNKNOWN;
@ -431,7 +358,7 @@ export class Stack {
} }
// If the stack is not running, we don't need to restart it // If the stack is not running, we don't need to restart it
await this.updateStatus(); this.updateStatus();
log.debug("update", "Status: " + this.status); log.debug("update", "Status: " + this.status);
if (this.status !== RUNNING) { if (this.status !== RUNNING) {
return exitCode; return exitCode;
@ -479,35 +406,24 @@ export class Stack {
async getServiceStatusList() { async getServiceStatusList() {
let statusList = new Map<string, number>(); let statusList = new Map<string, number>();
try { let res = childProcess.spawnSync("docker", [ "compose", "ps", "--format", "json" ], {
let res = await childProcessAsync.spawn("docker", [ "compose", "ps", "--format", "json" ], { cwd: this.path,
cwd: this.path, });
encoding: "utf-8",
});
if (!res.stdout) { let lines = res.stdout.toString().split("\n");
return statusList;
}
let lines = res.stdout?.toString().split("\n"); for (let line of lines) {
try {
for (let line of lines) { let obj = JSON.parse(line);
try { if (obj.Health === "") {
let obj = JSON.parse(line); statusList.set(obj.Service, obj.State);
if (obj.Health === "") { } else {
statusList.set(obj.Service, obj.State); statusList.set(obj.Service, obj.Health);
} else {
statusList.set(obj.Service, obj.Health);
}
} catch (e) {
} }
} catch (e) {
} }
return statusList;
} catch (e) {
log.error("getServiceStatusList", e);
return statusList;
} }
return statusList;
} }
} }

View File

@ -5,6 +5,8 @@ import { LimitQueue } from "./utils/limit-queue";
import { DockgeSocket } from "./util-server"; import { DockgeSocket } from "./util-server";
import { import {
allowedCommandList, allowedRawKeys, allowedCommandList, allowedRawKeys,
getComposeTerminalName,
getCryptoRandomInt,
PROGRESS_TERMINAL_ROWS, PROGRESS_TERMINAL_ROWS,
TERMINAL_COLS, TERMINAL_COLS,
TERMINAL_ROWS TERMINAL_ROWS
@ -67,7 +69,6 @@ export class Terminal {
set cols(cols : number) { set cols(cols : number) {
this._cols = cols; this._cols = cols;
log.debug("Terminal", `Terminal cols: ${this._cols}`); // Added to check if cols is being set when changing terminal size.
try { try {
this.ptyProcess?.resize(this.cols, this.rows); this.ptyProcess?.resize(this.cols, this.rows);
} catch (e) { } catch (e) {
@ -206,20 +207,14 @@ export class Terminal {
} }
public static exec(server : DockgeServer, socket : DockgeSocket | undefined, terminalName : string, file : string, args : string | string[], cwd : string) : Promise<number> { public static exec(server : DockgeServer, socket : DockgeSocket | undefined, terminalName : string, file : string, args : string | string[], cwd : string) : Promise<number> {
return new Promise((resolve, reject) => { const terminal = new Terminal(server, terminalName, file, args, cwd);
// check if terminal exists terminal.rows = PROGRESS_TERMINAL_ROWS;
if (Terminal.terminalMap.has(terminalName)) {
reject("Another operation is already running, please try again later.");
return;
}
let terminal = new Terminal(server, terminalName, file, args, cwd); if (socket) {
terminal.rows = PROGRESS_TERMINAL_ROWS; terminal.join(socket);
}
if (socket) {
terminal.join(socket);
}
return new Promise((resolve) => {
terminal.onExit((exitCode : number) => { terminal.onExit((exitCode : number) => {
resolve(exitCode); resolve(exitCode);
}); });

View File

@ -1,17 +1,13 @@
/* /*
* Common utilities for backend and frontend * Common utilities for backend and frontend
*/ */
import yaml, { Document, Pair, Scalar } from "yaml"; import { Document } from "yaml";
import { DotenvParseOutput } from "dotenv";
// Init dayjs // Init dayjs
import dayjs from "dayjs"; import dayjs from "dayjs";
import timezone from "dayjs/plugin/timezone"; import timezone from "dayjs/plugin/timezone";
import utc from "dayjs/plugin/utc"; import utc from "dayjs/plugin/utc";
import relativeTime from "dayjs/plugin/relativeTime"; import relativeTime from "dayjs/plugin/relativeTime";
// @ts-ignore
import { replaceVariablesSync } from "@inventage/envsubst";
dayjs.extend(utc); dayjs.extend(utc);
dayjs.extend(timezone); dayjs.extend(timezone);
dayjs.extend(relativeTime); dayjs.extend(relativeTime);
@ -21,11 +17,6 @@ export interface LooseObject {
[key: string]: any [key: string]: any
} }
export interface BaseRes {
ok: boolean;
msg?: string;
}
let randomBytes : (numBytes: number) => Uint8Array; let randomBytes : (numBytes: number) => Uint8Array;
initRandomBytes(); initRandomBytes();
@ -116,13 +107,6 @@ export const allowedRawKeys = [
"\u0003", // Ctrl + C "\u0003", // Ctrl + C
]; ];
export const acceptedComposeFileNames = [
"compose.yaml",
"docker-compose.yaml",
"docker-compose.yml",
"compose.yml",
];
/** /**
* Generate a decimal integer number from a string * Generate a decimal integer number from a string
* @param str Input * @param str Input
@ -219,7 +203,7 @@ export function getContainerTerminalName(container : string) {
} }
export function getContainerExecTerminalName(stackName : string, container : string, index : number) { export function getContainerExecTerminalName(stackName : string, container : string, index : number) {
return "container-exec-" + stackName + "-" + container + "-" + index; return "container-exec-" + container + "-" + index;
} }
export function copyYAMLComments(doc : Document, src : Document) { export function copyYAMLComments(doc : Document, src : Document) {
@ -356,52 +340,3 @@ export function parseDockerPort(input : string, defaultHostname : string = "loca
display: display, display: display,
}; };
} }
export function envsubst(string : string, variables : LooseObject) : string {
return replaceVariablesSync(string, variables)[0];
}
/**
* Traverse all values in the yaml and for each value, if there are template variables, replace it environment variables
* Emulates the behavior of how docker-compose handles environment variables in yaml files
* @param content Yaml string
* @param env Environment variables
* @returns string Yaml string with environment variables replaced
*/
export function envsubstYAML(content : string, env : DotenvParseOutput) : string {
const doc = yaml.parseDocument(content);
if (doc.contents) {
// @ts-ignore
for (const item of doc.contents.items) {
traverseYAML(item, env);
}
}
return doc.toString();
}
/**
* Used for envsubstYAML(...)
* @param pair
* @param env
*/
function traverseYAML(pair : Pair, env : DotenvParseOutput) : void {
// @ts-ignore
if (pair.value && pair.value.items) {
// @ts-ignore
for (const item of pair.value.items) {
if (item instanceof Pair) {
traverseYAML(item, env);
} else if (item instanceof Scalar) {
let value = item.value as unknown;
if (typeof(value) === "string") {
item.value = envsubst(value, env);
}
}
}
// @ts-ignore
} else if (pair.value && typeof(pair.value.value) === "string") {
// @ts-ignore
pair.value.value = envsubst(pair.value.value, env);
}
}

View File

@ -5,7 +5,6 @@ import { log } from "./log";
import { ERROR_TYPE_VALIDATION } from "./util-common"; import { ERROR_TYPE_VALIDATION } from "./util-common";
import { R } from "redbean-node"; import { R } from "redbean-node";
import { verifyPassword } from "./password-hash"; import { verifyPassword } from "./password-hash";
import fs from "fs";
export interface JWTDecoded { export interface JWTDecoded {
username : string; username : string;
@ -83,9 +82,3 @@ export async function doubleCheckPassword(socket : DockgeSocket, currentPassword
return user; return user;
} }
export function fileExists(file : string) {
return fs.promises.access(file, fs.constants.F_OK)
.then(() => true)
.catch(() => false);
}

View File

@ -13,10 +13,10 @@ services:
# If you want to use private registries, you need to share the auth file with Dockge: # If you want to use private registries, you need to share the auth file with Dockge:
# - /root/.docker/:/root/.docker # - /root/.docker/:/root/.docker
# Stacks Directory # Your stacks directory in the host (The paths inside container must be the same as the host)
# ⚠️ READ IT CAREFULLY. If you did it wrong, your data could end up writing into a WRONG PATH. # ⚠️⚠️ If you did it wrong, your data could end up be written into a wrong path.
# ⚠️ 1. FULL path only. No relative path (MUST) # ✔️✔️✔️✔️ CORRECT: - /my-stacks:/my-stacks (Both paths match)
# ⚠️ 2. Left Stacks Path === Right Stacks Path (MUST) # ❌❌❌❌ WRONG: - /docker:/my-stacks (Both paths do not match)
- /opt/stacks:/opt/stacks - /opt/stacks:/opt/stacks
environment: environment:
# Tell Dockge where is your stacks directory # Tell Dockge where is your stacks directory

View File

@ -1,7 +1,9 @@
# Due to the bug of #145, Node.js's version cannot be changed, unless upstream is fixed. FROM node:20-bookworm-slim
FROM node:18.17.1-bookworm-slim
ENV PNPM_HOME="/pnpm" ENV PNPM_HOME="/pnpm"
ENV PATH="$PNPM_HOME:$PATH" ENV PATH="$PNPM_HOME:$PATH"
# COPY --from=docker:dind /usr/local/bin/docker /usr/local/bin/
RUN apt update && apt install --yes --no-install-recommends \ RUN apt update && apt install --yes --no-install-recommends \
curl \ curl \
ca-certificates \ ca-certificates \
@ -22,3 +24,16 @@ RUN apt update && apt install --yes --no-install-recommends \
&& rm -rf /var/lib/apt/lists/* \ && rm -rf /var/lib/apt/lists/* \
&& npm install pnpm -g \ && npm install pnpm -g \
&& pnpm install -g tsx && pnpm install -g tsx
# ensures that /var/run/docker.sock exists
# changes the ownership of /var/run/docker.sock
RUN touch /var/run/docker.sock && chown node:node /var/run/docker.sock
# Full Base Image
# MariaDB, Chromium and fonts
#FROM base-slim AS base
#ENV DOCKGE_ENABLE_EMBEDDED_MARIADB=1
#RUN apt update && \
# apt --yes --no-install-recommends install mariadb-server && \
# rm -rf /var/lib/apt/lists/* && \
# apt --yes autoremove

View File

@ -1,10 +0,0 @@
############################################
# Build in Golang
############################################
FROM golang:1.21.4-bookworm
WORKDIR /app
ARG TARGETPLATFORM
COPY ./extra/healthcheck.go ./extra/healthcheck.go
# Compile healthcheck.go
RUN go build -x -o ./extra/healthcheck ./extra/healthcheck.go

View File

@ -1,8 +1,3 @@
############################################
# Healthcheck Binary
############################################
FROM louislam/dockge:build-healthcheck AS build_healthcheck
############################################ ############################################
# Build # Build
############################################ ############################################
@ -17,17 +12,16 @@ RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --prod --frozen-l
############################################ ############################################
FROM louislam/dockge:base AS release FROM louislam/dockge:base AS release
WORKDIR /app WORKDIR /app
COPY --chown=node:node --from=build_healthcheck /app/extra/healthcheck /app/extra/healthcheck
COPY --from=build /app/node_modules /app/node_modules
COPY --chown=node:node . . COPY --chown=node:node . .
COPY --from=build /app/node_modules /app/node_modules
RUN mkdir ./data RUN mkdir ./data
VOLUME /app/data VOLUME /app/data
EXPOSE 5001 EXPOSE 5001
HEALTHCHECK --interval=60s --timeout=30s --start-period=60s --retries=5 CMD extra/healthcheck
ENTRYPOINT ["/usr/bin/dumb-init", "--"] ENTRYPOINT ["/usr/bin/dumb-init", "--"]
CMD ["tsx", "./backend/index.ts"] CMD ["tsx", "./backend/index.ts"]
############################################ ############################################
# Mark as Nightly # Mark as Nightly
############################################ ############################################

View File

@ -1,74 +0,0 @@
/*
* If changed, have to run `npm run build-docker-builder-go`.
* This script should be run after a period of time (180s), because the server may need some time to prepare.
*/
package main
import (
"crypto/tls"
"io/ioutil"
"log"
"net/http"
"os"
"strings"
"time"
)
func main() {
// Is K8S + "dockge" as the container name
// See https://github.com/louislam/uptime-kuma/pull/2083
isK8s := strings.HasPrefix(os.Getenv("DOCKGE_PORT"), "tcp://")
// process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{
InsecureSkipVerify: true,
}
client := http.Client{
Timeout: 28 * time.Second,
}
sslKey := os.Getenv("DOCKGE_SSL_KEY")
sslCert := os.Getenv("DOCKGE_SSL_CERT")
hostname := os.Getenv("DOCKGE_HOST")
if len(hostname) == 0 {
hostname = "127.0.0.1"
}
port := ""
// DOCKGE_PORT is override by K8S unexpectedly,
if !isK8s {
port = os.Getenv("DOCKGE_PORT")
}
if len(port) == 0 {
port = "5001"
}
protocol := ""
if len(sslKey) != 0 && len(sslCert) != 0 {
protocol = "https"
} else {
protocol = "http"
}
url := protocol + "://" + hostname + ":" + port
log.Println("Checking " + url)
resp, err := client.Get(url)
if err != nil {
log.Fatalln(err)
}
defer resp.Body.Close()
_, err = ioutil.ReadAll(resp.Body)
if err != nil {
log.Fatalln(err)
}
log.Printf("Health Check OK [Res Code: %d]\n", resp.StatusCode)
}

View File

@ -4,30 +4,16 @@ const input = `
`; `;
const template = ` const template = `
> [!WARNING]
>
### 🆕 New Features ### 🆕 New Features
-
### ⬆️ Improvements ### Improvements
-
### 🐛 Bug Fixes ### 🐞 Bug Fixes
-
### 🦎 Translation Contributions ### 🦎 Translation Contributions
-
### ⬆️ Security Fixes
-
### Others ### Others
- Other small changes, code refactoring and comment/doc updates in this repo: - Other small changes, code refactoring and comment/doc updates in this repo:
-
Please let me know if your username is missing, if your pull request has been merged in this version, or your commit has been included in one of the pull requests.
`; `;
const lines = input.split("\n").filter((line) => line.trim() !== ""); const lines = input.split("\n").filter((line) => line.trim() !== "");
@ -51,12 +37,6 @@ for (const line of lines) {
} }
message = message.split("* ").pop(); message = message.split("* ").pop();
console.log("-", pullRequestID, message, `(Thanks ${username})`);
let thanks = "";
if (username != "@louislam") {
thanks = `(Thanks ${username})`;
}
console.log(pullRequestID, message, thanks);
} }
console.log(template); console.log(template);

View File

@ -1,130 +0,0 @@
import { Database } from "../backend/database";
import { R } from "redbean-node";
import readline from "readline";
import { User } from "../backend/models/user";
import { DockgeServer } from "../backend/dockge-server";
import { log } from "../backend/log";
import { io } from "socket.io-client";
import { BaseRes } from "../backend/util-common";
console.log("== Dockge Reset Password Tool ==");
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
const server = new DockgeServer();
export const main = async () => {
// Check if
console.log("Connecting the database");
try {
await Database.init(server);
} catch (e) {
if (e instanceof Error) {
log.error("server", "Failed to connect to your database: " + e.message);
}
process.exit(1);
}
try {
// No need to actually reset the password for testing, just make sure no connection problem. It is ok for now.
if (!process.env.TEST_BACKEND) {
const user = await R.findOne("user");
if (! user) {
throw new Error("user not found, have you installed?");
}
console.log("Found user: " + user.username);
while (true) {
let password = await question("New Password: ");
let confirmPassword = await question("Confirm New Password: ");
if (password === confirmPassword) {
await User.resetPassword(user.id, password);
// Reset all sessions by reset jwt secret
await server.initJWTSecret();
console.log("Password reset successfully.");
// Disconnect all other socket clients of the user
await disconnectAllSocketClients(user.username, password);
break;
} else {
console.log("Passwords do not match, please try again.");
}
}
}
} catch (e) {
if (e instanceof Error) {
console.error("Error: " + e.message);
}
}
await Database.close();
rl.close();
console.log("Finished.");
};
/**
* Ask question of user
* @param question Question to ask
* @returns Users response
*/
function question(question : string) : Promise<string> {
return new Promise((resolve) => {
rl.question(question, (answer) => {
resolve(answer);
});
});
}
function disconnectAllSocketClients(username : string, password : string) : Promise<void> {
return new Promise((resolve) => {
const url = server.getLocalWebSocketURL();
console.log("Connecting to " + url + " to disconnect all other socket clients");
// Disconnect all socket connections
const socket = io(url, {
transports: [ "websocket" ],
reconnection: false,
timeout: 5000,
});
socket.on("connect", () => {
socket.emit("login", {
username,
password,
}, (res : BaseRes) => {
if (res.ok) {
console.log("Logged in.");
socket.emit("disconnectOtherSocketClients");
} else {
console.warn("Login failed.");
console.warn("Please restart the server to disconnect all sessions.");
}
socket.close();
});
});
socket.on("connect_error", function () {
// The localWebSocketURL is not guaranteed to be working for some complicated Uptime Kuma setup
// Ask the user to restart the server manually
console.warn("Failed to connect to " + url);
console.warn("Please restart the server to disconnect all sessions manually.");
resolve();
});
socket.on("disconnect", () => {
resolve();
});
});
}
if (!process.env.TEST_BACKEND) {
main();
}

View File

@ -30,10 +30,6 @@ export default {
displayName: { displayName: {
type: String, type: String,
required: true, required: true,
},
objectType: {
type: String,
default: "service",
} }
}, },
data() { data() {
@ -45,7 +41,8 @@ export default {
array() { array() {
// Create the array if not exists, it should be safe. // Create the array if not exists, it should be safe.
if (!this.service[this.name]) { if (!this.service[this.name]) {
return []; // eslint-disable-next-line vue/no-side-effects-in-computed-properties
this.service[this.name] = [];
} }
return this.service[this.name]; return this.service[this.name];
}, },
@ -59,24 +56,8 @@ export default {
return this.service[this.name] !== undefined; return this.service[this.name] !== undefined;
}, },
/**
* Not a good name, but it is used to get the object.
*/
service() { service() {
if (this.objectType === "service") { return this.$parent.$parent.service;
// Used in Container.vue
return this.$parent.$parent.service;
} else if (this.objectType === "x-dockge") {
if (!this.$parent.$parent.jsonConfig["x-dockge"]) {
return {};
}
// Used in Compose.vue
return this.$parent.$parent.jsonConfig["x-dockge"];
} else {
return {};
}
}, },
valid() { valid() {
@ -100,19 +81,6 @@ export default {
}, },
methods: { methods: {
addField() { addField() {
// Create the object if not exists.
if (this.objectType === "x-dockge") {
if (!this.$parent.$parent.jsonConfig["x-dockge"]) {
this.$parent.$parent.jsonConfig["x-dockge"] = {};
}
}
// Create the array if not exists.
if (!this.service[this.name]) {
this.service[this.name] = [];
}
this.array.push(""); this.array.push("");
}, },
remove(index) { remove(index) {

View File

@ -49,7 +49,8 @@ export default {
array() { array() {
// Create the array if not exists, it should be safe. // Create the array if not exists, it should be safe.
if (!this.service[this.name]) { if (!this.service[this.name]) {
return []; // eslint-disable-next-line vue/no-side-effects-in-computed-properties
this.service[this.name] = [];
} }
return this.service[this.name]; return this.service[this.name];
}, },
@ -88,10 +89,6 @@ export default {
}, },
methods: { methods: {
addField() { addField() {
// Create the array if not exists.
if (!this.service[this.name]) {
this.service[this.name] = [];
}
this.array.push(""); this.array.push("");
}, },
remove(index) { remove(index) {

View File

@ -9,7 +9,7 @@
<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 service.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>
@ -213,29 +213,16 @@ export default defineComponent({
jsonObject() { jsonObject() {
return this.$parent.$parent.jsonConfig; return this.$parent.$parent.jsonConfig;
}, },
envsubstJSONConfig() {
return this.$parent.$parent.envsubstJSONConfig;
},
envsubstService() {
if (!this.envsubstJSONConfig.services[this.name]) {
return {};
}
return this.envsubstJSONConfig.services[this.name];
},
imageName() { imageName() {
if (this.envsubstService.image) { if (this.service.image) {
return this.envsubstService.image.split(":")[0]; return this.service.image.split(":")[0];
} else { } else {
return ""; return "";
} }
}, },
imageTag() { imageTag() {
if (this.envsubstService.image) { if (this.service.image) {
let tag = this.envsubstService.image.split(":")[1]; let tag = this.service.image.split(":")[1];
if (tag) { if (tag) {
return tag; return tag;

View File

@ -152,14 +152,6 @@ export default {
}); });
result.sort((m1, m2) => { result.sort((m1, m2) => {
// sort by managed by dockge
if (m1.isManagedByDockge && !m2.isManagedByDockge) {
return -1;
} else if (!m1.isManagedByDockge && m2.isManagedByDockge) {
return 1;
}
if (m1.status !== m2.status) { if (m1.status !== m2.status) {
if (m2.status === RUNNING) { if (m2.status === RUNNING) {
return 1; return 1;

View File

@ -5,8 +5,7 @@
</template> </template>
<script> <script>
import { Terminal } from "@xterm/xterm"; import { Terminal } from "xterm";
import { FitAddon } from "@xterm/addon-fit";
import { WebLinksAddon } from "xterm-addon-web-links"; import { WebLinksAddon } from "xterm-addon-web-links";
import { TERMINAL_COLS, TERMINAL_ROWS } from "../../../backend/util-common"; import { TERMINAL_COLS, TERMINAL_ROWS } from "../../../backend/util-common";
@ -123,12 +122,10 @@ export default {
} }
}); });
} }
// Fit the terminal width to the div container size after terminal is created.
this.updateTerminalSize();
}, },
unmounted() { unmounted() {
window.removeEventListener("resize", this.onResizeEvent); // Remove the resize event listener from the window object.
this.$root.unbindTerminal(this.name); this.$root.unbindTerminal(this.name);
this.terminal.dispose(); this.terminal.dispose();
}, },
@ -211,30 +208,6 @@ export default {
} }
}); });
}); });
},
/**
* Update the terminal size to fit the container size.
*
* If the terminalFitAddOn is not created, creates it, loads it and then fits the terminal to the appropriate size.
* It then addes an event listener to the window object to listen for resize events and calls the fit method of the terminalFitAddOn.
*/
updateTerminalSize() {
if (!Object.hasOwn(this, "terminalFitAddOn")) {
this.terminalFitAddOn = new FitAddon();
this.terminal.loadAddon(this.terminalFitAddOn);
window.addEventListener("resize", this.onResizeEvent);
}
this.terminalFitAddOn.fit();
},
/**
* Handles the resize event of the terminal component.
*/
onResizeEvent() {
this.terminalFitAddOn.fit();
let rows = this.terminal.rows;
let cols = this.terminal.cols;
this.$root.getSocket().emit("terminalResize", this.name, rows, cols);
} }
} }
}; };

View File

@ -19,14 +19,10 @@ const languageList = {
"ru": "Русский", "ru": "Русский",
"cs-CZ": "Čeština", "cs-CZ": "Čeština",
"ar": "العربية", "ar": "العربية",
"th": "ไทย", "th":"ไทย",
"it-IT": "Italiano", "it-IT":"Italiano",
"sv-SE": "Svenska", "sv-SE":"Svenska",
"uk-UA": "Українська", "uk-UA":"Українська"
"da": "Dansk",
"ja": "日本語",
"nl": "Nederlands",
"ro": "Română",
}; };
let messages = { let messages = {
@ -39,7 +35,7 @@ for (let lang in languageList) {
}; };
} }
const rtlLangs = [ "fa", "ar-SY", "ur", "ar" ]; const rtlLangs = [ "fa", "ar-SY", "ur" ];
export const currentLocale = () => localStorage.locale export const currentLocale = () => localStorage.locale
|| languageList[navigator.language] && navigator.language || languageList[navigator.language] && navigator.language

View File

@ -2,18 +2,13 @@
A simple guide on how to translate `Dockge` in your native language. A simple guide on how to translate `Dockge` in your native language.
## How to Translate
(11-26-2023) Updated
1. Go to <https://weblate.kuma.pet>
2. Register an account on Weblate
3. Make sure your GitHub email is matched with Weblate's account, so that it could show you as a contributor on GitHub
4. Choose your language on Weblate and start translating.
## How to add a new language in the dropdown ## How to add a new language in the dropdown
1. Add your Language at <https://weblate.kuma.pet/projects/dockge/dockge/>. (11-21-2023) Updated
2. Find the language code (You can find it at the end of the URL)
1. Add your Language at `frontend/src/lang/` by creating a new file with your language Code, format: `zh-TW.json` .
2. Copy the content from `en.json` and make translations from that.
3. Add your language at the end of `languageList` in `frontend/src/i18n.ts`, format: `"zh-TW": "繁體中文 (台灣)"`, 3. Add your language at the end of `languageList` in `frontend/src/i18n.ts`, format: `"zh-TW": "繁體中文 (台灣)"`,
4. Commit to new branch and make a new Pull Request for me to approve. 4. Commit to new branch and make a new Pull Request for me to approve.
*Note:* Currently we are only accepting one Pull Request per Language Translate.

View File

@ -12,8 +12,8 @@
"registry": "السجل", "registry": "السجل",
"compose": "أنشاء كمبوز", "compose": "أنشاء كمبوز",
"addFirstStackMsg": "أنشيء أول كمبوز!", "addFirstStackMsg": "أنشيء أول كمبوز!",
"stackName": "اسم المكدسة", "stackName" : "اسم المكدسة",
"deployStack": "نشر", "deployStack": نر",
"deleteStack": "حذف", "deleteStack": "حذف",
"stopStack": "إيقاف", "stopStack": "إيقاف",
"restartStack": "إعادة تشغيل", "restartStack": "إعادة تشغيل",
@ -23,7 +23,7 @@
"editStack": "تعديل", "editStack": "تعديل",
"discardStack": "إهمال", "discardStack": "إهمال",
"saveStackDraft": "حفظ", "saveStackDraft": "حفظ",
"notAvailableShort": "غير متوفر", "notAvailableShort" : "غير متوفر",
"deleteStackMsg": "هل أنت متأكد أنك تريد حذف هذه المكدسة؟", "deleteStackMsg": "هل أنت متأكد أنك تريد حذف هذه المكدسة؟",
"stackNotManagedByDockgeMsg": "لا يتم إدارة هذه المكدس بواسطة Dockge.", "stackNotManagedByDockgeMsg": "لا يتم إدارة هذه المكدس بواسطة Dockge.",
"primaryHostname": "اسم المضيف الرئيسي", "primaryHostname": "اسم المضيف الرئيسي",
@ -46,7 +46,7 @@
"deleteContainer": "حذف", "deleteContainer": "حذف",
"addContainer": "أضافة حاوية", "addContainer": "أضافة حاوية",
"addNetwork": "أضافة شبكة", "addNetwork": "أضافة شبكة",
"disableauth.message1": "هل أنت متأكد أنك تريد <strong>تعطيل المصادقة</strong>؟", "disableauth.message1": "هل أنت متأكد أنك تريد <strong>تعطيل المصادقة</strong>?",
"disableauth.message2": "إنه مصمم للحالات <strong>التي تنوي فيها مصادقة الطرف الثالث</strong> أمام Dockge مثل Cloudflare Access, Authelia أو أي من آليات المصادقة الأخرى.", "disableauth.message2": "إنه مصمم للحالات <strong>التي تنوي فيها مصادقة الطرف الثالث</strong> أمام Dockge مثل Cloudflare Access, Authelia أو أي من آليات المصادقة الأخرى.",
"passwordNotMatchMsg": "كلمة المرور المكررة غير متطابقة.", "passwordNotMatchMsg": "كلمة المرور المكررة غير متطابقة.",
"autoGet": "الجلب التلقائي", "autoGet": "الجلب التلقائي",
@ -91,12 +91,5 @@
"Allowed commands:": "الأوامر المسموح بها:", "Allowed commands:": "الأوامر المسموح بها:",
"Internal Networks": "الشبكات الداخلية", "Internal Networks": "الشبكات الداخلية",
"External Networks": "الشبكات الخارجية", "External Networks": "الشبكات الخارجية",
"No External Networks": "لا توجد شبكات خارجية", "No External Networks": "لا توجد شبكات خارجية"
"reverseProxyMsg2": "تحقق كيف يتم إعداده لمقبس ويب",
"Cannot connect to the socket server.": "تعذر الاتصال بخادم المقبس.",
"reconnecting...": "إعادة الاتصال…",
"url": "رابط | روابط",
"extra": "إضافات",
"reverseProxyMsg1": "هل تستدخم خادم عكسي؟",
"connecting...": "جاري الاتصال بخادم المقبس…"
} }

View File

@ -12,7 +12,7 @@
"registry": "Регистър", "registry": "Регистър",
"compose": "Compose", "compose": "Compose",
"addFirstStackMsg": "Създайте вашия първи стак!", "addFirstStackMsg": "Създайте вашия първи стак!",
"stackName": "Име на стак", "stackName" : "Име на стак",
"deployStack": "Разположи", "deployStack": "Разположи",
"deleteStack": "Изтрий", "deleteStack": "Изтрий",
"stopStack": "Спри", "stopStack": "Спри",
@ -22,7 +22,7 @@
"editStack": "Редактирай", "editStack": "Редактирай",
"discardStack": "Отхвърли", "discardStack": "Отхвърли",
"saveStackDraft": "Запази", "saveStackDraft": "Запази",
"notAvailableShort": "N/A", "notAvailableShort" : "N/A",
"deleteStackMsg": "Сигурни ли сте, че желаете да изтриете този стак?", "deleteStackMsg": "Сигурни ли сте, че желаете да изтриете този стак?",
"stackNotManagedByDockgeMsg": "Този стак не се управлява от Dockge.", "stackNotManagedByDockgeMsg": "Този стак не се управлява от Dockge.",
"primaryHostname": "Основно име на хост", "primaryHostname": "Основно име на хост",
@ -90,13 +90,5 @@
"Allowed commands:": "Позволени команди:", "Allowed commands:": "Позволени команди:",
"Internal Networks": "Вътрешни мрежи", "Internal Networks": "Вътрешни мрежи",
"External Networks": "Външни мрежи", "External Networks": "Външни мрежи",
"No External Networks": "Не са налични външни мрежи", "No External Networks": "Не са налични външни мрежи"
"reverseProxyMsg2": "Проверете как да го конфигурирате за WebSocket",
"downStack": "Спри и изключи",
"reverseProxyMsg1": "Използвате ревърс прокси?",
"Cannot connect to the socket server.": "Не може да се свърже със сокет сървъра.",
"url": "URL адрес | URL адреси",
"extra": "Допълнително",
"reconnecting...": "Повторно свързване…",
"connecting...": "Свързване със сокет сървъра…"
} }

View File

@ -3,39 +3,39 @@
"Create your admin account": "Vytvořit účet administrátora", "Create your admin account": "Vytvořit účet administrátora",
"authIncorrectCreds": "Nesprávné uživatelské jméno nebo heslo.", "authIncorrectCreds": "Nesprávné uživatelské jméno nebo heslo.",
"PasswordsDoNotMatch": "Hesla se neshodují.", "PasswordsDoNotMatch": "Hesla se neshodují.",
"Repeat Password": "Napište Heslo Znovu", "Repeat Password": "Opakujte heslo",
"Create": "Vytvořit", "Create": "Vytvořit",
"signedInDisp": "Přihlášen jako {0}", "signedInDisp": "Přihlášen jako {0}",
"signedInDispDisabled": "Ověření Zakázáno.", "signedInDispDisabled": "Ověření zakázáno.",
"home": "Domů", "home": "Domů",
"console": "Konzole", "console": "Konzole",
"registry": "Registry", "registry": "Registry",
"compose": "Komponovat", "compose": "Compose",
"addFirstStackMsg": "Vytvořte svůj první zásobník!", "addFirstStackMsg": "Vytvořte svůj první stack!",
"stackName": "Název Zásobníku", "stackName": "Název stacku",
"deployStack": "Nainstalovat", "deployStack": "Nainstalovat",
"deleteStack": "Smazat", "deleteStack": "Smazat",
"stopStack": "Zastavit", "stopStack": "Zastavit",
"restartStack": "Restartovat", "restartStack": "Restartovat",
"updateStack": "Aktualizovat", "updateStack": "Aktualizovat",
"startStack": "Spustit", "startStack": "Spustit",
"downStack": "Zastavit & Vypnout", "downStack": "Zastavit a vypnout",
"editStack": "Upravit", "editStack": "Upravit",
"discardStack": "Zahodit", "discardStack": "Zahodit",
"saveStackDraft": "Uložit", "saveStackDraft": "Uložit",
"notAvailableShort": "N/A", "notAvailableShort": "N/A",
"deleteStackMsg": "Opravdu chcete smazat tento zásobník?", "deleteStackMsg": "Opravdu chcete smazat tento stack?",
"stackNotManagedByDockgeMsg": "Tento stack není spravován systémem Dockge.", "stackNotManagedByDockgeMsg": "Tento stack není spravován systémem Dockge.",
"primaryHostname": "Primární název hostitele", "primaryHostname": "Primární název hostitele",
"general": "Obecné", "general": "Obecné",
"container": "Kontejner | Kontejnery", "container": "Kontejner | Kontejnery",
"scanFolder": "Prohledat složku se zásobníky", "scanFolder": "Prohledat složku se stacky",
"dockerImage": "Obrázek", "dockerImage": "Obrázek",
"restartPolicyUnlessStopped": "Pokud není zastaveno", "restartPolicyUnlessStopped": "Pokud není zastaveno",
"restartPolicyAlways": "Vždy", "restartPolicyAlways": "Vždy",
"restartPolicyOnFailure": "Při Selhání", "restartPolicyOnFailure": "Při selhání",
"restartPolicyNo": "Ne", "restartPolicyNo": "Ne",
"environmentVariable": "Proměnná Prostředí | Proměnné Prostředí", "environmentVariable": "Proměnná prostředí | Proměnné prostředí",
"restartPolicy": "Politika restartu", "restartPolicy": "Politika restartu",
"containerName": "Název kontejneru", "containerName": "Název kontejneru",
"port": "Port | Porty", "port": "Port | Porty",
@ -91,11 +91,5 @@
"Allowed commands:": "Povolené příkazy:", "Allowed commands:": "Povolené příkazy:",
"Internal Networks": "Interní sítě", "Internal Networks": "Interní sítě",
"External Networks": "Externí sítě", "External Networks": "Externí sítě",
"No External Networks": "Žádné externí sítě", "No External Networks": "Žádné externí sítě"
"reconnecting...": "Opětovné připojení…",
"url": "Adresa URL | Adresy URL",
"extra": "Extra",
"reverseProxyMsg1": "Používáte Reverzní proxy server?",
"reverseProxyMsg2": "Podívat se jak to nastavit pro WebSocket",
"Cannot connect to the socket server.": "Nelze se připojit k serveru ."
} }

View File

@ -1,102 +0,0 @@
{
"languageName": "Dansk",
"authIncorrectCreds": "Forkert brugernavn eller adgangskode.",
"PasswordsDoNotMatch": "Adgangskode stemmer ikke overens.",
"Repeat Password": "Gentag adgangskode",
"Create": "Opret",
"signedInDisp": "Logget ind som {0}",
"signedInDispDisabled": "Auth Deaktiveret.",
"home": "Hjem",
"console": "Konsol",
"registry": "Registry",
"compose": "Compose",
"stackName": "Stack-navn",
"deployStack": "Udrulle",
"deleteStack": "Slet",
"stopStack": "Stop",
"restartStack": "Genstart",
"updateStack": "Opdatere",
"startStack": "Start",
"downStack": "Stop & Sluk",
"editStack": "Editere",
"discardStack": "Annuller",
"saveStackDraft": "Gem",
"notAvailableShort": "Ugyldig",
"stackNotManagedByDockgeMsg": "Denne stack administreres ikke af Dockge.",
"primaryHostname": "Primært værtsnavn",
"general": "Generelt",
"container": "Container | Containere",
"scanFolder": "Scan Stack-mappe",
"dockerImage": "Billede",
"restartPolicyUnlessStopped": "Medmindre stoppet",
"restartPolicyAlways": "Altid",
"restartPolicyOnFailure": "Ved fejl",
"restartPolicyNo": "Nej",
"restartPolicy": "Genstart politik",
"containerName": "Container navn",
"port": "Port | Porte",
"volume": "Volumen | Voluminer",
"network": "Netværk | Netværker",
"dependsOn": "Container Dependency | Container Dependencies",
"addListItem": "Tilføj {0}",
"deleteContainer": "Slet",
"addNetwork": "Tilføj Netværk",
"passwordNotMatchMsg": "Koden du gentog stemmer ikke overens.",
"autoGet": "Auto Get",
"add": "Tilføj",
"Edit": "Redigere",
"applyToYAML": "Anvend til YAML",
"createExternalNetwork": "Skabe",
"addInternalNetwork": "Tilføj",
"Save": "Gem",
"Language": "Sprog",
"Current User": "Nuværende bruger",
"Change Password": "Ændre adgangskode",
"Current Password": "Nuværende adgangskode",
"New Password": "Ny adgangskode",
"Repeat New Password": "Gentag ny adgangskode",
"Update Password": "Opdater adgangskode",
"Advanced": "Avanceret",
"Please use this option carefully!": "Brug venligst denne indstilling forsigtigt!",
"Enable Auth": "Aktiver Auth",
"Disable Auth": "Deaktiver Auth",
"I understand, please disable": "Jeg forstår, venligst deaktiver",
"Leave": "Forlad",
"Frontend Version": "Frontend Version",
"Check Update On GitHub": "Tjek opdatering på GitHub",
"Also check beta release": "Tjek også betaversionen",
"Remember me": "Husk mig",
"Login": "Login",
"Username": "Brugernavn",
"Password": "Adgangskode",
"Settings": "Indstillinger",
"Logout": "Log ud",
"Convert to Compose": "Konverter til Compose",
"active": "aktive",
"exited": "forladt",
"inactive": "inaktive",
"Appearance": "Udseende",
"Security": "Sikkerhed",
"Docker Run": "Docker Kør",
"About": "Om",
"Allowed commands:": "Tilladte kommandoer:",
"Internal Networks": "Interne netværk",
"External Networks": "Eksterne netværk",
"No External Networks": "Ingen eksterne netværk",
"reverseProxyMsg1": "Bruger du en Reverse-Proxy?",
"reverseProxyMsg2": "Tjek, hvordan du konfigurerer det til WebSocket",
"Cannot connect to the socket server.": "Kan ikke oprette forbindelse til socket-serveren.",
"reconnecting...": "Genopretter forbindelse…",
"connecting...": "Opretter forbindelse til socket-serveren…",
"url": "URL | URL'er",
"extra": "Ekstra",
"Create your admin account": "Opret din administratorkonto",
"addFirstStackMsg": "Compose din første stack!",
"deleteStackMsg": "Er du sikker på, at du vil slette denne stack?",
"environmentVariable": "Miljøvariabel | miljøvariabler",
"addContainer": "Tilføj Container",
"disableauth.message1": "Er du sikker på, at du vil <strong>deaktivere godkendelse</strong>?",
"disableauth.message2": "Det er designet til scenarier <strong>hvor du har til hensigt at implementere tredjepartsgodkendelse</strong> foran Dockge såsom Cloudflare Access, Authelia eller andre godkendelsesmekanismer.",
"Show update if available": "Vis opdatering, hvis tilgængelig",
"Lowercase only": "Kun små bogstaver"
}

View File

@ -6,14 +6,14 @@
"Repeat Password": "Passwort wiederholen", "Repeat Password": "Passwort wiederholen",
"Create": "Erstellen", "Create": "Erstellen",
"signedInDisp": "Angemeldet als {0}", "signedInDisp": "Angemeldet als {0}",
"signedInDispDisabled": "Anmeldung deaktiviert.", "signedInDispDisabled": "Authentifizierung deaktiviert.",
"home": "Startseite", "home": "Startseite",
"console": "Konsole", "console": "Konsole",
"registry": "Container Registry", "registry": "Register",
"compose": "Compose", "compose": "Zusammenstellen",
"addFirstStackMsg": "Stelle deinen ersten Stack zusammen!", "addFirstStackMsg": "Stelle deinen ersten Stack zusammen!",
"stackName": "Stack-Name", "stackName" : "Stack-Name",
"deployStack": "Deployen", "deployStack": "Bereitstellen",
"deleteStack": "Löschen", "deleteStack": "Löschen",
"stopStack": "Anhalten", "stopStack": "Anhalten",
"restartStack": "Neustarten", "restartStack": "Neustarten",
@ -22,33 +22,33 @@
"editStack": "Bearbeiten", "editStack": "Bearbeiten",
"discardStack": "Verwerfen", "discardStack": "Verwerfen",
"saveStackDraft": "Speichern", "saveStackDraft": "Speichern",
"notAvailableShort": "N/V", "notAvailableShort" : "N/A",
"deleteStackMsg": "Möchtest du diesen Stack wirklich löschen?", "deleteStackMsg": "Möchtest du diesen Stack wirklich löschen?",
"stackNotManagedByDockgeMsg": "Dieser Stack wird nicht von Dockge verwaltet.", "stackNotManagedByDockgeMsg": "Dieser Stack wird nicht von Dockge verwaltet.",
"primaryHostname": "Primärer Hostname", "primaryHostname": "Primärer Hostname",
"general": "Allgemein", "general": "Allgemein",
"container": "Container", "container": "Container | Container",
"scanFolder": "Stacks-Ordner durchsuchen", "scanFolder": "Stacks-Ordner durchsuchen",
"dockerImage": "Image", "dockerImage": "Image",
"restartPolicyUnlessStopped": "Falls nicht gestoppt", "restartPolicyUnlessStopped": "Falls nicht gestoppt",
"restartPolicyAlways": "Immer", "restartPolicyAlways": "Immer",
"restartPolicyOnFailure": "Bei Fehler", "restartPolicyOnFailure": "Bei Fehler",
"restartPolicyNo": "Kein Neustart", "restartPolicyNo": "Kein Neustart",
"environmentVariable": "Umgebungsvariable/n", "environmentVariable": "Umgebungsvariable | Umgebungsvariablen",
"restartPolicy": "Neustart Richtlinie", "restartPolicy": "Neustart Richtlinie",
"containerName": "Container-Name", "containerName": "Container-Name",
"port": "Port / Ports", "port": "Port | Ports",
"volume": "Volume / Volumes", "volume": "Volume | Volumes",
"network": "Netzwerk | Netzwerke", "network": "Netzwerk | Netzwerke",
"dependsOn": "Container-Abhängigkeit/en", "dependsOn": "Container-Abhängigkeit | Container-Abhängigkeiten",
"addListItem": "{0} hinzufügen", "addListItem": "{0} hinzufügen",
"deleteContainer": "Löschen", "deleteContainer": "Löschen",
"addContainer": "Container hinzufügen", "addContainer": "Container hinzufügen",
"addNetwork": "Netzwerk hinzufügen", "addNetwork": "Netzwerk hinzufügen",
"disableauth.message1": "Bist du sicher, dass du die <strong>Anmeldung deaktivieren</strong> möchtest?", "disableauth.message1": "Bist du sicher, dass du die <strong>Authentifizierung deaktivieren</strong> möchtest?",
"disableauth.message2": "Es ist für Szenarien vorgesehen, <strong>in denen du beabsichtigst, eine Drittanbieter-Authentifizierung</strong> vor Dockge zu implementieren, wie zum Beispiel Cloudflare Access, Authelia oder andere Authentifizierungsmechanismen.", "disableauth.message2": "Es ist für Szenarien vorgesehen, <strong>in denen du beabsichtigst, eine Drittanbieter-Authentifizierung</strong> vor Dockge zu implementieren, wie zum Beispiel Cloudflare Access, Authelia oder andere Authentifizierungsmechanismen.",
"passwordNotMatchMsg": "Das wiederholte Passwort stimmt nicht überein.", "passwordNotMatchMsg": "Das wiederholte Passwort stimmt nicht überein.",
"autoGet": "Automatisch laden", "autoGet": "Automatisch holen",
"add": "Hinzufügen", "add": "Hinzufügen",
"Edit": "Bearbeiten", "Edit": "Bearbeiten",
"applyToYAML": "Auf YAML anwenden", "applyToYAML": "Auf YAML anwenden",
@ -64,23 +64,23 @@
"Update Password": "Passwort aktualisieren", "Update Password": "Passwort aktualisieren",
"Advanced": "Erweitert", "Advanced": "Erweitert",
"Please use this option carefully!": "Bitte verwende diese Option sorgfältig!", "Please use this option carefully!": "Bitte verwende diese Option sorgfältig!",
"Enable Auth": "Anmeldung aktivieren", "Enable Auth": "Authentifizierung aktivieren",
"Disable Auth": "Anmeldung deaktivieren", "Disable Auth": "Authentifizierung deaktivieren",
"I understand, please disable": "Ich verstehe, bitte deaktivieren", "I understand, please disable": "Ich verstehe, bitte deaktivieren",
"Leave": "Verlassen", "Leave": "Verlassen",
"Frontend Version": "Frontend Version", "Frontend Version": "Frontend Version",
"Check Update On GitHub": "Update auf GitHub überprüfen", "Check Update On GitHub": "Update auf GitHub überprüfen",
"Show update if available": "Update anzeigen, wenn verfügbar", "Show update if available": "Update anzeigen, wenn verfügbar",
"Also check beta release": "Auch Beta-Version überprüfen", "Also check beta release": "Auch Beta-Version überprüfen",
"Remember me": "Angemeldet bleiben", "Remember me": "Anmeldung beibehalten",
"Login": "Anmelden", "Login": "Anmelden",
"Username": "Benutzername", "Username": "Benutzername",
"Password": "Passwort", "Password": "Passwort",
"Settings": "Einstellungen", "Settings": "Einstellungen",
"Logout": "Abmelden", "Logout": "Abmelden",
"Lowercase only": "Nur Kleinbuchstaben", "Lowercase only": "Nur Kleinbuchstaben",
"Convert to Compose": "In Compose-Syntax umwandeln", "Convert to Compose": "In Compose Syntax umwandeln",
"Docker Run": "Docker Run", "Docker Run": "Docker ausführen",
"active": "aktiv", "active": "aktiv",
"exited": "beendet", "exited": "beendet",
"inactive": "inaktiv", "inactive": "inaktiv",
@ -90,13 +90,5 @@
"Allowed commands:": "Zugelassene Befehle:", "Allowed commands:": "Zugelassene Befehle:",
"Internal Networks": "Interne Netzwerke", "Internal Networks": "Interne Netzwerke",
"External Networks": "Externe Netzwerke", "External Networks": "Externe Netzwerke",
"No External Networks": "Keine externen Netzwerke", "No External Networks": "Keine externen Netzwerke"
"Cannot connect to the socket server.": "Keine Verbindung zum Socket Server.", }
"reverseProxyMsg1": "Wird ein Reverse Proxy genutzt?",
"reconnecting...": "Erneuter Verbindungsaufbau…",
"downStack": "Stoppen & Aus",
"extra": "Extra",
"url": "URL / URLs",
"reverseProxyMsg2": "Lerne wie dieser für WebSockets zu konfigurieren ist.",
"connecting...": "Verbindungsaufbau zum Socket Server…"
}

View File

@ -12,7 +12,7 @@
"registry": "Registry", "registry": "Registry",
"compose": "Compose", "compose": "Compose",
"addFirstStackMsg": "Compose your first stack!", "addFirstStackMsg": "Compose your first stack!",
"stackName": "Stack Name", "stackName" : "Stack Name",
"deployStack": "Deploy", "deployStack": "Deploy",
"deleteStack": "Delete", "deleteStack": "Delete",
"stopStack": "Stop", "stopStack": "Stop",
@ -23,7 +23,7 @@
"editStack": "Edit", "editStack": "Edit",
"discardStack": "Discard", "discardStack": "Discard",
"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?",
"stackNotManagedByDockgeMsg": "This stack is not managed by Dockge.", "stackNotManagedByDockgeMsg": "This stack is not managed by Dockge.",
"primaryHostname": "Primary Hostname", "primaryHostname": "Primary Hostname",
@ -95,9 +95,6 @@
"reverseProxyMsg1": "Using a Reverse Proxy?", "reverseProxyMsg1": "Using a Reverse Proxy?",
"reverseProxyMsg2": "Check how to config it for WebSocket", "reverseProxyMsg2": "Check how to config it for WebSocket",
"Cannot connect to the socket server.": "Cannot connect to the socket server.", "Cannot connect to the socket server.": "Cannot connect to the socket server.",
"reconnecting...": "Reconnecting", "reconnecting...": "Reconnecting...",
"connecting...": "Connecting to the socket server…", "connecting...": "Connecting to the socket server..."
"url": "URL | URLs",
"extra": "Extra",
"newUpdate": "New Update"
} }

View File

@ -1,5 +1,5 @@
{ {
"languageName": "Français", "languageName": "Francais",
"Create your admin account": "Créez votre compte administrateur", "Create your admin account": "Créez votre compte administrateur",
"authIncorrectCreds": "identifiant ou mot de passe incorrect.", "authIncorrectCreds": "identifiant ou mot de passe incorrect.",
"Repeat Password": "Répéter le mot de passe", "Repeat Password": "Répéter le mot de passe",
@ -11,8 +11,8 @@
"console": "Console", "console": "Console",
"registry": "Registre", "registry": "Registre",
"compose": "Compose", "compose": "Compose",
"addFirstStackMsg": "Créez votre première pile !", "addFirstStackMsg": "Créez votre première pile!",
"stackName": "Nom de la pile", "stackName" : "Nom de la pile",
"deployStack": "Déployer", "deployStack": "Déployer",
"deleteStack": "Supprimer", "deleteStack": "Supprimer",
"stopStack": "Arrêter", "stopStack": "Arrêter",
@ -22,11 +22,11 @@
"editStack": "Modifier", "editStack": "Modifier",
"discardStack": "Ignorer", "discardStack": "Ignorer",
"saveStackDraft": "Sauvegarder", "saveStackDraft": "Sauvegarder",
"notAvailableShort": "N/A", "notAvailableShort" : "N/A",
"deleteStackMsg": "Êtes-vous sûr de vouloir supprimer cette pile ?", "deleteStackMsg": "Êtes-vous sûr de vouloir supprimer cette pile ?",
"stackNotManagedByDockgeMsg": "Cette pile n'est pas gérée par Dockge.", "stackNotManagedByDockgeMsg": "Cette pile n'est pas gérée par Dockge.",
"primaryHostname": "Nom d'hôte principal", "primaryHostname": "Nom d'hôte principal",
"general": "Général", "general": "Générale",
"container": "Conteneur | Conteneurs", "container": "Conteneur | Conteneurs",
"scanFolder": "Analyser le dossier des piles", "scanFolder": "Analyser le dossier des piles",
"dockerImage": "Image", "dockerImage": "Image",
@ -51,7 +51,7 @@
"autoGet": "Obtention automatique", "autoGet": "Obtention automatique",
"add": "Ajouter", "add": "Ajouter",
"Edit": "Modifier", "Edit": "Modifier",
"applyToYAML": "Appliquer au YAML", "applyToYAML": "Appliquer à YAML",
"createExternalNetwork": "Créer", "createExternalNetwork": "Créer",
"addInternalNetwork": "Ajouter", "addInternalNetwork": "Ajouter",
"Save": "Enregistrer", "Save": "Enregistrer",
@ -87,17 +87,8 @@
"Appearance": "Apparence", "Appearance": "Apparence",
"Security": "Sécurité", "Security": "Sécurité",
"About": "À propos", "About": "À propos",
"Allowed commands:": "Commandes autorisées :", "Allowed commands:": "Commandes autorisées:",
"Internal Networks": "Réseaux Internes", "Internal Networks": "Réseaux Internes",
"External Networks": "Réseaux Externes", "External Networks": "Réseaux Externes",
"No External Networks": "Aucun Réseau Externe", "No External Networks": "Aucun Réseau Externe"
"reverseProxyMsg2": "Vérifier comment le configurer pour WebSocket",
"connecting...": "Connexion au serveur socket…",
"url": "URL | URLs",
"extra": "Supplémentaire",
"downStack": "Arrêter et désactiver",
"reverseProxyMsg1": "Utilisez vous un proxy inverse ?",
"Cannot connect to the socket server.": "Impossible de se connecter au serveur socket.",
"reconnecting...": "Reconnexion…",
"newUpdate": "Nouvelle mise à jour"
} }

View File

@ -10,10 +10,10 @@
"home": "Home", "home": "Home",
"console": "Console", "console": "Console",
"registry": "Registro", "registry": "Registro",
"compose": "Componi", "compose": "Compose",
"addFirstStackMsg": "Componi il tuo primo stack!", "addFirstStackMsg": "Componi il tuo primo stack!",
"stackName": "Nome dello stack", "stackName" : "Nome dello stack",
"deployStack": "Rilascia", "deployStack": "Deploy",
"deleteStack": "Cancella", "deleteStack": "Cancella",
"stopStack": "Stop", "stopStack": "Stop",
"restartStack": "Riavvia", "restartStack": "Riavvia",
@ -23,7 +23,7 @@
"editStack": "Modifica", "editStack": "Modifica",
"discardStack": "Annulla", "discardStack": "Annulla",
"saveStackDraft": "Salva", "saveStackDraft": "Salva",
"notAvailableShort": "N/D", "notAvailableShort" : "N/D",
"deleteStackMsg": "Sei sicuro di voler eliminare questo stack?", "deleteStackMsg": "Sei sicuro di voler eliminare questo stack?",
"stackNotManagedByDockgeMsg": "Questo stack non è gestito da Dockge.", "stackNotManagedByDockgeMsg": "Questo stack non è gestito da Dockge.",
"primaryHostname": "Hostname primario", "primaryHostname": "Hostname primario",
@ -75,7 +75,7 @@
"Also check beta release": "Controlla anche le release in beta", "Also check beta release": "Controlla anche le release in beta",
"Remember me": "Ricordami", "Remember me": "Ricordami",
"Login": "Login", "Login": "Login",
"Username": "Nome Utente", "Username": "Username",
"Password": "Password", "Password": "Password",
"Settings": "Impostazioni", "Settings": "Impostazioni",
"Logout": "Logout", "Logout": "Logout",
@ -91,12 +91,5 @@
"Allowed commands:": "Comandi permessi:", "Allowed commands:": "Comandi permessi:",
"Internal Networks": "Reti interne", "Internal Networks": "Reti interne",
"External Networks": "Reti esterne", "External Networks": "Reti esterne",
"No External Networks": "Nessuna rete esterna", "No External Networks": "Nessuna rete esterna"
"reverseProxyMsg1": "Utilizzando un proxy inverso?",
"reverseProxyMsg2": "Controlla come configurarlo per WebSocket",
"Cannot connect to the socket server.": "Impossibile connettersi al server socket.",
"connecting...": "Connessione al server socket…",
"extra": "Extra",
"reconnecting...": "Riconnessione…",
"url": "Indirizzo | Indirizzi"
} }

View File

@ -1,98 +0,0 @@
{
"authIncorrectCreds": "ユーザーネームまたはパスワードが正しくありません。",
"PasswordsDoNotMatch": "パスワードが一致しません。",
"Repeat Password": "パスワードを再度入力してください",
"Create": "作成",
"signedInDispDisabled": "認証が無効化されています。",
"home": "ホーム",
"console": "コンソール",
"registry": "レジストリ",
"stackName": "スタック名",
"deployStack": "デプロイ",
"deleteStack": "削除",
"stopStack": "停止",
"restartStack": "再起動",
"updateStack": "更新",
"startStack": "起動",
"editStack": "編集",
"discardStack": "破棄",
"saveStackDraft": "保存",
"stackNotManagedByDockgeMsg": "このスタックはDockgeによって管理されていません。",
"general": "一般",
"scanFolder": "スタックフォルダをスキャン",
"dockerImage": "イメージ",
"environmentVariable": "環境変数",
"restartPolicy": "再起動ポリシー",
"containerName": "コンテナ名",
"port": "ポート",
"volume": "ボリューム",
"network": "ネットワーク",
"addListItem": "{0} を追加",
"addContainer": "コンテナを追加",
"addNetwork": "ネットワークを追加",
"compose": "Compose",
"primaryHostname": "主ホスト名",
"container": "コンテナ",
"dependsOn": "コンテナ依存関係",
"downStack": "停止して削除",
"notAvailableShort": "N/A",
"restartPolicyUnlessStopped": "手動で停止されるまで",
"restartPolicyAlways": "常時",
"restartPolicyOnFailure": "失敗時",
"restartPolicyNo": "しない",
"passwordNotMatchMsg": "繰り返しのパスワードが一致しません。",
"autoGet": "自動取得",
"add": "追加",
"Edit": "編集",
"applyToYAML": "YAMLに適用",
"createExternalNetwork": "作成",
"addInternalNetwork": "追加",
"Save": "保存",
"Language": "言語",
"Change Password": "パスワードを変更する",
"Current Password": "現在のパスワード",
"New Password": "新しいパスワード",
"Update Password": "パスワードを更新",
"Advanced": "高度",
"Please use this option carefully!": "このオプションは注意して使用してください!",
"Enable Auth": "認証を有効化",
"Disable Auth": "認証を無効化",
"Check Update On GitHub": "GitHubで更新を確認",
"Show update if available": "アップデートがある場合表示",
"Also check beta release": "ベータ版のリリースも確認する",
"Login": "ログイン",
"Username": "ユーザー名",
"Password": "パスワード",
"Settings": "設定",
"Logout": "ログアウト",
"Convert to Compose": "Composeに変換",
"Appearance": "外観",
"Security": "セキュリティ",
"Allowed commands:": "許可されたコマンド:",
"Internal Networks": "内部ネットワーク",
"External Networks": "外部ネットワーク",
"reverseProxyMsg2": "WebSocketの設定方法を確認",
"Cannot connect to the socket server.": "ソケットサーバーに接続できません。",
"reconnecting...": "再接続中…",
"Leave": "やめる",
"Frontend Version": "フロントエンドバージョン",
"Remember me": "覚えておく",
"No External Networks": "外部ネットワークなし",
"exited": "終了済み",
"inactive": "非アクティブ",
"active": "アクティブ",
"languageName": "日本語",
"Create your admin account": "管理者アカウントを作成してください",
"signedInDisp": "{0} としてログイン中",
"addFirstStackMsg": "最初のスタックを組み立てましょう!",
"deleteStackMsg": "本当にこのスタックを削除しますか?",
"deleteContainer": "削除",
"disableauth.message1": "本当に<strong>認証を無効化</strong>しますか?",
"disableauth.message2": "これはCloudflare AccessやAutheliaなどの認証手段をDockgeの前段に置いて<strong>サードパーティー認証を実装することをあなたが意図している</strong>場合のために設計されています。",
"Current User": "現在のユーザー",
"Repeat New Password": "新しいパスワードを繰り返してください",
"I understand, please disable": "理解しました。無効化してください",
"Lowercase only": "小文字のみ",
"reverseProxyMsg1": "リバースプロキシを使用していますか?",
"connecting...": "ソケットサーバーに接続中…"
}

View File

@ -90,13 +90,5 @@
"Allowed commands:": "허용된 명령어:", "Allowed commands:": "허용된 명령어:",
"Internal Networks": "내부 네트워크", "Internal Networks": "내부 네트워크",
"External Networks": "외부 네트워크", "External Networks": "외부 네트워크",
"No External Networks": "외부 네트워크 없음", "No External Networks": "외부 네트워크 없음"
"reverseProxyMsg2": "여기서 WebSocket을 위한 설정을 확인해 보세요",
"downStack": "정지 & Down",
"reverseProxyMsg1": "리버스 프록시를 사용하고 계신가요?",
"Cannot connect to the socket server.": "소켓 서버에 연결하지 못했습니다.",
"connecting...": "소켓 서버에 연결하는 중…",
"extra": "기타",
"url": "URL | URL",
"reconnecting...": "재연결 중…"
} }

View File

@ -1,102 +0,0 @@
{
"languageName": "Nederlands",
"authIncorrectCreds": "Onjuiste gebruikersnaam of wachtwoord.",
"PasswordsDoNotMatch": "Paswoorden komen niet overeen.",
"Repeat Password": "Herhaal wachtwoord",
"Create": "Aanmaken",
"signedInDisp": "Ingelogd als {0}",
"home": "Startpagina",
"console": "Console",
"registry": "Register",
"compose": "Samenstellen",
"stackName": "Stack naam",
"deployStack": "Opzetten",
"deleteStack": "Verwijder",
"stopStack": "Stop",
"restartStack": "Herstart",
"updateStack": "Update",
"startStack": "Start",
"downStack": "Stop & Down",
"editStack": "Bewerken",
"discardStack": "Verwijderen",
"saveStackDraft": "Opslaan",
"notAvailableShort": "NVT",
"stackNotManagedByDockgeMsg": "Deze stack wordt niet beheerd door Dockge.",
"primaryHostname": "Primaire hostnaam",
"general": "Algemeen",
"scanFolder": "Scan stacks folder",
"dockerImage": "Image",
"restartPolicyUnlessStopped": "Tenzij gestopt",
"restartPolicyAlways": "Altijd",
"restartPolicyOnFailure": "Bij fout",
"restartPolicyNo": "Neen",
"environmentVariable": "Omgevings variabele(n)",
"restartPolicy": "Herstart policy",
"containerName": "Containernaam",
"port": "Poort(en)",
"volume": "Volume(s)",
"network": "Netwerk(en)",
"addListItem": "Voeg {0} toe",
"deleteContainer": "Verwijder",
"addContainer": "Container toevoegen",
"addNetwork": "Netwerk toevoegen",
"signedInDispDisabled": "Aanmelden uitgeschakeld.",
"container": "Container(s)",
"autoGet": "Auto ophalen",
"add": "Toevoegen",
"Edit": "Bewerken",
"applyToYAML": "Toevoegen aan YAML",
"createExternalNetwork": "Aanmaken",
"addInternalNetwork": "Toevoegen",
"Save": "Opslaan",
"Language": "Taal",
"Change Password": "Verander wachtwoord",
"Current Password": "Huidig wachtwoord",
"New Password": "Nieuw wachtwoord",
"Repeat New Password": "Herhaal nieuw wachtwoord",
"Update Password": "Update wachtwoord",
"Advanced": "Geavanceerd",
"I understand, please disable": "Begrepen, dit uitschakelen",
"Disable Auth": "Aanmelden uitschakelen",
"Enable Auth": "Aanmelden inschakelen",
"Leave": "Afmelden",
"Frontend Version": "Frontend versie",
"Check Update On GitHub": "Controleer via GitHub op updates",
"Show update if available": "Toon update indien beschikbaar",
"Remember me": "Onthoud mij",
"Login": "Inloggen",
"Username": "Gebruikersnaam",
"Password": "Wachtwoord",
"Settings": "Instellingen",
"Logout": "Uitloggen",
"Lowercase only": "Geen hoofdletters",
"Docker Run": "Docker run",
"active": "actief",
"exited": "gestopt",
"inactive": "inactief",
"Appearance": "Uiterlijk",
"Security": "Beveiliging",
"About": "Over",
"Allowed commands:": "Toegelaten commando's:",
"Internal Networks": "Interne netwerken",
"No External Networks": "Geen externe netwerken",
"reverseProxyMsg1": "Reverse proxy in gebruik?",
"reverseProxyMsg2": "Controleer hoe te configureren voor WebSocket",
"Cannot connect to the socket server.": "Kan geen verbinding maken met de socket server.",
"reconnecting...": "Herverbinden...",
"connecting...": "Verbinden met de socket server...",
"url": "Url(s)",
"extra": "Extra",
"Create your admin account": "Creëer je beheerders-account",
"addFirstStackMsg": "Maak je eerste stack!",
"deleteStackMsg": "Zeker dat je deze stack wilt verwijderen?",
"dependsOn": "Container afhankelijkheid | afhankelijkheden",
"disableauth.message1": "Zeker dat u <strong>aanmelden</strong> wilt uitschakelen?",
"disableauth.message2": "Dit is enkel bedoeld om te gebruiken wanneer je<strong> third-party autorisatie wilt gebruiken voor Dockge</strong>, zoals Cloudflare Acces, Authelia, ...",
"passwordNotMatchMsg": "De wachtwoorden komen niet overeen.",
"Current User": "Huidige gebruiker",
"Please use this option carefully!": "Wees voorzichtig met deze optie!",
"Also check beta release": "Controleer ook op beta releases",
"Convert to Compose": "Converteer naar compose",
"External Networks": "Externe netwerken"
}

View File

@ -12,7 +12,7 @@
"registry": "Registro", "registry": "Registro",
"compose": "Compose", "compose": "Compose",
"addFirstStackMsg": "Crie sua primeira stack!", "addFirstStackMsg": "Crie sua primeira stack!",
"stackName": "Nome da stack", "stackName" : "Nome da stack",
"deployStack": "Deploy", "deployStack": "Deploy",
"deleteStack": "Excluir", "deleteStack": "Excluir",
"stopStack": "Parar", "stopStack": "Parar",
@ -22,7 +22,7 @@
"editStack": "Editar", "editStack": "Editar",
"discardStack": "Descartar", "discardStack": "Descartar",
"saveStackDraft": "Salvar", "saveStackDraft": "Salvar",
"notAvailableShort": "N/D", "notAvailableShort" : "N/D",
"deleteStackMsg": "Tem certeza que deseja excluir esta stack?", "deleteStackMsg": "Tem certeza que deseja excluir esta stack?",
"stackNotManagedByDockgeMsg": "Esta stack não é gerenciada pelo Dockge.", "stackNotManagedByDockgeMsg": "Esta stack não é gerenciada pelo Dockge.",
"primaryHostname": "Nome do Host Primário", "primaryHostname": "Nome do Host Primário",
@ -90,13 +90,5 @@
"Allowed commands:": "Comandos permitidos:", "Allowed commands:": "Comandos permitidos:",
"Internal Networks": "Redes internas", "Internal Networks": "Redes internas",
"External Networks": "Redes externas", "External Networks": "Redes externas",
"No External Networks": "Sem redes externas", "No External Networks": "Sem redes externas"
"reverseProxyMsg2": "Veja como configurar para WebSocket",
"downStack": "Parar & Encerrar",
"reverseProxyMsg1": "Utiliza proxy reverso?",
"Cannot connect to the socket server.": "Não é possível conectar ao socket server.",
"connecting...": "Conectando ao socket server…",
"url": "URL | URLs",
"extra": "Extra",
"reconnecting...": "Reconectando…"
} }

View File

@ -1,102 +0,0 @@
{
"Create your admin account": "Creați-vă contul de administrator",
"PasswordsDoNotMatch": "Parolele nu se potrivesc.",
"Repeat Password": "Repetați parola",
"signedInDisp": "Conectat ca {0}",
"signedInDispDisabled": "Autentificare dezactivată.",
"Create": "Creează",
"home": "Acasă",
"console": "Consolă",
"registry": "Registru",
"compose": "Compune",
"addFirstStackMsg": "Compune prima ta stivă!",
"stackName": "Nume stivă",
"deployStack": "Lansează",
"deleteStack": "Șterge",
"stopStack": "Oprește",
"restartStack": "Repornire",
"updateStack": "Actualizare",
"languageName": "Română",
"authIncorrectCreds": "Numele de utilizator sau parola incorectă.",
"startStack": "Pornește",
"editStack": "Editați",
"discardStack": "Renunţa",
"notAvailableShort": "N/A",
"deleteStackMsg": "Sigur doriți să ștergeți această stivă?",
"stackNotManagedByDockgeMsg": "Această stivă nu este gestionată de Dockge.",
"primaryHostname": "Numele gazdei principale",
"general": "General",
"container": "Container | Containere",
"scanFolder": "Scanează folderul cu stive",
"dockerImage": "Imagine",
"restartPolicyOnFailure": "La Defecţiune",
"restartPolicyNo": "Nu",
"restartPolicy": "Politica de repornire",
"restartPolicyAlways": "Mereu",
"containerName": "Numele Containerului",
"port": "Port | Porturi",
"volume": "Volum | Volume",
"network": "Reţea | Reţele",
"dependsOn": "Dependența containerului | Dependențele containerelor",
"addListItem": "Adaugă {0}",
"deleteContainer": "Șterge",
"addContainer": "Adaugă Container",
"addNetwork": "Adaugă Rețea",
"addInternalNetwork": "Adaugă",
"Save": "Salvează",
"Current User": "Utilizator Curent",
"Change Password": "Schimbă Parola",
"Current Password": "Parolă Curenta",
"New Password": "Parolă Nouă",
"Repeat New Password": "Repetă Parola Nouă",
"Update Password": "Actualizează Parola",
"Advanced": "Avansat",
"Enable Auth": "Activați Autentificarea",
"Disable Auth": "Dezactivați Autentificarea",
"I understand, please disable": "Am înțeles, vă rog dezactivați",
"Leave": "Părăsiți",
"Frontend Version": "Versiunea Frontend",
"Check Update On GitHub": "Verificați actualizarea pe GitHub",
"Also check beta release": "Verificați și versiunea beta",
"Remember me": "Ține-mă minte",
"Login": "Autentificare",
"Username": "Nume de utilizator",
"Password": "Parolă",
"passwordNotMatchMsg": "Parola repetată nu se potrivește.",
"autoGet": "Obținere automată",
"add": "Adăuga",
"Edit": "Editați",
"applyToYAML": "Aplicați la YAML",
"createExternalNetwork": "Creează",
"Settings": "Setări",
"Logout": "Deconectare",
"Lowercase only": "Doar litere mici",
"Convert to Compose": "Convertiți în Compose",
"Docker Run": "Docker Run",
"active": "activ",
"exited": "ieșit",
"inactive": "inactiv",
"Appearance": "Aspect",
"Security": "Securitate",
"About": "Despre",
"Allowed commands:": "Comenzi permise:",
"Internal Networks": "Rețele interne",
"External Networks": "Rețele externe",
"No External Networks": "Fără rețele externe",
"reverseProxyMsg1": "Folosești un proxy invers?",
"reverseProxyMsg2": "Verificați cum să-l configurați pentru WebSocket",
"Cannot connect to the socket server.": "Nu se poate conecta la serverul socket.",
"reconnecting...": "Reconectare...",
"connecting...": "Se conectează la serverul socket...",
"url": "URL | URLs",
"extra": "Suplimentar",
"downStack": "Opriți & Coborâți",
"saveStackDraft": "Salvați",
"restartPolicyUnlessStopped": "Dacă nu este oprit",
"environmentVariable": "Variabila de mediu | Variabile de mediu",
"Language": "Limbă",
"Please use this option carefully!": "Vă rugăm să utilizați această opțiune cu atenție!",
"Show update if available": "Afișează actualizarea dacă este disponibilă",
"disableauth.message1": "Sigur doriți să <strong>dezactivați autentificarea</strong>?",
"disableauth.message2": "Este conceput pentru scenarii <strong>în care intenționați să implementați autentificarea terță</strong> în fața Dockge-lui, cum ar fi Cloudflare Access, Authelia sau alte mecanisme de autentificare."
}

View File

@ -5,14 +5,14 @@
"PasswordsDoNotMatch": "Пароль не совпадает.", "PasswordsDoNotMatch": "Пароль не совпадает.",
"Repeat Password": "Повторите пароль", "Repeat Password": "Повторите пароль",
"Create": "Создать", "Create": "Создать",
"signedInDisp": "Авторизован как {0}", "signedInDisp": "Авторизлван как {0}",
"signedInDispDisabled": "Авторизация выключена.", "signedInDispDisabled": "Авторизация выключена.",
"home": "Главная", "home": "Главная",
"console": "Консоль", "console": "Консоль",
"registry": "Реестр (Registry)", "registry": "Registry",
"compose": "Составить (Compose)", "compose": "Compose",
"addFirstStackMsg": "Создайте свой первый стек!", "addFirstStackMsg": "Создайте свой первый стек!",
"stackName": "Имя стека", "stackName" : "Имя стека",
"deployStack": "Развернуть", "deployStack": "Развернуть",
"deleteStack": "Удалить", "deleteStack": "Удалить",
"stopStack": "Остановить", "stopStack": "Остановить",
@ -22,7 +22,7 @@
"editStack": "Изменить", "editStack": "Изменить",
"discardStack": "Отменить", "discardStack": "Отменить",
"saveStackDraft": "Сохранить", "saveStackDraft": "Сохранить",
"notAvailableShort": "Н/Д", "notAvailableShort" : "Н/Д",
"deleteStackMsg": "Вы уверены что хотите удалить этот стек?", "deleteStackMsg": "Вы уверены что хотите удалить этот стек?",
"stackNotManagedByDockgeMsg": "Данный стек не обслуживается Dockge.", "stackNotManagedByDockgeMsg": "Данный стек не обслуживается Dockge.",
"primaryHostname": "Имя хоста", "primaryHostname": "Имя хоста",
@ -79,7 +79,7 @@
"Settings": "Настройки", "Settings": "Настройки",
"Logout": "Выйти", "Logout": "Выйти",
"Lowercase only": "Только нижний регистр", "Lowercase only": "Только нижний регистр",
"Convert to Compose": "Преобразовать в Compose", "Convert to Compose": "Преобразовать вCompose",
"Docker Run": "Запустить Docker", "Docker Run": "Запустить Docker",
"active": "активный", "active": "активный",
"exited": "завершенный", "exited": "завершенный",
@ -90,13 +90,5 @@
"Allowed commands:": "Разрешенные команды:", "Allowed commands:": "Разрешенные команды:",
"Internal Networks": "Внутренние сети", "Internal Networks": "Внутренние сети",
"External Networks": "Внешние сети", "External Networks": "Внешние сети",
"No External Networks": "Нет внешних сетей", "No External Networks": "Нет внешних сетей"
"downStack": "Остановить и выключить",
"reverseProxyMsg1": "Использовать Реверс Прокси?",
"reconnecting...": "Переподключение…",
"Cannot connect to the socket server.": "Не удается подключиться к серверу сокетов.",
"url": "URL адрес(а)",
"extra": "Дополнительно",
"reverseProxyMsg2": "Проверьте, как настроить его для WebSocket",
"connecting...": "Подключение к серверу сокетов…"
} }

View File

@ -90,10 +90,5 @@
"Allowed commands:": "Dovoljeni ukazi:", "Allowed commands:": "Dovoljeni ukazi:",
"Internal Networks": "Notranja omrežja", "Internal Networks": "Notranja omrežja",
"External Networks": "Zunanja omrežja", "External Networks": "Zunanja omrežja",
"No External Networks": "Ni zunanjih omrežij", "No External Networks": "Ni zunanjih omrežij"
"downStack": "Ustavi & Odstrani",
"connecting...": "Povezovanje s strežnikom…",
"reverseProxyMsg1": "Uporabljate obratni proxy?",
"extra": "Dodatno",
"reconnecting...": "Ponovna povezava …"
} }

View File

@ -1,6 +1,6 @@
{ {
"languageName": "Svenska", "languageName": "Svenska",
"Create your admin account": "Skapa ditt Admin-konto", "Create your admin account": "Skapa ditt Admin-konto.",
"authIncorrectCreds": "Fel användarnamn eller lösenord.", "authIncorrectCreds": "Fel användarnamn eller lösenord.",
"PasswordsDoNotMatch": "Lösenorden matchar inte.", "PasswordsDoNotMatch": "Lösenorden matchar inte.",
"Repeat Password": "Repetera lösenord", "Repeat Password": "Repetera lösenord",
@ -12,28 +12,28 @@
"registry": "Register", "registry": "Register",
"compose": "Komponera", "compose": "Komponera",
"addFirstStackMsg": "Komponera din första stack!", "addFirstStackMsg": "Komponera din första stack!",
"stackName": "Stacknamn", "stackName" : "Stacknamn",
"deployStack": "Distribuera", "deployStack": "Distribuera",
"deleteStack": "Radera", "deleteStack": "Radera",
"stopStack": "Stoppa", "stopStack": "Stop",
"restartStack": "Starta om", "restartStack": "Starta om",
"updateStack": "Uppdatera", "updateStack": "Uppdatera",
"startStack": "Starta", "startStack": "Starta",
"downStack": "Stoppa & Ner", "downStack": "Stop & Ner",
"editStack": "Redigera", "editStack": "Redigera",
"discardStack": "Kasta", "discardStack": "Kasta",
"saveStackDraft": "Spara", "saveStackDraft": "Spara",
"notAvailableShort": "N/A", "notAvailableShort" : "N/A",
"deleteStackMsg": "Är du säker på att du vill radera stacken?", "deleteStackMsg": "Är du säker på att du vill radera stacken?",
"stackNotManagedByDockgeMsg": "Denna stacken hanteras inte av Dockge.", "stackNotManagedByDockgeMsg": "Denna stacken hanteras inte av Dockge.",
"primaryHostname": "Primärt värdnamn", "primaryHostname": "Primärt värdnamn",
"general": "Allmän", "general": "Allmän",
"container": "Container | Containrar", "container": "Container | Containrar",
"scanFolder": "Skanna Stackmapp", "scanFolder": "Scanna Stackfolder",
"dockerImage": "Avbild", "dockerImage": "Bild",
"restartPolicyUnlessStopped": "Om inte stoppad", "restartPolicyUnlessStopped": "Om inte stoppas",
"restartPolicyAlways": "Alltid", "restartPolicyAlways": "Alltid",
"restartPolicyOnFailure": "Vid misslyckande", "restartPolicyOnFailure": "Vid Misslyckande",
"restartPolicyNo": "Nej", "restartPolicyNo": "Nej",
"environmentVariable": "Miljövariabel | Miljövariabler", "environmentVariable": "Miljövariabel | Miljövariabler",
"restartPolicy": "Omstartspolicy", "restartPolicy": "Omstartspolicy",
@ -44,12 +44,12 @@
"dependsOn": "Containerberoende | Containerberoenden", "dependsOn": "Containerberoende | Containerberoenden",
"addListItem": "Lägg till {0}", "addListItem": "Lägg till {0}",
"deleteContainer": "Radera", "deleteContainer": "Radera",
"addContainer": "Lägg till container", "addContainer": "Lägg till Container",
"addNetwork": "Lägg till nätverk", "addNetwork": "Lägg till Nätverk",
"disableauth.message1": "Är du säker på att du vill <strong>inaktivera autentisering</strong>?", "disableauth.message1": "Är du säker på att du vill <strong>inaktivera autentisering</strong>?",
"disableauth.message2": "Det är designat för scenarion <strong>där du ska implementera tredjepartsautentisering</strong> framför Dockge som Cloudflare Access, Authelia eller andra autentiseringsmekanismer.", "disableauth.message2": "Det är designat för senarion <stong>när du ska implementera tredjeparts autentisering</strong> framör Dockge som Cloudflare Access, Authelia eller andra autentiseringsmekanismer.",
"passwordNotMatchMsg": "Det upprepade lösenordet matchar inte.", "passwordNotMatchMsg": "Det upprepade lösenordet matchar inte",
"autoGet": "Auto-hämta", "autoGet": "Auto Hämta",
"add": "Lägg till", "add": "Lägg till",
"Edit": "Redigera", "Edit": "Redigera",
"applyToYAML": "Lägg till i YAML", "applyToYAML": "Lägg till i YAML",
@ -57,8 +57,8 @@
"addInternalNetwork": "Lägg till", "addInternalNetwork": "Lägg till",
"Save": "Spara", "Save": "Spara",
"Language": "Språk", "Language": "Språk",
"Current User": "Nuvarande användare", "Current User": "Nuvarande användaren",
"Change Password": "Ändra lösenord", "Change Password": "Byt lösenord",
"Current Password": "Nuvarande lösenord", "Current Password": "Nuvarande lösenord",
"New Password": "Nytt lösenord", "New Password": "Nytt lösenord",
"Repeat New Password": "Upprepa nytt lösenord", "Repeat New Password": "Upprepa nytt lösenord",
@ -70,9 +70,9 @@
"I understand, please disable": "Jag förstår, vänligen inaktivera", "I understand, please disable": "Jag förstår, vänligen inaktivera",
"Leave": "Lämna", "Leave": "Lämna",
"Frontend Version": "Frontendversion", "Frontend Version": "Frontendversion",
"Check Update On GitHub": "Kontrollera uppdatering på GitHub", "Check Update On GitHub": "Kontrollera Uppdatering på GitHub",
"Show update if available": "Visa uppdatering om tillgänglig", "Show update if available": "Visa uppdatering om tillgänglig",
"Also check beta release": "Kontrollera även betaversioner", "Also check beta release": "Kontrollera även betaversionen",
"Remember me": "Kom ihåg mig", "Remember me": "Kom ihåg mig",
"Login": "Logga in", "Login": "Logga in",
"Username": "Användarnamn", "Username": "Användarnamn",
@ -80,8 +80,8 @@
"Settings": "Inställningar", "Settings": "Inställningar",
"Logout": "Logga ut", "Logout": "Logga ut",
"Lowercase only": "Endast små tecken", "Lowercase only": "Endast små tecken",
"Convert to Compose": "Omvandla till compose", "Convert to Compose": "Omvandla till Compose",
"Docker Run": "Docker kör", "Docker Run": "Docker Run",
"active": "aktiv", "active": "aktiv",
"exited": "avslutad", "exited": "avslutad",
"inactive": "inaktiv", "inactive": "inaktiv",
@ -89,14 +89,7 @@
"Security": "Säkerhet", "Security": "Säkerhet",
"About": "Om", "About": "Om",
"Allowed commands:": "Tillåtna kommandon:", "Allowed commands:": "Tillåtna kommandon:",
"Internal Networks": "Interna nätverk", "Internal Networks": "Interna Nätverk",
"External Networks": "Externa nätverk", "External Networks": "Externa Nätverk",
"No External Networks": "Inga externa nätverk", "No External Networks": "Inga Externa Nätverk"
"reverseProxyMsg1": "Används omvänd proxy?",
"connecting...": "Ansluter till socketserver…",
"Cannot connect to the socket server.": "Kan inte ansluta till socketservern.",
"reverseProxyMsg2": "Kontrollera hur man konfigurerar webbsocket",
"url": "URL | URLer",
"extra": "Extra",
"reconnecting...": "Återansluter…"
} }

View File

@ -91,12 +91,5 @@
"Allowed commands:": "คำสั่งที่อนุญาต:", "Allowed commands:": "คำสั่งที่อนุญาต:",
"Internal Networks": "เครือข่ายภายใน", "Internal Networks": "เครือข่ายภายใน",
"External Networks": "เครือข่ายภายนอก", "External Networks": "เครือข่ายภายนอก",
"No External Networks": "ไม่มีเครือข่ายภายนอก", "No External Networks": "ไม่มีเครือข่ายภายนอก"
"reverseProxyMsg2": "ตรวจสอบวิธีกำหนดค่าสำหรับ WebSocket", }
"Cannot connect to the socket server.": "ไม่สามารถเชื่อมต่อกับเซิร์ฟเวอร์ socket ได้",
"reverseProxyMsg1": "ใช้ Reverse Proxy หรือไม่?",
"connecting...": "กำลังเชื่อมต่อกับเซิร์ฟเวอร์ socket…",
"url": "URL | URLs",
"extra": "พิเศษ",
"reconnecting...": "กำลังเชื่อมต่อใหม่…"
}

View File

@ -12,7 +12,7 @@
"registry": "Kayıt Defteri", "registry": "Kayıt Defteri",
"compose": "Compose", "compose": "Compose",
"addFirstStackMsg": "İlk yığınınızı oluşturun!", "addFirstStackMsg": "İlk yığınınızı oluşturun!",
"stackName": "Yığın Adı", "stackName" : "Yığın Adı",
"deployStack": "Dağıtmak", "deployStack": "Dağıtmak",
"deleteStack": "Sil", "deleteStack": "Sil",
"stopStack": "Dudur", "stopStack": "Dudur",
@ -22,7 +22,7 @@
"editStack": "Düzenle", "editStack": "Düzenle",
"discardStack": ıkar", "discardStack": ıkar",
"saveStackDraft": "Kaydet", "saveStackDraft": "Kaydet",
"notAvailableShort": "N/A", "notAvailableShort" : "N/A",
"deleteStackMsg": "Bu yığını silmek istediğinizden emin misiniz?", "deleteStackMsg": "Bu yığını silmek istediğinizden emin misiniz?",
"stackNotManagedByDockgeMsg": "Bu yığın Dockge tarafından yönetilmemektedir.", "stackNotManagedByDockgeMsg": "Bu yığın Dockge tarafından yönetilmemektedir.",
"primaryHostname": "Birincil Ana Bilgisayar Adı", "primaryHostname": "Birincil Ana Bilgisayar Adı",
@ -90,13 +90,5 @@
"Allowed commands:": "İzin verilen komutlar:", "Allowed commands:": "İzin verilen komutlar:",
"Internal Networks": "İç Ağlar", "Internal Networks": "İç Ağlar",
"External Networks": "Dış Ağlar", "External Networks": "Dış Ağlar",
"No External Networks": "Dış Ağ Yok", "No External Networks": "Dış Ağ Yok"
"extra": "Ekstra",
"reverseProxyMsg1": "Ters Proxy mi kullanıyorsunuz?",
"reverseProxyMsg2": "WebSocket için nasıl yapılandırma yapılacağını kontrol edin",
"reconnecting...": "Yeniden bağlanıyor…",
"connecting...": "Soket sunucusuna bağlanıyor…",
"url": "URL | URLler",
"Cannot connect to the socket server.": "Soket sunucusuna bağlanılamıyor.",
"downStack": "Durdur & Kapat"
} }

View File

@ -1,8 +1,8 @@
{ {
"languageName": "Українська", "languageName": "Українська",
"Create your admin account": "Створити акаунт адміністратора", "Create your admin account": "Створити акаунт адміністратора",
"authIncorrectCreds": "Неправильне ім'я користувача або пароль.", "authIncorrectCreds": "Невірний логін чи пароль",
"PasswordsDoNotMatch": "Паролі не збігаються.", "PasswordsDoNotMatch": "Паролі не співпадають.",
"Repeat Password": "Повторіть пароль", "Repeat Password": "Повторіть пароль",
"Create": "Створити", "Create": "Створити",
"signedInDisp": "Авторизовано як {0}", "signedInDisp": "Авторизовано як {0}",
@ -12,8 +12,8 @@
"registry": "Registry", "registry": "Registry",
"compose": "Compose", "compose": "Compose",
"addFirstStackMsg": "Додайте свій перший стек!", "addFirstStackMsg": "Додайте свій перший стек!",
"stackName": "Назва стеку", "stackName" : "Назва стеку",
"deployStack": "Розгорнути", "deployStack": "Розрознути",
"deleteStack": "Видалити", "deleteStack": "Видалити",
"stopStack": "Зупинити", "stopStack": "Зупинити",
"restartStack": "Перезапустити", "restartStack": "Перезапустити",
@ -22,7 +22,7 @@
"editStack": "Редагувати", "editStack": "Редагувати",
"discardStack": "Відмінити", "discardStack": "Відмінити",
"saveStackDraft": "Зберегти", "saveStackDraft": "Зберегти",
"notAvailableShort": "Н/Д", "notAvailableShort" : "Н/Д",
"deleteStackMsg": "Ви впевнені що хочете видалити цей стек?", "deleteStackMsg": "Ви впевнені що хочете видалити цей стек?",
"stackNotManagedByDockgeMsg": "Даний стек не управляється Dockge.", "stackNotManagedByDockgeMsg": "Даний стек не управляється Dockge.",
"primaryHostname": "Назва хосту", "primaryHostname": "Назва хосту",
@ -47,7 +47,7 @@
"addNetwork": "Додати Мережу", "addNetwork": "Додати Мережу",
"disableauth.message1": "Ви впевнені що хочете <strong>вимкнути авторизацію</strong>?", "disableauth.message1": "Ви впевнені що хочете <strong>вимкнути авторизацію</strong>?",
"disableauth.message2": "Це призначено для сценаріїв, <strong>де ви збираєтесь використати сторонню авторизацію</strong> перед Dockge, наприклад Cloudflare Access, Authelia чи інші.", "disableauth.message2": "Це призначено для сценаріїв, <strong>де ви збираєтесь використати сторонню авторизацію</strong> перед Dockge, наприклад Cloudflare Access, Authelia чи інші.",
"passwordNotMatchMsg": овторення паролю не збігається.", "passwordNotMatchMsg": "Паролі не співпадають.",
"autoGet": "Отримати", "autoGet": "Отримати",
"add": "Додати", "add": "Додати",
"Edit": "Змінити", "Edit": "Змінити",
@ -63,10 +63,10 @@
"Repeat New Password": "Повторіть новий пароль", "Repeat New Password": "Повторіть новий пароль",
"Update Password": "Оновити пароль", "Update Password": "Оновити пароль",
"Advanced": "Розширені опції", "Advanced": "Розширені опції",
"Please use this option carefully!": "Будь ласка, використовуйте цю опцію з обережністю!", "Please use this option carefully!": "Будь-ласка, використовуйте цю опцію з обережністю!",
"Enable Auth": "Увімкнути автентифікацію", "Enable Auth": "Увімкнути автентифікацію",
"Disable Auth": "Вимкнути автентифікацію", "Disable Auth": "Вимкнути автентифікацію",
"I understand, please disable": "Зрозуміло, все одно вимкнути", "I understand, please disable": "Зрозуміло, все-одно вимкнути",
"Leave": "Покинути", "Leave": "Покинути",
"Frontend Version": "Версія інтерфейсу", "Frontend Version": "Версія інтерфейсу",
"Check Update On GitHub": "Перевірити оновлення на GitHub", "Check Update On GitHub": "Перевірити оновлення на GitHub",
@ -90,13 +90,5 @@
"Allowed commands:": "Дозволені команди:", "Allowed commands:": "Дозволені команди:",
"Internal Networks": "Внутрішні мережі", "Internal Networks": "Внутрішні мережі",
"External Networks": "Зовнішні мережі", "External Networks": "Зовнішні мережі",
"No External Networks": "Немає зовнішніх мереж", "No External Networks": "Немає зовнішніх мереж"
"downStack": "Зупинити і вимкнути",
"reverseProxyMsg1": "Використовуєте зворотній проксі?",
"Cannot connect to the socket server.": "Не вдається підключитися до сервера сокетів.",
"reconnecting...": "Повторне підключення…",
"connecting...": "Підключення до сервера сокетів…",
"url": "URL-адреса | URL-адреси",
"reverseProxyMsg2": "Перевірте, як налаштувати його для WebSocket",
"extra": "Додатково"
} }

View File

@ -12,23 +12,23 @@
"registry": "رجسٹری", "registry": "رجسٹری",
"compose": "تحریر", "compose": "تحریر",
"addFirstStackMsg": "اپنا پہلا اسٹیک کمپوز کریں!", "addFirstStackMsg": "اپنا پہلا اسٹیک کمپوز کریں!",
"stackName": "اسٹیک کا نام", "stackName" : "اسٹیک کا نام",
"deployStack": "تعینات", "deployStack": "تعینات",
"deleteStack": "حذف کریں", "deleteStack": "حذف کریں",
"stopStack": "روکو", "stopStack": "روکو",
"restartStack": "دوبارہ شروع کریں", "restartStack": "دوبارہ شروع کریں",
"updateStack": "اپ ڈیٹ", "updateStack": "اپ ڈیٹ",
"startStack": "شروع کریں", "startStack": "شروع کریں۔",
"editStack": "ترمیم", "editStack": "ترمیم",
"discardStack": "رد کر دیں", "discardStack": "رد کر دیں۔",
"saveStackDraft": "محفوظ کریں۔", "saveStackDraft": "محفوظ کریں۔",
"notAvailableShort": "N / A", "notAvailableShort" : "N / A",
"deleteStackMsg": "کیا آپ واقعی اس اسٹیک کو حذف کرنا چاہتے ہیں؟", "deleteStackMsg": "کیا آپ واقعی اس اسٹیک کو حذف کرنا چاہتے ہیں؟",
"stackNotManagedByDockgeMsg": "یہ اسٹیک Dockge کے زیر انتظام نہیں ہے۔", "stackNotManagedByDockgeMsg": "یہ اسٹیک Dockge کے زیر انتظام نہیں ہے۔",
"primaryHostname": "بنیادی میزبان نام", "primaryHostname": "بنیادی میزبان نام",
"general": "جنرل", "general": "جنرل",
"container": "کنٹینر | کنٹینرز", "container": "کنٹینر | کنٹینرز",
"scanFolder": "اسٹیک فولڈر کو اسکین کریں", "scanFolder": "اسٹیک فولڈر کو اسکین کریں۔",
"dockerImage": "تصویر", "dockerImage": "تصویر",
"restartPolicyUnlessStopped": "جب تک روکا نہیں جاتا", "restartPolicyUnlessStopped": "جب تک روکا نہیں جاتا",
"restartPolicyAlways": "ہمیشہ", "restartPolicyAlways": "ہمیشہ",
@ -51,7 +51,7 @@
"autoGet": "آٹو حاصل کریں", "autoGet": "آٹو حاصل کریں",
"add": "شامل کریں", "add": "شامل کریں",
"Edit": "ترمیم", "Edit": "ترمیم",
"applyToYAML": "YAML پر درخواست دیں", "applyToYAML": "YAML پر درخواست دیں۔",
"createExternalNetwork": "بنانا", "createExternalNetwork": "بنانا",
"addInternalNetwork": "شامل کریں", "addInternalNetwork": "شامل کریں",
"Save": "محفوظ کریں", "Save": "محفوظ کریں",
@ -64,12 +64,12 @@
"Update Password": "پاس ورڈ اپ ڈیٹ کریں", "Update Password": "پاس ورڈ اپ ڈیٹ کریں",
"Advanced": "ترقی یافتہ", "Advanced": "ترقی یافتہ",
"Please use this option carefully!": "براہ کرم اس اختیار کو احتیاط سے استعمال کریں!", "Please use this option carefully!": "براہ کرم اس اختیار کو احتیاط سے استعمال کریں!",
"Enable Auth": "تصدیق کو فعال کریں", "Enable Auth": "تصدیق کو فعال کریں۔",
"Disable Auth": "توثیق کو غیر فعال کریں", "Disable Auth": "توثیق کو غیر فعال کریں۔",
"I understand, please disable": "میں سمجھتا ہوں، براہ کرم غیر فعال کریں", "I understand, please disable": "میں سمجھتا ہوں، براہ کرم غیر فعال کریں۔",
"Leave": "چھوڑ دو", "Leave": "چھوڑ دو",
"Frontend Version": "فرنٹ اینڈ ورژن", "Frontend Version": "فرنٹ اینڈ ورژن",
"Check Update On GitHub": "گیتوب پر اپ ڈیٹ چیک کریں", "Check Update On GitHub": "گیتوب پر اپ ڈیٹ چیک کریں۔",
"Show update if available": "اگر دستیاب ہو تو اپ ڈیٹ دکھائیں", "Show update if available": "اگر دستیاب ہو تو اپ ڈیٹ دکھائیں",
"Also check beta release": "بیٹا ریلیز بھی چیک کریں", "Also check beta release": "بیٹا ریلیز بھی چیک کریں",
"Remember me": "مجھے پہچانتے ہو", "Remember me": "مجھے پہچانتے ہو",
@ -90,13 +90,5 @@
"Allowed commands:": "اجازت شدہ احکامات:", "Allowed commands:": "اجازت شدہ احکامات:",
"Internal Networks": "اندرونی نیٹ ورکس", "Internal Networks": "اندرونی نیٹ ورکس",
"External Networks": "بیرونی نیٹ ورکس", "External Networks": "بیرونی نیٹ ورکس",
"No External Networks": "کوئی بیرونی نیٹ ورک نہیں", "No External Networks": "کوئی بیرونی نیٹ ورک نہیں"
"reverseProxyMsg1": "ایک ریورس پراکسی کا استعمال کرتے ہوئے؟",
"Cannot connect to the socket server.": "ساکٹ سرور سے منسلک نہیں ہو سکتا۔",
"reconnecting...": "دوبارہ منسلک ہو رہا ہے…",
"connecting...": "ساکٹ سرور سے منسلک ہو رہا ہے…",
"url": "یو آر ایل | یو آر ایل",
"extra": "اضافی",
"downStack": "اسٹاپ اینڈ ڈاؤن",
"reverseProxyMsg2": "اسے WebSocket کے لیے ترتیب دینے کا طریقہ چیک کریں"
} }

View File

@ -1,18 +1,18 @@
{ {
"languageName": "简体中文", "languageName": "简体中文",
"Create your admin account": "创建你的管理员账号", "Create your admin account": "创建你的管理员账号",
"authIncorrectCreds": "用户名或密码错误", "authIncorrectCreds": "用户名或密码错误",
"PasswordsDoNotMatch": "两次输入的密码不一致。", "PasswordsDoNotMatch": "两次输入的密码不一致。",
"Repeat Password": "重复以确认密码", "Repeat Password": "重复以确认密码",
"Create": "创建", "Create": "创建",
"signedInDisp": "当前用户: {0}", "signedInDisp": "当前用户: {0}",
"signedInDispDisabled": "已禁用身份验证", "signedInDispDisabled": "已禁用身份验证",
"home": "主页", "home": "主页",
"console": "终端", "console": "终端",
"registry": "镜像仓库", "registry": "镜像仓库",
"compose": "Compose", "compose": "Compose",
"addFirstStackMsg": "组合你的第一个堆栈!", "addFirstStackMsg": "组合你的第一个堆栈!",
"stackName": "堆栈名称", "stackName" : "堆栈名称",
"deployStack": "部署", "deployStack": "部署",
"deleteStack": "删除", "deleteStack": "删除",
"stopStack": "停止", "stopStack": "停止",
@ -22,9 +22,9 @@
"editStack": "编辑", "editStack": "编辑",
"discardStack": "放弃", "discardStack": "放弃",
"saveStackDraft": "保存", "saveStackDraft": "保存",
"notAvailableShort": "不可用", "notAvailableShort" : "不可用",
"deleteStackMsg": "你确定要删除这个堆栈吗?", "deleteStackMsg": "你确定要删除这个堆栈吗?",
"stackNotManagedByDockgeMsg": "这个堆栈不由Dockge管理", "stackNotManagedByDockgeMsg": "这个堆栈不由Dockge管理",
"primaryHostname": "主机名", "primaryHostname": "主机名",
"general": "常规", "general": "常规",
"container": "容器 | 容器组", "container": "容器 | 容器组",
@ -90,13 +90,5 @@
"Allowed commands:": "允许使用的指令:", "Allowed commands:": "允许使用的指令:",
"Internal Networks": "内部网络", "Internal Networks": "内部网络",
"External Networks": "外部网络", "External Networks": "外部网络",
"No External Networks": "无外部网络", "No External Networks": "无外部网络"
"reconnecting...": "重连中…",
"reverseProxyMsg2": "检查如何配置WebSocket",
"reverseProxyMsg1": "正在使用反向代理?",
"connecting...": "正在连接到socket服务器…",
"Cannot connect to the socket server.": "无法连接到socket服务器。",
"url": "网址 | 网址",
"extra": "额外",
"downStack": "停止并删除"
} }

View File

@ -1,12 +1,12 @@
{ {
"languageName": "繁體中文(台灣)", "languageName": "繁體中文(台灣)",
"Create your admin account": "建立您的管理員帳號", "Create your admin account": "建立您的管理員帳號",
"authIncorrectCreds": "使用者名稱或密碼錯誤", "authIncorrectCreds": "使用者名稱或密碼錯誤",
"PasswordsDoNotMatch": "兩次輸入的密碼不一致。", "PasswordsDoNotMatch": "兩次輸入的密碼不一致。",
"Repeat Password": "重複以確認密碼", "Repeat Password": "重複以確認密碼",
"Create": "建立", "Create": "建立",
"signedInDisp": "目前使用者:{0}", "signedInDisp": "目前使用者:{0}",
"signedInDispDisabled": "已停用身份驗證", "signedInDispDisabled": "已停用身份驗證",
"home": "首頁", "home": "首頁",
"console": "主控台", "console": "主控台",
"registry": "映像倉庫", "registry": "映像倉庫",
@ -24,7 +24,7 @@
"saveStackDraft": "儲存", "saveStackDraft": "儲存",
"notAvailableShort": "不可用", "notAvailableShort": "不可用",
"deleteStackMsg": "您確定要刪除這個堆疊嗎?", "deleteStackMsg": "您確定要刪除這個堆疊嗎?",
"stackNotManagedByDockgeMsg": "這個堆疊不由 Dockge 管理", "stackNotManagedByDockgeMsg": "這個堆疊不由 Dockge 管理",
"primaryHostname": "主機名稱", "primaryHostname": "主機名稱",
"general": "一般", "general": "一般",
"container": "容器 | 容器群組", "container": "容器 | 容器群組",
@ -51,7 +51,7 @@
"autoGet": "自動取得", "autoGet": "自動取得",
"add": "新增", "add": "新增",
"Edit": "編輯", "Edit": "編輯",
"applyToYAML": "用到 YAML", "applyToYAML": "用到YAML",
"createExternalNetwork": "建立", "createExternalNetwork": "建立",
"addInternalNetwork": "新增", "addInternalNetwork": "新增",
"Save": "儲存", "Save": "儲存",
@ -71,7 +71,7 @@
"Frontend Version": "前端版本", "Frontend Version": "前端版本",
"Check Update On GitHub": "在 GitHub 上檢查更新", "Check Update On GitHub": "在 GitHub 上檢查更新",
"Show update if available": "有更新時提醒我", "Show update if available": "有更新時提醒我",
"Also check beta release": "同時檢查 Beta 更新", "Also check beta release": "同時檢查 Beta 渠道更新",
"Remember me": "記住我", "Remember me": "記住我",
"Login": "登入", "Login": "登入",
"Username": "使用者名稱", "Username": "使用者名稱",
@ -90,13 +90,5 @@
"Allowed commands:": "允許使用的指令:", "Allowed commands:": "允許使用的指令:",
"Internal Networks": "內部網路", "Internal Networks": "內部網路",
"External Networks": "外部網路", "External Networks": "外部網路",
"No External Networks": "無外部網路", "No External Networks": "無外部網路"
"downStack": "停止",
"reverseProxyMsg1": "在使用反向代理嗎?",
"reverseProxyMsg2": "點擊這裡了解如何為 WebSocket 配置反向代理",
"Cannot connect to the socket server.": "無法連接到 Socket 伺服器。",
"reconnecting...": "重新連線中…",
"connecting...": "連線至 Socket 伺服器中…",
"url": "網址 | 網址",
"extra": "額外"
} }

View File

@ -16,8 +16,8 @@
<span class="fs-4 title">Dockge</span> <span class="fs-4 title">Dockge</span>
</router-link> </router-link>
<a v-if="hasNewVersion" target="_blank" href="https://github.com/louislam/dockge/releases" class="btn btn-warning me-3"> <a v-if="hasNewVersion" target="_blank" href="https://github.com/louislam/dockge/releases" class="btn btn-info me-3">
<font-awesome-icon icon="arrow-alt-circle-up" /> {{ $t("newUpdate") }} <font-awesome-icon icon="arrow-alt-circle-up" /> {{ $t("New Update") }}
</a> </a>
<ul class="nav nav-pills"> <ul class="nav nav-pills">

View File

@ -10,12 +10,12 @@ import { i18n } from "./i18n";
// Dependencies // Dependencies
import "bootstrap"; import "bootstrap";
import Toast, { POSITION, useToast } from "vue-toastification"; import Toast, { POSITION, useToast } from "vue-toastification";
import "@xterm/xterm/lib/xterm.js"; import "xterm/lib/xterm.js";
// CSS // CSS
import "@fontsource/jetbrains-mono"; import "@fontsource/jetbrains-mono";
import "vue-toastification/dist/index.css"; import "vue-toastification/dist/index.css";
import "@xterm/xterm/css/xterm.css"; import "xterm/css/xterm.css";
import "./styles/main.scss"; import "./styles/main.scss";
// Minxins // Minxins

View File

@ -2,7 +2,7 @@ import { io } from "socket.io-client";
import { Socket } from "socket.io-client"; import { Socket } from "socket.io-client";
import { defineComponent } from "vue"; import { defineComponent } from "vue";
import jwtDecode from "jwt-decode"; import jwtDecode from "jwt-decode";
import { Terminal } from "@xterm/xterm"; import { Terminal } from "xterm";
let socket : Socket; let socket : Socket;
@ -109,7 +109,7 @@ export default defineComponent({
}, 1500); }, 1500);
socket = io(url, { socket = io(url, {
transports: [ "websocket", "polling" ] transports: [ "websocket" ]
}); });
socket.on("connect", () => { socket.on("connect", () => {
@ -202,10 +202,6 @@ export default defineComponent({
} }
} }
}); });
socket.on("refresh", () => {
location.reload();
});
}, },
/** /**

View File

@ -56,13 +56,6 @@
</button> </button>
</div> </div>
<!-- URLs -->
<div v-if="urls.length > 0" class="mb-3">
<a v-for="(url, index) in urls" :key="index" target="_blank" :href="url.url">
<span class="badge bg-secondary me-2">{{ url.display }}</span>
</a>
</div>
<!-- Progress Terminal --> <!-- Progress Terminal -->
<transition name="slide-fade" appear> <transition name="slide-fade" appear>
<Terminal <Terminal
@ -118,20 +111,6 @@
<button v-if="false && isEditMode && jsonConfig.services && Object.keys(jsonConfig.services).length > 0" class="btn btn-normal mb-3" @click="addContainer">{{ $t("addContainer") }}</button> <button v-if="false && isEditMode && jsonConfig.services && Object.keys(jsonConfig.services).length > 0" class="btn btn-normal mb-3" @click="addContainer">{{ $t("addContainer") }}</button>
<!-- General -->
<div v-if="isEditMode">
<h4 class="mb-3">{{ $t("extra") }}</h4>
<div class="shadow-box big-padding mb-3">
<!-- URLs -->
<div class="mb-4">
<label class="form-label">
{{ $tc("url", 2) }}
</label>
<ArrayInput name="urls" :display-name="$t('url')" placeholder="https://" object-type="x-dockge" />
</div>
</div>
</div>
<!-- Combined Terminal Output --> <!-- Combined Terminal Output -->
<div v-show="!isEditMode"> <div v-show="!isEditMode">
<h4 class="mb-3">Terminal</h4> <h4 class="mb-3">Terminal</h4>
@ -154,7 +133,7 @@
ref="editor" ref="editor"
v-model="stack.composeYAML" v-model="stack.composeYAML"
class="yaml-editor" class="yaml-editor"
:highlight="highlighterYAML" :highlight="highlighter"
line-numbers :readonly="!isEditMode" line-numbers :readonly="!isEditMode"
@input="yamlCodeChange" @input="yamlCodeChange"
@focus="editorFocus = true" @focus="editorFocus = true"
@ -165,22 +144,6 @@
{{ yamlError }} {{ yamlError }}
</div> </div>
<!-- ENV editor -->
<div v-if="isEditMode">
<h4 class="mb-3">.env</h4>
<div class="shadow-box mb-3 editor-box" :class="{'edit-mode' : isEditMode}">
<prism-editor
ref="editor"
v-model="stack.composeENV"
class="env-editor"
:highlight="highlighterENV"
line-numbers :readonly="!isEditMode"
@focus="editorFocus = true"
@blur="editorFocus = false"
></prism-editor>
</div>
</div>
<div v-if="isEditMode"> <div v-if="isEditMode">
<!-- Volumes --> <!-- Volumes -->
<div v-if="false"> <div v-if="false">
@ -231,7 +194,7 @@ import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
import { import {
COMBINED_TERMINAL_COLS, COMBINED_TERMINAL_COLS,
COMBINED_TERMINAL_ROWS, COMBINED_TERMINAL_ROWS,
copyYAMLComments, envsubstYAML, copyYAMLComments,
getCombinedTerminalName, getCombinedTerminalName,
getComposeTerminalName, getComposeTerminalName,
PROGRESS_TERMINAL_ROWS, PROGRESS_TERMINAL_ROWS,
@ -239,7 +202,6 @@ import {
} from "../../../backend/util-common"; } from "../../../backend/util-common";
import { BModal } from "bootstrap-vue-next"; import { BModal } from "bootstrap-vue-next";
import NetworkInput from "../components/NetworkInput.vue"; import NetworkInput from "../components/NetworkInput.vue";
import dotenv from "dotenv";
const template = `version: "3.8" const template = `version: "3.8"
services: services:
@ -249,16 +211,10 @@ services:
ports: ports:
- "8080:80" - "8080:80"
`; `;
const envDefault = "# VARIABLE=value #comment";
let yamlErrorTimeout = null; let yamlErrorTimeout = null;
let serviceStatusTimeout = null; let serviceStatusTimeout = null;
let prismjsSymbolDefinition = {
"symbol": {
pattern: /(?<!\$)\$(\{[^{}]*\}|\w+)/,
}
};
export default { export default {
components: { components: {
@ -278,7 +234,6 @@ export default {
return { return {
editorFocus: false, editorFocus: false,
jsonConfig: {}, jsonConfig: {},
envsubstJSONConfig: {},
yamlError: "", yamlError: "",
processing: true, processing: true,
showProgressTerminal: false, showProgressTerminal: false,
@ -297,34 +252,6 @@ export default {
}; };
}, },
computed: { computed: {
urls() {
if (!this.envsubstJSONConfig["x-dockge"] || !this.envsubstJSONConfig["x-dockge"].urls || !Array.isArray(this.envsubstJSONConfig["x-dockge"].urls)) {
return [];
}
let urls = [];
for (const url of this.envsubstJSONConfig["x-dockge"].urls) {
let display;
try {
let obj = new URL(url);
let pathname = obj.pathname;
if (pathname === "/") {
pathname = "";
}
display = obj.host + pathname + obj.search;
} catch (e) {
display = url;
}
urls.push({
display,
url,
});
}
return urls;
},
isAdd() { isAdd() {
return this.$route.path === "/compose" && !this.submitted; return this.$route.path === "/compose" && !this.submitted;
}, },
@ -374,17 +301,6 @@ export default {
}, },
deep: true, deep: true,
}, },
"stack.composeENV": {
handler() {
if (this.editorFocus) {
console.debug("env code changed");
this.yamlCodeChange();
}
},
deep: true,
},
jsonConfig: { jsonConfig: {
handler() { handler() {
if (!this.editorFocus) { if (!this.editorFocus) {
@ -416,26 +332,19 @@ export default {
this.isEditMode = true; this.isEditMode = true;
let composeYAML; let composeYAML;
let composeENV;
if (this.$root.composeTemplate) { if (this.$root.composeTemplate) {
composeYAML = this.$root.composeTemplate; composeYAML = this.$root.composeTemplate;
this.$root.composeTemplate = ""; this.$root.composeTemplate = "";
} else { } else {
composeYAML = template; composeYAML = template;
} }
if (this.$root.envTemplate) {
composeENV = this.$root.envTemplate;
this.$root.envTemplate = "";
} else {
composeENV = envDefault;
}
// Default Values // Default Values
this.stack = { this.stack = {
name: "", name: "",
composeYAML, composeYAML,
composeENV,
isManagedByDockge: true, isManagedByDockge: true,
}; };
@ -534,7 +443,7 @@ export default {
this.bindTerminal(this.terminalName); this.bindTerminal(this.terminalName);
this.$root.getSocket().emit("deployStack", this.stack.name, this.stack.composeYAML, this.stack.composeENV, this.isAdd, (res) => { this.$root.getSocket().emit("deployStack", this.stack.name, this.stack.composeYAML, this.isAdd, (res) => {
this.processing = false; this.processing = false;
this.$root.toastRes(res); this.$root.toastRes(res);
@ -548,7 +457,7 @@ export default {
saveStack() { saveStack() {
this.processing = true; this.processing = true;
this.$root.getSocket().emit("saveStack", this.stack.name, this.stack.composeYAML, this.stack.composeENV, this.isAdd, (res) => { this.$root.getSocket().emit("saveStack", this.stack.name, this.stack.composeYAML, this.isAdd, (res) => {
this.processing = false; this.processing = false;
this.$root.toastRes(res); this.$root.toastRes(res);
@ -618,81 +527,32 @@ export default {
this.isEditMode = false; this.isEditMode = false;
}, },
highlighterYAML(code) { highlighter(code) {
if (!languages.yaml_with_symbols) { return highlight(code, languages.yaml);
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) {
let doc = parseDocument(yaml);
if (doc.errors.length > 0) {
throw doc.errors[0];
}
const config = doc.toJS() ?? {};
// Check data types
// "services" must be an object
if (!config.services) {
config.services = {};
}
if (Array.isArray(config.services) || typeof config.services !== "object") {
throw new Error("Services must be an object");
}
return {
config,
doc,
};
}, },
yamlCodeChange() { yamlCodeChange() {
try { try {
let { config, doc } = this.yamlToJSON(this.stack.composeYAML); let doc = parseDocument(this.stack.composeYAML);
if (doc.errors.length > 0) {
throw doc.errors[0];
}
const config = doc.toJS() ?? {};
// Check data types
// "services" must be an object
if (!config.services) {
config.services = {};
}
if (Array.isArray(config.services) || typeof config.services !== "object") {
throw new Error("Services must be an object");
}
this.yamlDoc = doc; this.yamlDoc = doc;
this.jsonConfig = config; this.jsonConfig = config;
let env = dotenv.parse(this.stack.composeENV);
let envYAML = envsubstYAML(this.stack.composeYAML, env);
this.envsubstJSONConfig = this.yamlToJSON(envYAML).config;
clearTimeout(yamlErrorTimeout); clearTimeout(yamlErrorTimeout);
this.yamlError = ""; this.yamlError = "";
} catch (e) { } catch (e) {

View File

@ -1,16 +1,12 @@
{ {
"name": "dockge", "name": "dockge",
"version": "1.3.4", "version": "1.1.1",
"type": "module", "type": "module",
"engines": {
"node": ">= 18.0.0 && <= 18.17.1"
},
"scripts": { "scripts": {
"fmt": "eslint \"**/*.{ts,vue}\" --fix", "fmt": "eslint \"**/*.{ts,vue}\" --fix",
"lint": "eslint \"**/*.{ts,vue}\"", "lint": "eslint \"**/*.{ts,vue}\"",
"check-ts": "tsc --noEmit", "check-ts": "tsc --noEmit",
"start": "tsx ./backend/index.ts", "start": "tsx ./backend/index.ts",
"dev": "concurrently -k -r \"wait-on tcp:5000 && pnpm run dev:backend \" \"pnpm run dev:frontend\"",
"dev:backend": "cross-env NODE_ENV=development tsx watch --inspect ./backend/index.ts", "dev:backend": "cross-env NODE_ENV=development tsx watch --inspect ./backend/index.ts",
"dev:frontend": "cross-env NODE_ENV=development vite --host --config ./frontend/vite.config.ts", "dev:frontend": "cross-env NODE_ENV=development vite --host --config ./frontend/vite.config.ts",
"release-final": "tsx ./extra/test-docker.ts && tsx extra/update-version.ts && pnpm run build:frontend && npm run build:docker", "release-final": "tsx ./extra/test-docker.ts && tsx extra/update-version.ts && pnpm run build:frontend && npm run build:docker",
@ -18,15 +14,12 @@
"build:docker-base": "docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/dockge:base -f ./docker/Base.Dockerfile . --push", "build:docker-base": "docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/dockge:base -f ./docker/Base.Dockerfile . --push",
"build:docker": "node ./extra/env2arg.js docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/dockge:latest -t louislam/dockge:1 -t louislam/dockge:$VERSION --target release -f ./docker/Dockerfile . --push", "build:docker": "node ./extra/env2arg.js docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/dockge:latest -t louislam/dockge:1 -t louislam/dockge:$VERSION --target release -f ./docker/Dockerfile . --push",
"build:docker-nightly": "pnpm run build:frontend && docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/dockge:nightly --target nightly -f ./docker/Dockerfile . --push", "build:docker-nightly": "pnpm run build:frontend && docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/dockge:nightly --target nightly -f ./docker/Dockerfile . --push",
"build:healthcheck": "docker buildx build -f docker/BuildHealthCheck.Dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/dockge:build-healthcheck . --push",
"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"
"reset-password": "tsx ./extra/reset-password.ts"
}, },
"dependencies": { "dependencies": {
"@homebridge/node-pty-prebuilt-multiarch": "~0.11.12", "@homebridge/node-pty-prebuilt-multiarch": "~0.11.11",
"@inventage/envsubst": "^0.16.0",
"@louislam/sqlite3": "~15.1.6", "@louislam/sqlite3": "~15.1.6",
"bcryptjs": "~2.4.3", "bcryptjs": "~2.4.3",
"check-password-strength": "~2.0.7", "check-password-strength": "~2.0.7",
@ -35,7 +28,6 @@
"composerize": "~1.4.1", "composerize": "~1.4.1",
"croner": "~7.0.5", "croner": "~7.0.5",
"dayjs": "~1.11.10", "dayjs": "~1.11.10",
"dotenv": "~16.3.1",
"express": "~4.18.2", "express": "~4.18.2",
"express-static-gzip": "~2.1.7", "express-static-gzip": "~2.1.7",
"http-graceful-shutdown": "~3.1.13", "http-graceful-shutdown": "~3.1.13",
@ -43,37 +35,33 @@
"jwt-decode": "~3.1.2", "jwt-decode": "~3.1.2",
"knex": "~2.5.1", "knex": "~2.5.1",
"limiter-es6-compat": "~2.1.2", "limiter-es6-compat": "~2.1.2",
"mysql2": "~3.6.5", "mysql2": "~3.6.3",
"promisify-child-process": "~4.1.2",
"redbean-node": "~0.3.3", "redbean-node": "~0.3.3",
"socket.io": "~4.7.2", "socket.io": "~4.7.2",
"socket.io-client": "~4.7.2", "socket.io-client": "~4.7.2",
"timezones-list": "~3.0.2", "timezones-list": "~3.0.2",
"ts-command-line-args": "~2.5.1", "ts-command-line-args": "~2.5.1",
"tsx": "~4.6.2", "tsx": "~3.14.0",
"type-fest": "~4.3.3", "type-fest": "~4.3.3",
"yaml": "~2.3.4" "yaml": "~2.3.4"
}, },
"devDependencies": { "devDependencies": {
"@actions/github": "^6.0.0", "@actions/github": "^6.0.0",
"@fontsource/jetbrains-mono": "^5.0.18", "@fontsource/jetbrains-mono": "^5.0.17",
"@fortawesome/fontawesome-svg-core": "6.4.2", "@fortawesome/fontawesome-svg-core": "6.4.2",
"@fortawesome/free-regular-svg-icons": "6.4.2", "@fortawesome/free-regular-svg-icons": "6.4.2",
"@fortawesome/free-solid-svg-icons": "6.4.2", "@fortawesome/free-solid-svg-icons": "6.4.2",
"@fortawesome/vue-fontawesome": "3.0.3", "@fortawesome/vue-fontawesome": "3.0.3",
"@types/bcryptjs": "^2.4.6", "@types/bcryptjs": "^2.4.6",
"@types/bootstrap": "~5.2.10", "@types/bootstrap": "~5.2.9",
"@types/command-exists": "~1.2.3", "@types/command-exists": "~1.2.3",
"@types/express": "~4.17.21", "@types/express": "~4.17.21",
"@types/jsonwebtoken": "~9.0.5", "@types/jsonwebtoken": "~9.0.5",
"@typescript-eslint/eslint-plugin": "~6.8.0", "@typescript-eslint/eslint-plugin": "~6.8.0",
"@typescript-eslint/parser": "~6.8.0", "@typescript-eslint/parser": "~6.8.0",
"@vitejs/plugin-vue": "~4.5.2", "@vitejs/plugin-vue": "~4.5.0",
"@xterm/addon-fit": "beta",
"@xterm/xterm": "beta",
"bootstrap": "5.3.2", "bootstrap": "5.3.2",
"bootstrap-vue-next": "~0.14.10", "bootstrap-vue-next": "~0.14.10",
"concurrently": "^8.2.2",
"cross-env": "~7.0.3", "cross-env": "~7.0.3",
"eslint": "~8.50.0", "eslint": "~8.50.0",
"eslint-plugin-jsdoc": "~46.8.2", "eslint-plugin-jsdoc": "~46.8.2",
@ -82,16 +70,16 @@
"sass": "~1.68.0", "sass": "~1.68.0",
"typescript": "~5.2.2", "typescript": "~5.2.2",
"unplugin-vue-components": "~0.25.2", "unplugin-vue-components": "~0.25.2",
"vite": "~5.0.7", "vite": "~5.0.0",
"vite-plugin-compression": "~0.5.1", "vite-plugin-compression": "~0.5.1",
"vue": "~3.3.11", "vue": "~3.3.8",
"vue-eslint-parser": "~9.3.2", "vue-eslint-parser": "~9.3.2",
"vue-i18n": "~9.5.0", "vue-i18n": "~9.5.0",
"vue-prism-editor": "2.0.0-alpha.2", "vue-prism-editor": "2.0.0-alpha.2",
"vue-qrcode": "~2.2.0", "vue-qrcode": "~2.2.0",
"vue-router": "~4.2.5", "vue-router": "~4.2.5",
"vue-toastification": "2.0.0-rc.5", "vue-toastification": "2.0.0-rc.5",
"wait-on": "^7.2.0", "xterm": "5.4.0-beta.37",
"xterm-addon-web-links": "~0.9.0" "xterm-addon-web-links": "~0.9.0"
} }
} }

1412
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff