mirror of
https://github.com/louislam/dockge.git
synced 2025-08-12 13:17:39 +02:00
Compare commits
191 Commits
1.3.0
...
fix-consol
Author | SHA1 | Date | |
---|---|---|---|
8902db0ae4 | |||
cdbf7f1843 | |||
c62b91682e | |||
970a826d5a | |||
86c7dfdb5b | |||
6c357fb603 | |||
90255f05cb | |||
c40c463788 | |||
9fedd8790d | |||
65c719d95d | |||
fb349e06b1 | |||
322f4ccee8 | |||
38d424d8bc | |||
4438adc04a | |||
053ea3643c | |||
b7b1435d62 | |||
9830bc345a | |||
9d8fbf1af2 | |||
fb366cbf24 | |||
fd3e4910e2 | |||
6c0d8da11e | |||
a2f96913c2 | |||
40fc0ebb06 | |||
be562ce66e | |||
3fff0dbd51 | |||
71e773ae9f | |||
74e9efd471 | |||
d451e06e84 | |||
a65a9f5549 | |||
9b73e44cd9 | |||
81818a19d1 | |||
1372bd2ce1 | |||
01906205f0 | |||
28337c5430 | |||
5baf48db63 | |||
b2c8fdab75 | |||
e12525fa42 | |||
3e3f67c6b7 | |||
020faa49d2 | |||
df95d7ce9d | |||
7a2524c542 | |||
6ceaa70cdd | |||
caea8996da | |||
39e3d5a07c | |||
723afb5bc2 | |||
3b3b3a7940 | |||
f9309a0650 | |||
54c2be7abe | |||
48db1c73a8 | |||
88f696d9b1 | |||
f80cfca64b | |||
1ddd70791a | |||
5f01347d2f | |||
04c9a8669d | |||
91b7c18c52 | |||
9cef4ad9ee | |||
e7dd099f17 | |||
d27fd2919b | |||
e2f5796470 | |||
88f26f53c5 | |||
ccd9d96227 | |||
a8dcfe4ccd | |||
941ec0056a | |||
1bb6f2532c | |||
6fb24adc66 | |||
c4fe952121 | |||
59bfe79c40 | |||
9e89f49e38 | |||
19beb02b1e | |||
9dd68372c2 | |||
109222f024 | |||
5ad42a6dab | |||
74c8baef93 | |||
c7ea2f9ee9 | |||
4a9173f5dc | |||
3d641090c0 | |||
32527100a0 | |||
30c69583a7 | |||
69cbe16745 | |||
f5df9a777c | |||
c33a469972 | |||
f667467091 | |||
2ff27b4073 | |||
8ad6702932 | |||
6a7d7b5e43 | |||
66747b7a73 | |||
051cc11eaa | |||
2e22f95720 | |||
4d10dc75a7 | |||
af2d40eeac | |||
5420c73960 | |||
2e6e2bda38 | |||
36c3f01d00 | |||
7e05f51676 | |||
f9baa9180f | |||
3204020749 | |||
6d8487c879 | |||
cb72629596 | |||
658c2828e2 | |||
22b9f04426 | |||
0992408fa0 | |||
a24b2199fa | |||
b7b1708696 | |||
17566fcd95 | |||
ec6bdea711 | |||
f8ad8c45fd | |||
c239f40acc | |||
8efa58e0d0 | |||
dbbefa6c09 | |||
a253f8ab25 | |||
701b0158b1 | |||
c94eb4805d | |||
4670121dfa | |||
9d5d062420 | |||
21f7a677a3 | |||
25026b1ed5 | |||
afe433dbfa | |||
480c498974 | |||
8fe75feb69 | |||
17046b500b | |||
75ff8e1d5c | |||
660da44938 | |||
193f975c4c | |||
7f1b03edab | |||
900ab8978f | |||
11e71f373d | |||
8bd432a4b6 | |||
7795bcab03 | |||
3ad7302e03 | |||
3140947174 | |||
2c7b938f69 | |||
d3a595b02f | |||
4b93794cda | |||
8b8a9d0f1f | |||
d4546e1a85 | |||
b8cff4cc51 | |||
cc02eee50c | |||
5578f28456 | |||
11f9302e62 | |||
012f7f1116 | |||
ea2b02587b | |||
91ba7761f9 | |||
91b3165ea8 | |||
f621d9c4c3 | |||
3e486008bb | |||
4049ae22f0 | |||
df58de180e | |||
07e259db5c | |||
1b84531b31 | |||
de2de0573b | |||
80e885e85d | |||
e54ede3f1c | |||
ac2a62abb1 | |||
e77ff3622d | |||
b5bd9a711a | |||
442c7fce67 | |||
7d55a84aa2 | |||
22bbba9652 | |||
3bc6779af4 | |||
3ef2be1c11 | |||
f6f7283f09 | |||
69e237a676 | |||
6a3eebfd57 | |||
3c137122b6 | |||
e2819afce1 | |||
94ca8a152a | |||
db0add7e4c | |||
0f52bb78b8 | |||
03c7815b58 | |||
80d5c685e5 | |||
a8482ec8ac | |||
07c52ccebb | |||
587d2dcaca | |||
5f1f3593fd | |||
316c566c76 | |||
d32bd3937f | |||
007eac7b58 | |||
b945ddea55 | |||
9b6b49947c | |||
3ba267a3dc | |||
01411f2d7e | |||
ba51031db6 | |||
daa8d12eee | |||
bd58de535e | |||
8c6bcef987 | |||
bec5460395 | |||
4e899dcf21 | |||
8296c7b18f | |||
607c908f2d | |||
bd5dd3c3ad | |||
6eca6dc59f |
37
.github/workflows/ci.yml
vendored
37
.github/workflows/ci.yml
vendored
@ -14,50 +14,31 @@ jobs:
|
|||||||
ci:
|
ci:
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
os: [ubuntu-latest, windows-latest, macos-latest, ARM64]
|
os: [ubuntu-latest, windows-latest, macos-latest, ARM, ARM64]
|
||||||
node: [18.17.1] # Can be changed
|
node: [22] # Can be changed
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Code
|
- name: Checkout Code
|
||||||
|
run: | # Mainly for Windows
|
||||||
|
git config --global core.autocrlf false
|
||||||
|
git config --global core.eol lf
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- run: git config --global core.autocrlf false # Mainly for Windows
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
|
|
||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: ${{matrix.node}}
|
node-version: ${{matrix.node}}
|
||||||
|
|
||||||
- uses: pnpm/action-setup@v2
|
|
||||||
name: Install pnpm
|
|
||||||
with:
|
|
||||||
version: 8
|
|
||||||
run_install: false
|
|
||||||
|
|
||||||
- name: Get pnpm store directory
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
- uses: actions/cache@v3
|
|
||||||
name: Setup pnpm cache
|
|
||||||
with:
|
|
||||||
path: ${{ env.STORE_PATH }}
|
|
||||||
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-pnpm-store-
|
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: pnpm install
|
run: npm install
|
||||||
|
|
||||||
- name: Lint
|
- name: Lint
|
||||||
run: pnpm run lint
|
run: npm run lint
|
||||||
|
|
||||||
- name: Check Typescript
|
- name: Check Typescript
|
||||||
run: pnpm run check-ts
|
run: npm run check-ts
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: pnpm run build:frontend
|
run: npm run build:frontend
|
||||||
# more things can be add later like tests etc..
|
# more things can be add later like tests etc..
|
||||||
|
|
||||||
|
22
.github/workflows/close-incorrect-issue.yml
vendored
22
.github/workflows/close-incorrect-issue.yml
vendored
@ -16,27 +16,5 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
- uses: pnpm/action-setup@v2
|
|
||||||
name: Install pnpm
|
|
||||||
with:
|
|
||||||
version: 8
|
|
||||||
run_install: false
|
|
||||||
|
|
||||||
- name: Get pnpm store directory
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
- uses: actions/cache@v3
|
|
||||||
name: Setup pnpm cache
|
|
||||||
with:
|
|
||||||
path: ${{ env.STORE_PATH }}
|
|
||||||
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-pnpm-store-
|
|
||||||
|
|
||||||
- name: Install dependencies
|
|
||||||
run: pnpm install
|
|
||||||
|
|
||||||
- name: Close Incorrect Issue
|
- name: Close Incorrect Issue
|
||||||
run: node extra/close-incorrect-issue.js ${{ secrets.GITHUB_TOKEN }} ${{ github.event.issue.number }} ${{ github.event.issue.user.login }}
|
run: node extra/close-incorrect-issue.js ${{ secrets.GITHUB_TOKEN }} ${{ github.event.issue.number }} ${{ github.event.issue.user.login }}
|
||||||
|
4
.github/workflows/json-yaml-validate.yml
vendored
4
.github/workflows/json-yaml-validate.yml
vendored
@ -17,11 +17,11 @@ jobs:
|
|||||||
json-yaml-validate:
|
json-yaml-validate:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: json-yaml-validate
|
- name: json-yaml-validate
|
||||||
id: json-yaml-validate
|
id: json-yaml-validate
|
||||||
uses: GrantBirki/json-yaml-validate@v1.3.0
|
uses: GrantBirki/json-yaml-validate@v2.6.1
|
||||||
with:
|
with:
|
||||||
comment: "false" # enable comment mode
|
comment: "false" # enable comment mode
|
||||||
exclude_file: ".github/config/exclude.txt" # gitignore style file for exclusions
|
exclude_file: ".github/config/exclude.txt" # gitignore style file for exclusions
|
||||||
|
@ -58,8 +58,7 @@ I personally do not like something that requires so many configurations before y
|
|||||||
|
|
||||||
## Tools
|
## Tools
|
||||||
|
|
||||||
- [`Node.js`](https://nodejs.org/) >= 20
|
- [`Node.js`](https://nodejs.org/) >= 22.14.0
|
||||||
- [`pnpm`](https://pnpm.io/)
|
|
||||||
- [`git`](https://git-scm.com/)
|
- [`git`](https://git-scm.com/)
|
||||||
- IDE that supports [`ESLint`](https://eslint.org/) and EditorConfig (I am using [`IntelliJ IDEA`](https://www.jetbrains.com/idea/))
|
- IDE that supports [`ESLint`](https://eslint.org/) and EditorConfig (I am using [`IntelliJ IDEA`](https://www.jetbrains.com/idea/))
|
||||||
- A SQLite GUI tool (f.ex. [`SQLite Expert Personal`](https://www.sqliteexpert.com/download.html) or [`DBeaver Community`](https://dbeaver.io/download/))
|
- A SQLite GUI tool (f.ex. [`SQLite Expert Personal`](https://www.sqliteexpert.com/download.html) or [`DBeaver Community`](https://dbeaver.io/download/))
|
||||||
@ -67,14 +66,14 @@ I personally do not like something that requires so many configurations before y
|
|||||||
## Install Dependencies for Development
|
## Install Dependencies for Development
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pnpm install
|
npm install
|
||||||
```
|
```
|
||||||
|
|
||||||
## Dev Server
|
## Dev Server
|
||||||
|
|
||||||
```
|
```
|
||||||
pnpm run dev:frontend
|
npm run dev:frontend
|
||||||
pnpm run dev:backend
|
npm run dev:backend
|
||||||
```
|
```
|
||||||
|
|
||||||
## Backend Dev Server
|
## Backend Dev Server
|
||||||
@ -94,7 +93,7 @@ You can use Vue.js devtools Chrome extension for debugging.
|
|||||||
### Build the frontend
|
### Build the frontend
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pnpm run build
|
npm run build
|
||||||
```
|
```
|
||||||
|
|
||||||
## Database Migration
|
## Database Migration
|
||||||
@ -117,7 +116,7 @@ Both frontend and backend share the same package.json. However, the frontend dep
|
|||||||
Should only be done by the maintainer.
|
Should only be done by the maintainer.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pnpm update
|
npm update
|
||||||
````
|
````
|
||||||
|
|
||||||
It should update the patch release version only.
|
It should update the patch release version only.
|
||||||
|
29
README.md
29
README.md
@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
A fancy, easy-to-use and reactive self-hosted docker compose.yaml stack-oriented manager.
|
A fancy, easy-to-use and reactive self-hosted docker compose.yaml stack-oriented manager.
|
||||||
|
|
||||||
      
|
[](https://github.com/louislam/dockge) [](https://hub.docker.com/r/louislam/dockge/tags) [](https://hub.docker.com/r/louislam/dockge/tags) [](https://github.com/louislam/dockge/commits/master/)
|
||||||
|
|
||||||
<img src="https://github.com/louislam/dockge/assets/1336778/26a583e1-ecb1-4a8d-aedf-76157d714ad7" width="900" alt="" />
|
<img src="https://github.com/louislam/dockge/assets/1336778/26a583e1-ecb1-4a8d-aedf-76157d714ad7" width="900" alt="" />
|
||||||
|
|
||||||
@ -14,20 +14,19 @@ View Video: https://youtu.be/AWAlOQeNpgU?t=48
|
|||||||
|
|
||||||
## ⭐ Features
|
## ⭐ Features
|
||||||
|
|
||||||
- Manage `compose.yaml`
|
- 🧑💼 Manage your `compose.yaml` files
|
||||||
- Create/Edit/Start/Stop/Restart/Delete
|
- Create/Edit/Start/Stop/Restart/Delete
|
||||||
- Update Docker Images
|
- Update Docker Images
|
||||||
- Interactive Editor for `compose.yaml`
|
- ⌨️ Interactive Editor for `compose.yaml`
|
||||||
- Interactive Web Terminal
|
- 🦦 Interactive Web Terminal
|
||||||
- Reactive
|
- 🕷️ (1.4.0 🆕) Multiple agents support - You can manage multiple stacks from different Docker hosts in one single interface
|
||||||
- Everything is just responsive. Progress (Pull/Up/Down) and terminal output are in real-time
|
- 🏪 Convert `docker run ...` commands into `compose.yaml`
|
||||||
- Easy-to-use & fancy UI
|
- 📙 File based structure - Dockge won't kidnap your compose files, they are stored on your drive as usual. You can interact with them using normal `docker compose` commands
|
||||||
- If you love Uptime Kuma's UI/UX, you will love this one too
|
|
||||||
- Convert `docker run ...` commands into `compose.yaml`
|
|
||||||
- File based structure
|
|
||||||
- Dockge won't kidnap your compose files, they are stored on your drive as usual. You can interact with them using normal `docker compose` commands
|
|
||||||
<img src="https://github.com/louislam/dockge/assets/1336778/cc071864-592e-4909-b73a-343a57494002" width=300 />
|
|
||||||
|
|
||||||
|
<img src="https://github.com/louislam/dockge/assets/1336778/cc071864-592e-4909-b73a-343a57494002" width=300 />
|
||||||
|
|
||||||
|
- 🚄 Reactive - Everything is just responsive. Progress (Pull/Up/Down) and terminal output are in real-time
|
||||||
|
- 🐣 Easy-to-use & fancy UI - If you love Uptime Kuma's UI/UX, you will love this one too
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
@ -131,7 +130,7 @@ Be sure to read the [guide](https://github.com/louislam/dockge/blob/master/CONTR
|
|||||||
|
|
||||||
#### "Dockge"?
|
#### "Dockge"?
|
||||||
|
|
||||||
"Dockge" is a coinage word which is created by myself. I hope it sounds like `Dodge`.
|
"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.
|
||||||
|
|
||||||
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,13 +147,13 @@ 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 replcement?
|
#### Is Dockge a Portainer replacement?
|
||||||
|
|
||||||
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.
|
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.
|
||||||
|
|
||||||
If you want to manage your container with docker-compose only, the answer may be yes.
|
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, signle containers, the answer may be no.
|
If you still need to manage something like docker networks, single containers, the answer may be no.
|
||||||
|
|
||||||
#### Can I install both Dockge and Portainer?
|
#### Can I install both Dockge and Portainer?
|
||||||
|
|
||||||
|
293
backend/agent-manager.ts
Normal file
293
backend/agent-manager.ts
Normal file
@ -0,0 +1,293 @@
|
|||||||
|
import { DockgeSocket } from "./util-server";
|
||||||
|
import { io, Socket as SocketClient } from "socket.io-client";
|
||||||
|
import { log } from "./log";
|
||||||
|
import { Agent } from "./models/agent";
|
||||||
|
import { isDev, LooseObject, sleep } from "../common/util-common";
|
||||||
|
import semver from "semver";
|
||||||
|
import { R } from "redbean-node";
|
||||||
|
import dayjs, { Dayjs } from "dayjs";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dockge Instance Manager
|
||||||
|
* One AgentManager per Socket connection
|
||||||
|
*/
|
||||||
|
export class AgentManager {
|
||||||
|
|
||||||
|
protected socket : DockgeSocket;
|
||||||
|
protected agentSocketList : Record<string, SocketClient> = {};
|
||||||
|
protected agentLoggedInList : Record<string, boolean> = {};
|
||||||
|
protected _firstConnectTime : Dayjs = dayjs();
|
||||||
|
|
||||||
|
constructor(socket: DockgeSocket) {
|
||||||
|
this.socket = socket;
|
||||||
|
}
|
||||||
|
|
||||||
|
get firstConnectTime() : Dayjs {
|
||||||
|
return this._firstConnectTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
test(url : string, username : string, password : string) : Promise<void> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
let obj = new URL(url);
|
||||||
|
let endpoint = obj.host;
|
||||||
|
|
||||||
|
if (!endpoint) {
|
||||||
|
reject(new Error("Invalid Dockge URL"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.agentSocketList[endpoint]) {
|
||||||
|
reject(new Error("The Dockge URL already exists"));
|
||||||
|
}
|
||||||
|
|
||||||
|
let client = io(url, {
|
||||||
|
reconnection: false,
|
||||||
|
extraHeaders: {
|
||||||
|
endpoint,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
client.on("connect", () => {
|
||||||
|
client.emit("login", {
|
||||||
|
username: username,
|
||||||
|
password: password,
|
||||||
|
}, (res : LooseObject) => {
|
||||||
|
if (res.ok) {
|
||||||
|
resolve();
|
||||||
|
} else {
|
||||||
|
reject(new Error(res.msg));
|
||||||
|
}
|
||||||
|
client.disconnect();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
client.on("connect_error", (err) => {
|
||||||
|
if (err.message === "xhr poll error") {
|
||||||
|
reject(new Error("Unable to connect to the Dockge instance"));
|
||||||
|
} else {
|
||||||
|
reject(err);
|
||||||
|
}
|
||||||
|
client.disconnect();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param url
|
||||||
|
* @param username
|
||||||
|
* @param password
|
||||||
|
*/
|
||||||
|
async add(url : string, username : string, password : string) : Promise<Agent> {
|
||||||
|
let bean = R.dispense("agent") as Agent;
|
||||||
|
bean.url = url;
|
||||||
|
bean.username = username;
|
||||||
|
bean.password = password;
|
||||||
|
await R.store(bean);
|
||||||
|
return bean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param url
|
||||||
|
*/
|
||||||
|
async remove(url : string) {
|
||||||
|
let bean = await R.findOne("agent", " url = ? ", [
|
||||||
|
url,
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (bean) {
|
||||||
|
await R.trash(bean);
|
||||||
|
let endpoint = bean.endpoint;
|
||||||
|
this.disconnect(endpoint);
|
||||||
|
this.sendAgentList();
|
||||||
|
delete this.agentSocketList[endpoint];
|
||||||
|
} else {
|
||||||
|
throw new Error("Agent not found");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
connect(url : string, username : string, password : string) {
|
||||||
|
let obj = new URL(url);
|
||||||
|
let endpoint = obj.host;
|
||||||
|
|
||||||
|
this.socket.emit("agentStatus", {
|
||||||
|
endpoint: endpoint,
|
||||||
|
status: "connecting",
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!endpoint) {
|
||||||
|
log.error("agent-manager", "Invalid endpoint: " + endpoint + " URL: " + url);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.agentSocketList[endpoint]) {
|
||||||
|
log.debug("agent-manager", "Already connected to the socket server: " + endpoint);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info("agent-manager", "Connecting to the socket server: " + endpoint);
|
||||||
|
let client = io(url, {
|
||||||
|
extraHeaders: {
|
||||||
|
endpoint,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
client.on("connect", () => {
|
||||||
|
log.info("agent-manager", "Connected to the socket server: " + endpoint);
|
||||||
|
|
||||||
|
client.emit("login", {
|
||||||
|
username: username,
|
||||||
|
password: password,
|
||||||
|
}, (res : LooseObject) => {
|
||||||
|
if (res.ok) {
|
||||||
|
log.info("agent-manager", "Logged in to the socket server: " + endpoint);
|
||||||
|
this.agentLoggedInList[endpoint] = true;
|
||||||
|
this.socket.emit("agentStatus", {
|
||||||
|
endpoint: endpoint,
|
||||||
|
status: "online",
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
log.error("agent-manager", "Failed to login to the socket server: " + endpoint);
|
||||||
|
this.agentLoggedInList[endpoint] = false;
|
||||||
|
this.socket.emit("agentStatus", {
|
||||||
|
endpoint: endpoint,
|
||||||
|
status: "offline",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
client.on("connect_error", (err) => {
|
||||||
|
log.error("agent-manager", "Error from the socket server: " + endpoint);
|
||||||
|
this.socket.emit("agentStatus", {
|
||||||
|
endpoint: endpoint,
|
||||||
|
status: "offline",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
client.on("disconnect", () => {
|
||||||
|
log.info("agent-manager", "Disconnected from the socket server: " + endpoint);
|
||||||
|
this.socket.emit("agentStatus", {
|
||||||
|
endpoint: endpoint,
|
||||||
|
status: "offline",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
client.on("agent", (...args : unknown[]) => {
|
||||||
|
this.socket.emit("agent", ...args);
|
||||||
|
});
|
||||||
|
|
||||||
|
client.on("info", (res) => {
|
||||||
|
log.debug("agent-manager", res);
|
||||||
|
|
||||||
|
// Disconnect if the version is lower than 1.4.0
|
||||||
|
if (!isDev && semver.satisfies(res.version, "< 1.4.0")) {
|
||||||
|
this.socket.emit("agentStatus", {
|
||||||
|
endpoint: endpoint,
|
||||||
|
status: "offline",
|
||||||
|
msg: `${endpoint}: Unsupported version: ` + res.version,
|
||||||
|
});
|
||||||
|
client.disconnect();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.agentSocketList[endpoint] = client;
|
||||||
|
}
|
||||||
|
|
||||||
|
disconnect(endpoint : string) {
|
||||||
|
let client = this.agentSocketList[endpoint];
|
||||||
|
client?.disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
async connectAll() {
|
||||||
|
this._firstConnectTime = dayjs();
|
||||||
|
|
||||||
|
if (this.socket.endpoint) {
|
||||||
|
log.info("agent-manager", "This connection is connected as an agent, skip connectAll()");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let list : Record<string, Agent> = await Agent.getAgentList();
|
||||||
|
|
||||||
|
if (Object.keys(list).length !== 0) {
|
||||||
|
log.info("agent-manager", "Connecting to all instance socket server(s)...");
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let endpoint in list) {
|
||||||
|
let agent = list[endpoint];
|
||||||
|
this.connect(agent.url, agent.username, agent.password);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
disconnectAll() {
|
||||||
|
for (let endpoint in this.agentSocketList) {
|
||||||
|
this.disconnect(endpoint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async emitToEndpoint(endpoint: string, eventName: string, ...args : unknown[]) {
|
||||||
|
log.debug("agent-manager", "Emitting event to endpoint: " + endpoint);
|
||||||
|
let client = this.agentSocketList[endpoint];
|
||||||
|
|
||||||
|
if (!client) {
|
||||||
|
log.error("agent-manager", "Socket client not found for endpoint: " + endpoint);
|
||||||
|
throw new Error("Socket client not found for endpoint: " + endpoint);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!client.connected || !this.agentLoggedInList[endpoint]) {
|
||||||
|
// Maybe the request is too quick, the socket is not connected yet, check firstConnectTime
|
||||||
|
// If it is within 10 seconds, we should apply retry logic here
|
||||||
|
let diff = dayjs().diff(this.firstConnectTime, "second");
|
||||||
|
log.debug("agent-manager", endpoint + ": diff: " + diff);
|
||||||
|
let ok = false;
|
||||||
|
while (diff < 10) {
|
||||||
|
if (client.connected && this.agentLoggedInList[endpoint]) {
|
||||||
|
log.debug("agent-manager", `${endpoint}: Connected & Logged in`);
|
||||||
|
ok = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
log.debug("agent-manager", endpoint + ": not ready yet, retrying in 1 second...");
|
||||||
|
await sleep(1000);
|
||||||
|
diff = dayjs().diff(this.firstConnectTime, "second");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ok) {
|
||||||
|
log.error("agent-manager", `${endpoint}: Socket client not connected`);
|
||||||
|
throw new Error("Socket client not connected for endpoint: " + endpoint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
client.emit("agent", endpoint, eventName, ...args);
|
||||||
|
}
|
||||||
|
|
||||||
|
emitToAllEndpoints(eventName: string, ...args : unknown[]) {
|
||||||
|
log.debug("agent-manager", "Emitting event to all endpoints");
|
||||||
|
for (let endpoint in this.agentSocketList) {
|
||||||
|
this.emitToEndpoint(endpoint, eventName, ...args).catch((e) => {
|
||||||
|
log.warn("agent-manager", e.message);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async sendAgentList() {
|
||||||
|
let list = await Agent.getAgentList();
|
||||||
|
let result : Record<string, LooseObject> = {};
|
||||||
|
|
||||||
|
// Myself
|
||||||
|
result[""] = {
|
||||||
|
url: "",
|
||||||
|
username: "",
|
||||||
|
endpoint: "",
|
||||||
|
};
|
||||||
|
|
||||||
|
for (let endpoint in list) {
|
||||||
|
let agent = list[endpoint];
|
||||||
|
result[endpoint] = agent.toJSON();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.socket.emit("agentList", {
|
||||||
|
ok: true,
|
||||||
|
agentList: result,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
7
backend/agent-socket-handler.ts
Normal file
7
backend/agent-socket-handler.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import { DockgeServer } from "./dockge-server";
|
||||||
|
import { AgentSocket } from "../common/agent-socket";
|
||||||
|
import { DockgeSocket } from "./util-server";
|
||||||
|
|
||||||
|
export abstract class AgentSocketHandler {
|
||||||
|
abstract create(socket : DockgeSocket, server : DockgeServer, agentSocket : AgentSocket): void;
|
||||||
|
}
|
@ -1,45 +1,46 @@
|
|||||||
import { SocketHandler } from "../socket-handler.js";
|
import { AgentSocketHandler } from "../agent-socket-handler";
|
||||||
import { DockgeServer } from "../dockge-server";
|
import { DockgeServer } from "../dockge-server";
|
||||||
import { callbackError, checkLogin, DockgeSocket, ValidationError } from "../util-server";
|
import { callbackError, callbackResult, checkLogin, DockgeSocket, ValidationError } from "../util-server";
|
||||||
import { Stack } from "../stack";
|
import { Stack } from "../stack";
|
||||||
|
import { AgentSocket } from "../../common/agent-socket";
|
||||||
|
|
||||||
// @ts-ignore
|
export class DockerSocketHandler extends AgentSocketHandler {
|
||||||
import composerize from "composerize";
|
create(socket : DockgeSocket, server : DockgeServer, agentSocket : AgentSocket) {
|
||||||
|
// Do not call super.create()
|
||||||
|
|
||||||
export class DockerSocketHandler extends SocketHandler {
|
agentSocket.on("deployStack", async (name : unknown, composeYAML : unknown, composeENV : unknown, isAdd : unknown, callback) => {
|
||||||
create(socket : DockgeSocket, server : DockgeServer) {
|
|
||||||
|
|
||||||
socket.on("deployStack", async (name : unknown, composeYAML : unknown, composeENV : unknown, isAdd : unknown, callback) => {
|
|
||||||
try {
|
try {
|
||||||
checkLogin(socket);
|
checkLogin(socket);
|
||||||
const stack = await this.saveStack(socket, server, name, composeYAML, composeENV, isAdd);
|
const stack = await this.saveStack(server, name, composeYAML, composeENV, isAdd);
|
||||||
await stack.deploy(socket);
|
await stack.deploy(socket);
|
||||||
server.sendStackList();
|
server.sendStackList();
|
||||||
callback({
|
callbackResult({
|
||||||
ok: true,
|
ok: true,
|
||||||
msg: "Deployed",
|
msg: "Deployed",
|
||||||
});
|
msgi18n: true,
|
||||||
|
}, callback);
|
||||||
stack.joinCombinedTerminal(socket);
|
stack.joinCombinedTerminal(socket);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
callbackError(e, callback);
|
callbackError(e, callback);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on("saveStack", async (name : unknown, composeYAML : unknown, composeENV : unknown, isAdd : unknown, callback) => {
|
agentSocket.on("saveStack", async (name : unknown, composeYAML : unknown, composeENV : unknown, isAdd : unknown, callback) => {
|
||||||
try {
|
try {
|
||||||
checkLogin(socket);
|
checkLogin(socket);
|
||||||
this.saveStack(socket, server, name, composeYAML, composeENV, isAdd);
|
await this.saveStack(server, name, composeYAML, composeENV, isAdd);
|
||||||
callback({
|
callbackResult({
|
||||||
ok: true,
|
ok: true,
|
||||||
"msg": "Saved"
|
msg: "Saved",
|
||||||
});
|
msgi18n: true,
|
||||||
|
}, callback);
|
||||||
server.sendStackList();
|
server.sendStackList();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
callbackError(e, callback);
|
callbackError(e, callback);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on("deleteStack", async (name : unknown, callback) => {
|
agentSocket.on("deleteStack", async (name : unknown, callback) => {
|
||||||
try {
|
try {
|
||||||
checkLogin(socket);
|
checkLogin(socket);
|
||||||
if (typeof(name) !== "string") {
|
if (typeof(name) !== "string") {
|
||||||
@ -55,17 +56,18 @@ export class DockerSocketHandler extends SocketHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
server.sendStackList();
|
server.sendStackList();
|
||||||
callback({
|
callbackResult({
|
||||||
ok: true,
|
ok: true,
|
||||||
msg: "Deleted"
|
msg: "Deleted",
|
||||||
});
|
msgi18n: true,
|
||||||
|
}, callback);
|
||||||
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
callbackError(e, callback);
|
callbackError(e, callback);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on("getStack", async (stackName : unknown, callback) => {
|
agentSocket.on("getStack", async (stackName : unknown, callback) => {
|
||||||
try {
|
try {
|
||||||
checkLogin(socket);
|
checkLogin(socket);
|
||||||
|
|
||||||
@ -79,31 +81,32 @@ export class DockerSocketHandler extends SocketHandler {
|
|||||||
stack.joinCombinedTerminal(socket);
|
stack.joinCombinedTerminal(socket);
|
||||||
}
|
}
|
||||||
|
|
||||||
callback({
|
callbackResult({
|
||||||
ok: true,
|
ok: true,
|
||||||
stack: stack.toJSON(),
|
stack: await stack.toJSON(socket.endpoint),
|
||||||
});
|
}, callback);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
callbackError(e, callback);
|
callbackError(e, callback);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// requestStackList
|
// requestStackList
|
||||||
socket.on("requestStackList", async (callback) => {
|
agentSocket.on("requestStackList", async (callback) => {
|
||||||
try {
|
try {
|
||||||
checkLogin(socket);
|
checkLogin(socket);
|
||||||
server.sendStackList();
|
server.sendStackList();
|
||||||
callback({
|
callbackResult({
|
||||||
ok: true,
|
ok: true,
|
||||||
msg: "Updated"
|
msg: "Updated",
|
||||||
});
|
msgi18n: true,
|
||||||
|
}, callback);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
callbackError(e, callback);
|
callbackError(e, callback);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// startStack
|
// startStack
|
||||||
socket.on("startStack", async (stackName : unknown, callback) => {
|
agentSocket.on("startStack", async (stackName : unknown, callback) => {
|
||||||
try {
|
try {
|
||||||
checkLogin(socket);
|
checkLogin(socket);
|
||||||
|
|
||||||
@ -113,10 +116,11 @@ export class DockerSocketHandler extends SocketHandler {
|
|||||||
|
|
||||||
const stack = await Stack.getStack(server, stackName);
|
const stack = await Stack.getStack(server, stackName);
|
||||||
await stack.start(socket);
|
await stack.start(socket);
|
||||||
callback({
|
callbackResult({
|
||||||
ok: true,
|
ok: true,
|
||||||
msg: "Started"
|
msg: "Started",
|
||||||
});
|
msgi18n: true,
|
||||||
|
}, callback);
|
||||||
server.sendStackList();
|
server.sendStackList();
|
||||||
|
|
||||||
stack.joinCombinedTerminal(socket);
|
stack.joinCombinedTerminal(socket);
|
||||||
@ -127,7 +131,7 @@ export class DockerSocketHandler extends SocketHandler {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// stopStack
|
// stopStack
|
||||||
socket.on("stopStack", async (stackName : unknown, callback) => {
|
agentSocket.on("stopStack", async (stackName : unknown, callback) => {
|
||||||
try {
|
try {
|
||||||
checkLogin(socket);
|
checkLogin(socket);
|
||||||
|
|
||||||
@ -137,10 +141,11 @@ export class DockerSocketHandler extends SocketHandler {
|
|||||||
|
|
||||||
const stack = await Stack.getStack(server, stackName);
|
const stack = await Stack.getStack(server, stackName);
|
||||||
await stack.stop(socket);
|
await stack.stop(socket);
|
||||||
callback({
|
callbackResult({
|
||||||
ok: true,
|
ok: true,
|
||||||
msg: "Stopped"
|
msg: "Stopped",
|
||||||
});
|
msgi18n: true,
|
||||||
|
}, callback);
|
||||||
server.sendStackList();
|
server.sendStackList();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
callbackError(e, callback);
|
callbackError(e, callback);
|
||||||
@ -148,7 +153,7 @@ export class DockerSocketHandler extends SocketHandler {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// restartStack
|
// restartStack
|
||||||
socket.on("restartStack", async (stackName : unknown, callback) => {
|
agentSocket.on("restartStack", async (stackName : unknown, callback) => {
|
||||||
try {
|
try {
|
||||||
checkLogin(socket);
|
checkLogin(socket);
|
||||||
|
|
||||||
@ -158,10 +163,11 @@ export class DockerSocketHandler extends SocketHandler {
|
|||||||
|
|
||||||
const stack = await Stack.getStack(server, stackName);
|
const stack = await Stack.getStack(server, stackName);
|
||||||
await stack.restart(socket);
|
await stack.restart(socket);
|
||||||
callback({
|
callbackResult({
|
||||||
ok: true,
|
ok: true,
|
||||||
msg: "Restarted"
|
msg: "Restarted",
|
||||||
});
|
msgi18n: true,
|
||||||
|
}, callback);
|
||||||
server.sendStackList();
|
server.sendStackList();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
callbackError(e, callback);
|
callbackError(e, callback);
|
||||||
@ -169,7 +175,7 @@ export class DockerSocketHandler extends SocketHandler {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// updateStack
|
// updateStack
|
||||||
socket.on("updateStack", async (stackName : unknown, callback) => {
|
agentSocket.on("updateStack", async (stackName : unknown, callback) => {
|
||||||
try {
|
try {
|
||||||
checkLogin(socket);
|
checkLogin(socket);
|
||||||
|
|
||||||
@ -179,10 +185,11 @@ export class DockerSocketHandler extends SocketHandler {
|
|||||||
|
|
||||||
const stack = await Stack.getStack(server, stackName);
|
const stack = await Stack.getStack(server, stackName);
|
||||||
await stack.update(socket);
|
await stack.update(socket);
|
||||||
callback({
|
callbackResult({
|
||||||
ok: true,
|
ok: true,
|
||||||
msg: "Updated"
|
msg: "Updated",
|
||||||
});
|
msgi18n: true,
|
||||||
|
}, callback);
|
||||||
server.sendStackList();
|
server.sendStackList();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
callbackError(e, callback);
|
callbackError(e, callback);
|
||||||
@ -190,7 +197,7 @@ export class DockerSocketHandler extends SocketHandler {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// down stack
|
// down stack
|
||||||
socket.on("downStack", async (stackName : unknown, callback) => {
|
agentSocket.on("downStack", async (stackName : unknown, callback) => {
|
||||||
try {
|
try {
|
||||||
checkLogin(socket);
|
checkLogin(socket);
|
||||||
|
|
||||||
@ -200,10 +207,11 @@ export class DockerSocketHandler extends SocketHandler {
|
|||||||
|
|
||||||
const stack = await Stack.getStack(server, stackName);
|
const stack = await Stack.getStack(server, stackName);
|
||||||
await stack.down(socket);
|
await stack.down(socket);
|
||||||
callback({
|
callbackResult({
|
||||||
ok: true,
|
ok: true,
|
||||||
msg: "Downed"
|
msg: "Downed",
|
||||||
});
|
msgi18n: true,
|
||||||
|
}, callback);
|
||||||
server.sendStackList();
|
server.sendStackList();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
callbackError(e, callback);
|
callbackError(e, callback);
|
||||||
@ -211,7 +219,7 @@ export class DockerSocketHandler extends SocketHandler {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Services status
|
// Services status
|
||||||
socket.on("serviceStatusList", async (stackName : unknown, callback) => {
|
agentSocket.on("serviceStatusList", async (stackName : unknown, callback) => {
|
||||||
try {
|
try {
|
||||||
checkLogin(socket);
|
checkLogin(socket);
|
||||||
|
|
||||||
@ -221,50 +229,31 @@ export class DockerSocketHandler extends SocketHandler {
|
|||||||
|
|
||||||
const stack = await Stack.getStack(server, stackName, true);
|
const stack = await Stack.getStack(server, stackName, true);
|
||||||
const serviceStatusList = Object.fromEntries(await stack.getServiceStatusList());
|
const serviceStatusList = Object.fromEntries(await stack.getServiceStatusList());
|
||||||
callback({
|
callbackResult({
|
||||||
ok: true,
|
ok: true,
|
||||||
serviceStatusList,
|
serviceStatusList,
|
||||||
});
|
}, callback);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
callbackError(e, callback);
|
callbackError(e, callback);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// getExternalNetworkList
|
// getExternalNetworkList
|
||||||
socket.on("getDockerNetworkList", async (callback) => {
|
agentSocket.on("getDockerNetworkList", async (callback) => {
|
||||||
try {
|
try {
|
||||||
checkLogin(socket);
|
checkLogin(socket);
|
||||||
const dockerNetworkList = server.getDockerNetworkList();
|
const dockerNetworkList = await server.getDockerNetworkList();
|
||||||
callback({
|
callbackResult({
|
||||||
ok: true,
|
ok: true,
|
||||||
dockerNetworkList,
|
dockerNetworkList,
|
||||||
});
|
}, callback);
|
||||||
} catch (e) {
|
|
||||||
callbackError(e, callback);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// composerize
|
|
||||||
socket.on("composerize", async (dockerRunCommand : unknown, callback) => {
|
|
||||||
try {
|
|
||||||
checkLogin(socket);
|
|
||||||
|
|
||||||
if (typeof(dockerRunCommand) !== "string") {
|
|
||||||
throw new ValidationError("dockerRunCommand must be a string");
|
|
||||||
}
|
|
||||||
|
|
||||||
const composeTemplate = composerize(dockerRunCommand);
|
|
||||||
callback({
|
|
||||||
ok: true,
|
|
||||||
composeTemplate,
|
|
||||||
});
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
callbackError(e, callback);
|
callbackError(e, callback);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async saveStack(socket : DockgeSocket, server : DockgeServer, name : unknown, composeYAML : unknown, composeENV : unknown, isAdd : unknown) : Promise<Stack> {
|
async saveStack(server : DockgeServer, name : unknown, composeYAML : unknown, composeENV : unknown, isAdd : unknown) : Promise<Stack> {
|
||||||
// Check types
|
// Check types
|
||||||
if (typeof(name) !== "string") {
|
if (typeof(name) !== "string") {
|
||||||
throw new ValidationError("Name must be a string");
|
throw new ValidationError("Name must be a string");
|
@ -1,24 +1,15 @@
|
|||||||
import { SocketHandler } from "../socket-handler.js";
|
|
||||||
import { DockgeServer } from "../dockge-server";
|
import { DockgeServer } from "../dockge-server";
|
||||||
import { callbackError, checkLogin, DockgeSocket, ValidationError } from "../util-server";
|
import { callbackError, callbackResult, checkLogin, DockgeSocket, ValidationError } from "../util-server";
|
||||||
import { log } from "../log";
|
import { log } from "../log";
|
||||||
import yaml from "yaml";
|
|
||||||
import path from "path";
|
|
||||||
import fs from "fs";
|
|
||||||
import {
|
|
||||||
allowedCommandList,
|
|
||||||
allowedRawKeys,
|
|
||||||
getComposeTerminalName, getContainerExecTerminalName,
|
|
||||||
isDev,
|
|
||||||
PROGRESS_TERMINAL_ROWS
|
|
||||||
} from "../util-common";
|
|
||||||
import { InteractiveTerminal, MainTerminal, Terminal } from "../terminal";
|
import { InteractiveTerminal, MainTerminal, Terminal } from "../terminal";
|
||||||
import { Stack } from "../stack";
|
import { Stack } from "../stack";
|
||||||
|
import { AgentSocketHandler } from "../agent-socket-handler";
|
||||||
|
import { AgentSocket } from "../../common/agent-socket";
|
||||||
|
|
||||||
export class TerminalSocketHandler extends SocketHandler {
|
export class TerminalSocketHandler extends AgentSocketHandler {
|
||||||
create(socket : DockgeSocket, server : DockgeServer) {
|
create(socket : DockgeSocket, server : DockgeServer, agentSocket : AgentSocket) {
|
||||||
|
|
||||||
socket.on("terminalInput", async (terminalName : unknown, cmd : unknown, errorCallback) => {
|
agentSocket.on("terminalInput", async (terminalName : unknown, cmd : unknown, callback) => {
|
||||||
try {
|
try {
|
||||||
checkLogin(socket);
|
checkLogin(socket);
|
||||||
|
|
||||||
@ -38,20 +29,20 @@ export class TerminalSocketHandler extends SocketHandler {
|
|||||||
throw new Error("Terminal not found or it is not a Interactive Terminal.");
|
throw new Error("Terminal not found or it is not a Interactive Terminal.");
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e instanceof Error) {
|
callbackError(e, callback);
|
||||||
errorCallback({
|
|
||||||
ok: false,
|
|
||||||
msg: e.message,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Main Terminal
|
// Main Terminal
|
||||||
socket.on("mainTerminal", async (terminalName : unknown, callback) => {
|
agentSocket.on("mainTerminal", async (terminalName : unknown, callback) => {
|
||||||
try {
|
try {
|
||||||
checkLogin(socket);
|
checkLogin(socket);
|
||||||
|
|
||||||
|
// Throw an error if console is not enabled
|
||||||
|
if (!server.config.enableConsole) {
|
||||||
|
throw new ValidationError("Console is not enabled.");
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: Reset the name here, force one main terminal for now
|
// TODO: Reset the name here, force one main terminal for now
|
||||||
terminalName = "console";
|
terminalName = "console";
|
||||||
|
|
||||||
@ -59,29 +50,41 @@ export class TerminalSocketHandler extends SocketHandler {
|
|||||||
throw new ValidationError("Terminal name must be a string.");
|
throw new ValidationError("Terminal name must be a string.");
|
||||||
}
|
}
|
||||||
|
|
||||||
log.debug("deployStack", "Terminal name: " + terminalName);
|
log.debug("mainTerminal", "Terminal name: " + terminalName);
|
||||||
|
|
||||||
let terminal = Terminal.getTerminal(terminalName);
|
let terminal = Terminal.getTerminal(terminalName);
|
||||||
|
|
||||||
if (!terminal) {
|
if (!terminal) {
|
||||||
terminal = new MainTerminal(server, terminalName);
|
terminal = new MainTerminal(server, terminalName);
|
||||||
terminal.rows = 50;
|
terminal.rows = 50;
|
||||||
log.debug("deployStack", "Terminal created");
|
log.debug("mainTerminal", "Terminal created");
|
||||||
}
|
}
|
||||||
|
|
||||||
terminal.join(socket);
|
terminal.join(socket);
|
||||||
terminal.start();
|
terminal.start();
|
||||||
|
|
||||||
callback({
|
callbackResult({
|
||||||
ok: true,
|
ok: true,
|
||||||
});
|
}, callback);
|
||||||
|
} catch (e) {
|
||||||
|
callbackError(e, callback);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Check if MainTerminal is enabled
|
||||||
|
agentSocket.on("checkMainTerminal", async (callback) => {
|
||||||
|
try {
|
||||||
|
checkLogin(socket);
|
||||||
|
callbackResult({
|
||||||
|
ok: server.config.enableConsole,
|
||||||
|
}, callback);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
callbackError(e, callback);
|
callbackError(e, callback);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Interactive Terminal for containers
|
// Interactive Terminal for containers
|
||||||
socket.on("interactiveTerminal", async (stackName : unknown, serviceName : unknown, shell : unknown, callback) => {
|
agentSocket.on("interactiveTerminal", async (stackName : unknown, serviceName : unknown, shell : unknown, callback) => {
|
||||||
try {
|
try {
|
||||||
checkLogin(socket);
|
checkLogin(socket);
|
||||||
|
|
||||||
@ -104,16 +107,16 @@ export class TerminalSocketHandler extends SocketHandler {
|
|||||||
const stack = await Stack.getStack(server, stackName);
|
const stack = await Stack.getStack(server, stackName);
|
||||||
stack.joinContainerTerminal(socket, serviceName, shell);
|
stack.joinContainerTerminal(socket, serviceName, shell);
|
||||||
|
|
||||||
callback({
|
callbackResult({
|
||||||
ok: true,
|
ok: true,
|
||||||
});
|
}, callback);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
callbackError(e, callback);
|
callbackError(e, callback);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Join Output Terminal
|
// Join Output Terminal
|
||||||
socket.on("terminalJoin", async (terminalName : unknown, callback) => {
|
agentSocket.on("terminalJoin", async (terminalName : unknown, callback) => {
|
||||||
if (typeof(callback) !== "function") {
|
if (typeof(callback) !== "function") {
|
||||||
log.debug("console", "Callback is not a function.");
|
log.debug("console", "Callback is not a function.");
|
||||||
return;
|
return;
|
||||||
@ -141,7 +144,7 @@ export class TerminalSocketHandler extends SocketHandler {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Leave Combined Terminal
|
// Leave Combined Terminal
|
||||||
socket.on("leaveCombinedTerminal", async (stackName : unknown, callback) => {
|
agentSocket.on("leaveCombinedTerminal", async (stackName : unknown, callback) => {
|
||||||
try {
|
try {
|
||||||
checkLogin(socket);
|
checkLogin(socket);
|
||||||
|
|
||||||
@ -154,17 +157,48 @@ export class TerminalSocketHandler extends SocketHandler {
|
|||||||
const stack = await Stack.getStack(server, stackName);
|
const stack = await Stack.getStack(server, stackName);
|
||||||
await stack.leaveCombinedTerminal(socket);
|
await stack.leaveCombinedTerminal(socket);
|
||||||
|
|
||||||
callback({
|
callbackResult({
|
||||||
ok: true,
|
ok: true,
|
||||||
});
|
}, callback);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
callbackError(e, callback);
|
callbackError(e, callback);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO: Resize Terminal
|
// Resize Terminal
|
||||||
socket.on("terminalResize", async (rows : unknown) => {
|
agentSocket.on("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}`
|
||||||
|
);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -3,69 +3,55 @@ 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";
|
||||||
|
|
||||||
let interval : NodeJS.Timeout;
|
class CheckVersion {
|
||||||
|
version = packageJSON.version;
|
||||||
|
latestVersion? : string;
|
||||||
|
interval? : NodeJS.Timeout;
|
||||||
|
|
||||||
export function startInterval() {
|
async 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";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const checkBeta = await Settings.get("checkBeta");
|
log.debug("update-checker", "Retrieving latest versions");
|
||||||
|
|
||||||
if (checkBeta && data.beta) {
|
try {
|
||||||
if (compareVersions.compare(data.beta, data.slow, ">")) {
|
const res = await fetch(CHECK_URL);
|
||||||
obj.latestVersion = data.beta;
|
const data = await res.json();
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (_) {
|
await check();
|
||||||
log.info("update-checker", "Failed to check for new versions");
|
this.interval = setInterval(check, UPDATE_CHECKER_INTERVAL_MS);
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
check();
|
|
||||||
interval = setInterval(check, UPDATE_CHECKER_INTERVAL_MS);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const checkVersion = new CheckVersion();
|
||||||
|
export default checkVersion;
|
||||||
|
@ -9,7 +9,7 @@ import knex from "knex";
|
|||||||
import Dialect from "knex/lib/dialects/sqlite3/index.js";
|
import Dialect from "knex/lib/dialects/sqlite3/index.js";
|
||||||
|
|
||||||
import sqlite from "@louislam/sqlite3";
|
import sqlite from "@louislam/sqlite3";
|
||||||
import { sleep } from "./util-common";
|
import { sleep } from "../common/util-common";
|
||||||
|
|
||||||
interface DBConfig {
|
interface DBConfig {
|
||||||
type?: "sqlite" | "mysql";
|
type?: "sqlite" | "mysql";
|
||||||
|
@ -1,3 +0,0 @@
|
|||||||
export class Docker {
|
|
||||||
|
|
||||||
}
|
|
@ -1,3 +1,4 @@
|
|||||||
|
import "dotenv/config";
|
||||||
import { MainRouter } from "./routers/main-router";
|
import { MainRouter } from "./routers/main-router";
|
||||||
import * as fs from "node:fs";
|
import * as fs from "node:fs";
|
||||||
import { PackageJson } from "type-fest";
|
import { PackageJson } from "type-fest";
|
||||||
@ -17,23 +18,26 @@ import { Settings } from "./settings";
|
|||||||
import checkVersion from "./check-version";
|
import checkVersion from "./check-version";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import { R } from "redbean-node";
|
import { R } from "redbean-node";
|
||||||
import { genSecret, isDev } from "./util-common";
|
import { genSecret, isDev, LooseObject } from "../common/util-common";
|
||||||
import { generatePasswordHash } from "./password-hash";
|
import { generatePasswordHash } from "./password-hash";
|
||||||
import { Bean } from "redbean-node/dist/bean";
|
import { Bean } from "redbean-node/dist/bean";
|
||||||
import { Arguments, Config, DockgeSocket } from "./util-server";
|
import { Arguments, Config, DockgeSocket } from "./util-server";
|
||||||
import { DockerSocketHandler } from "./socket-handlers/docker-socket-handler";
|
import { DockerSocketHandler } from "./agent-socket-handlers/docker-socket-handler";
|
||||||
import expressStaticGzip from "express-static-gzip";
|
import expressStaticGzip from "express-static-gzip";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import { TerminalSocketHandler } from "./socket-handlers/terminal-socket-handler";
|
import { TerminalSocketHandler } from "./agent-socket-handlers/terminal-socket-handler";
|
||||||
import { Stack } from "./stack";
|
import { Stack } from "./stack";
|
||||||
import { Cron } from "croner";
|
import { Cron } from "croner";
|
||||||
import gracefulShutdown from "http-graceful-shutdown";
|
import gracefulShutdown from "http-graceful-shutdown";
|
||||||
import User from "./models/user";
|
import User from "./models/user";
|
||||||
import childProcessAsync from "promisify-child-process";
|
import childProcessAsync from "promisify-child-process";
|
||||||
|
import { AgentManager } from "./agent-manager";
|
||||||
|
import { AgentProxySocketHandler } from "./socket-handlers/agent-proxy-socket-handler";
|
||||||
|
import { AgentSocketHandler } from "./agent-socket-handler";
|
||||||
|
import { AgentSocket } from "../common/agent-socket";
|
||||||
|
import { ManageAgentSocketHandler } from "./socket-handlers/manage-agent-socket-handler";
|
||||||
import { Terminal } from "./terminal";
|
import { Terminal } from "./terminal";
|
||||||
|
|
||||||
import "dotenv/config";
|
|
||||||
|
|
||||||
export class DockgeServer {
|
export class DockgeServer {
|
||||||
app : Express;
|
app : Express;
|
||||||
httpServer : http.Server;
|
httpServer : http.Server;
|
||||||
@ -50,10 +54,19 @@ export class DockgeServer {
|
|||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* List of socket handlers
|
* List of socket handlers (no agent support)
|
||||||
*/
|
*/
|
||||||
socketHandlerList : SocketHandler[] = [
|
socketHandlerList : SocketHandler[] = [
|
||||||
new MainSocketHandler(),
|
new MainSocketHandler(),
|
||||||
|
new ManageAgentSocketHandler(),
|
||||||
|
];
|
||||||
|
|
||||||
|
agentProxySocketHandler = new AgentProxySocketHandler();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of socket handlers (support agent)
|
||||||
|
*/
|
||||||
|
agentSocketHandlerList : AgentSocketHandler[] = [
|
||||||
new DockerSocketHandler(),
|
new DockerSocketHandler(),
|
||||||
new TerminalSocketHandler(),
|
new TerminalSocketHandler(),
|
||||||
];
|
];
|
||||||
@ -123,6 +136,11 @@ export class DockgeServer {
|
|||||||
stacksDir: {
|
stacksDir: {
|
||||||
type: String,
|
type: String,
|
||||||
optional: true,
|
optional: true,
|
||||||
|
},
|
||||||
|
enableConsole: {
|
||||||
|
type: Boolean,
|
||||||
|
optional: true,
|
||||||
|
defaultValue: false,
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -136,6 +154,7 @@ export class DockgeServer {
|
|||||||
this.config.hostname = args.hostname || process.env.DOCKGE_HOSTNAME || undefined;
|
this.config.hostname = args.hostname || process.env.DOCKGE_HOSTNAME || undefined;
|
||||||
this.config.dataDir = args.dataDir || process.env.DOCKGE_DATA_DIR || "./data/";
|
this.config.dataDir = args.dataDir || process.env.DOCKGE_DATA_DIR || "./data/";
|
||||||
this.config.stacksDir = args.stacksDir || process.env.DOCKGE_STACKS_DIR || defaultStacksDir;
|
this.config.stacksDir = args.stacksDir || process.env.DOCKGE_STACKS_DIR || defaultStacksDir;
|
||||||
|
this.config.enableConsole = args.enableConsole || process.env.DOCKGE_ENABLE_CONSOLE === "true" || false;
|
||||||
this.stacksDir = this.config.stacksDir;
|
this.stacksDir = this.config.stacksDir;
|
||||||
|
|
||||||
log.debug("server", this.config);
|
log.debug("server", this.config);
|
||||||
@ -194,23 +213,88 @@ 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) => {
|
||||||
|
let isOriginValid = true;
|
||||||
|
const bypass = isDev || process.env.UPTIME_KUMA_WS_ORIGIN_CHECK === "bypass";
|
||||||
|
|
||||||
|
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) => {
|
||||||
log.info("server", "Socket connected!");
|
let dockgeSocket = socket as DockgeSocket;
|
||||||
|
dockgeSocket.instanceManager = new AgentManager(dockgeSocket);
|
||||||
|
dockgeSocket.emitAgent = (event : string, ...args : unknown[]) => {
|
||||||
|
let obj = args[0];
|
||||||
|
if (typeof(obj) === "object") {
|
||||||
|
let obj2 = obj as LooseObject;
|
||||||
|
obj2.endpoint = dockgeSocket.endpoint;
|
||||||
|
}
|
||||||
|
dockgeSocket.emit("agent", event, ...args);
|
||||||
|
};
|
||||||
|
|
||||||
this.sendInfo(socket, true);
|
if (typeof(socket.request.headers.endpoint) === "string") {
|
||||||
|
dockgeSocket.endpoint = socket.request.headers.endpoint;
|
||||||
|
} else {
|
||||||
|
dockgeSocket.endpoint = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dockgeSocket.endpoint) {
|
||||||
|
log.info("server", "Socket connected (agent), as endpoint " + dockgeSocket.endpoint);
|
||||||
|
} else {
|
||||||
|
log.info("server", "Socket connected (direct)");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.sendInfo(dockgeSocket, true);
|
||||||
|
|
||||||
if (this.needSetup) {
|
if (this.needSetup) {
|
||||||
log.info("server", "Redirect to setup page");
|
log.info("server", "Redirect to setup page");
|
||||||
socket.emit("setup");
|
dockgeSocket.emit("setup");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create socket handlers
|
// Create socket handlers (original, no agent support)
|
||||||
for (const socketHandler of this.socketHandlerList) {
|
for (const socketHandler of this.socketHandlerList) {
|
||||||
socketHandler.create(socket as DockgeSocket, this);
|
socketHandler.create(dockgeSocket, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create Agent Socket
|
||||||
|
let agentSocket = new AgentSocket();
|
||||||
|
|
||||||
|
// Create agent socket handlers
|
||||||
|
for (const socketHandler of this.agentSocketHandlerList) {
|
||||||
|
socketHandler.create(dockgeSocket, this, agentSocket);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create agent proxy socket handlers
|
||||||
|
this.agentProxySocketHandler.create2(dockgeSocket, this, agentSocket);
|
||||||
|
|
||||||
// ***************************
|
// ***************************
|
||||||
// Better do anything after added all socket handlers here
|
// Better do anything after added all socket handlers here
|
||||||
// ***************************
|
// ***************************
|
||||||
@ -218,12 +302,18 @@ export class DockgeServer {
|
|||||||
log.debug("auth", "check auto login");
|
log.debug("auth", "check auto login");
|
||||||
if (await Settings.get("disableAuth")) {
|
if (await Settings.get("disableAuth")) {
|
||||||
log.info("auth", "Disabled Auth: auto login to admin");
|
log.info("auth", "Disabled Auth: auto login to admin");
|
||||||
this.afterLogin(socket as DockgeSocket, await R.findOne("user") as User);
|
this.afterLogin(dockgeSocket, await R.findOne("user") as User);
|
||||||
socket.emit("autoLogin");
|
dockgeSocket.emit("autoLogin");
|
||||||
} else {
|
} else {
|
||||||
log.debug("auth", "need auth");
|
log.debug("auth", "need auth");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Socket disconnect
|
||||||
|
dockgeSocket.on("disconnect", () => {
|
||||||
|
log.info("server", "Socket disconnected!");
|
||||||
|
dockgeSocket.instanceManager.disconnectAll();
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
this.io.on("disconnect", () => {
|
this.io.on("disconnect", () => {
|
||||||
@ -248,6 +338,11 @@ export class DockgeServer {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
log.error("server", e);
|
log.error("server", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
socket.instanceManager.sendAgentList();
|
||||||
|
|
||||||
|
// Also connect to other dockge instances
|
||||||
|
socket.instanceManager.connectAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -308,6 +403,7 @@ export class DockgeServer {
|
|||||||
this.sendStackList(true);
|
this.sendStackList(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
checkVersion.startInterval();
|
||||||
});
|
});
|
||||||
|
|
||||||
gracefulShutdown(this.httpServer, {
|
gracefulShutdown(this.httpServer, {
|
||||||
@ -485,26 +581,34 @@ export class DockgeServer {
|
|||||||
return jwtSecretBean;
|
return jwtSecretBean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send stack list to all connected sockets
|
||||||
|
* @param useCache
|
||||||
|
*/
|
||||||
async sendStackList(useCache = false) {
|
async sendStackList(useCache = false) {
|
||||||
let roomList = this.io.sockets.adapter.rooms.keys();
|
let socketList = this.io.sockets.sockets.values();
|
||||||
let map : Map<string, object> | undefined;
|
|
||||||
|
let stackList;
|
||||||
|
|
||||||
|
for (let socket of socketList) {
|
||||||
|
let dockgeSocket = socket as DockgeSocket;
|
||||||
|
|
||||||
for (let room of roomList) {
|
|
||||||
// Check if the room is a number (user id)
|
// Check if the room is a number (user id)
|
||||||
if (Number(room)) {
|
if (dockgeSocket.userID) {
|
||||||
|
|
||||||
// Get the list only if there is a room
|
// Get the list only if there is a logged in user
|
||||||
if (!map) {
|
if (!stackList) {
|
||||||
map = new Map();
|
stackList = await Stack.getStackList(this, useCache);
|
||||||
let stackList = await Stack.getStackList(this, useCache);
|
|
||||||
|
|
||||||
for (let [ stackName, stack ] of stackList) {
|
|
||||||
map.set(stackName, stack.toSimpleJSON());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
log.debug("server", "Send stack list to room " + room);
|
let map : Map<string, object> = new Map();
|
||||||
this.io.to(room).emit("stackList", {
|
|
||||||
|
for (let [ stackName, stack ] of stackList) {
|
||||||
|
map.set(stackName, stack.toSimpleJSON(dockgeSocket.endpoint));
|
||||||
|
}
|
||||||
|
|
||||||
|
log.debug("server", "Send stack list to user: " + dockgeSocket.id + " (" + dockgeSocket.endpoint + ")");
|
||||||
|
dockgeSocket.emitAgent("stackList", {
|
||||||
ok: true,
|
ok: true,
|
||||||
stackList: Object.fromEntries(map),
|
stackList: Object.fromEntries(map),
|
||||||
});
|
});
|
||||||
@ -512,25 +616,6 @@ export class DockgeServer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async sendStackStatusList() {
|
|
||||||
let statusList = await Stack.getStatusList();
|
|
||||||
|
|
||||||
let roomList = this.io.sockets.adapter.rooms.keys();
|
|
||||||
|
|
||||||
for (let room of roomList) {
|
|
||||||
// Check if the room is a number (user id)
|
|
||||||
if (Number(room)) {
|
|
||||||
log.debug("server", "Send stack status list to room " + room);
|
|
||||||
this.io.to(room).emit("stackStatusList", {
|
|
||||||
ok: true,
|
|
||||||
stackStatusList: Object.fromEntries(statusList),
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
log.debug("server", "Skip sending stack status list to room " + room);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async getDockerNetworkList() : Promise<string[]> {
|
async getDockerNetworkList() : Promise<string[]> {
|
||||||
let res = await childProcessAsync.spawn("docker", [ "network", "ls", "--format", "{{.Name}}" ], {
|
let res = await childProcessAsync.spawn("docker", [ "network", "ls", "--format", "{{.Name}}" ], {
|
||||||
encoding: "utf-8",
|
encoding: "utf-8",
|
||||||
@ -577,4 +662,35 @@ 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 | undefined, currentSocketID? : string) {
|
||||||
|
for (const rawSocket of this.io.sockets.sockets.values()) {
|
||||||
|
let socket = rawSocket as DockgeSocket;
|
||||||
|
if ((!userID || 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}`;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
// Console colors
|
// Console colors
|
||||||
// https://stackoverflow.com/questions/9781218/how-to-change-node-jss-console-font-color
|
// https://stackoverflow.com/questions/9781218/how-to-change-node-jss-console-font-color
|
||||||
import { intHash, isDev } from "./util-common";
|
import { intHash, isDev } from "../common/util-common";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
|
|
||||||
export const CONSOLE_STYLE_Reset = "\x1b[0m";
|
export const CONSOLE_STYLE_Reset = "\x1b[0m";
|
||||||
|
16
backend/migrations/2023-12-20-2117-agent-table.ts
Normal file
16
backend/migrations/2023-12-20-2117-agent-table.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
// Create the user table
|
||||||
|
return knex.schema.createTable("agent", (table) => {
|
||||||
|
table.increments("id");
|
||||||
|
table.string("url", 255).notNullable().unique();
|
||||||
|
table.string("username", 255).notNullable();
|
||||||
|
table.string("password", 255).notNullable();
|
||||||
|
table.boolean("active").notNullable().defaultTo(true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
return knex.schema.dropTable("agent");
|
||||||
|
}
|
31
backend/models/agent.ts
Normal file
31
backend/models/agent.ts
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import { BeanModel } from "redbean-node/dist/bean-model";
|
||||||
|
import { R } from "redbean-node";
|
||||||
|
import { LooseObject } from "../../common/util-common";
|
||||||
|
|
||||||
|
export class Agent extends BeanModel {
|
||||||
|
|
||||||
|
static async getAgentList() : Promise<Record<string, Agent>> {
|
||||||
|
let list = await R.findAll("agent") as Agent[];
|
||||||
|
let result : Record<string, Agent> = {};
|
||||||
|
for (let agent of list) {
|
||||||
|
result[agent.endpoint] = agent;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
get endpoint() : string {
|
||||||
|
let obj = new URL(this.url);
|
||||||
|
return obj.host;
|
||||||
|
}
|
||||||
|
|
||||||
|
toJSON() : LooseObject {
|
||||||
|
return {
|
||||||
|
url: this.url,
|
||||||
|
username: this.username,
|
||||||
|
endpoint: this.endpoint,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Agent;
|
@ -1,6 +1,6 @@
|
|||||||
import { R } from "redbean-node";
|
import { R } from "redbean-node";
|
||||||
import { log } from "./log";
|
import { log } from "./log";
|
||||||
import { LooseObject } from "./util-common";
|
import { LooseObject } from "../common/util-common";
|
||||||
|
|
||||||
export class Settings {
|
export class Settings {
|
||||||
|
|
||||||
|
47
backend/socket-handlers/agent-proxy-socket-handler.ts
Normal file
47
backend/socket-handlers/agent-proxy-socket-handler.ts
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
import { SocketHandler } from "../socket-handler.js";
|
||||||
|
import { DockgeServer } from "../dockge-server";
|
||||||
|
import { log } from "../log";
|
||||||
|
import { checkLogin, DockgeSocket } from "../util-server";
|
||||||
|
import { AgentSocket } from "../../common/agent-socket";
|
||||||
|
import { ALL_ENDPOINTS } from "../../common/util-common";
|
||||||
|
|
||||||
|
export class AgentProxySocketHandler extends SocketHandler {
|
||||||
|
|
||||||
|
create2(socket : DockgeSocket, server : DockgeServer, agentSocket : AgentSocket) {
|
||||||
|
// Agent - proxying requests if needed
|
||||||
|
socket.on("agent", async (endpoint : unknown, eventName : unknown, ...args : unknown[]) => {
|
||||||
|
try {
|
||||||
|
checkLogin(socket);
|
||||||
|
|
||||||
|
// Check Type
|
||||||
|
if (typeof(endpoint) !== "string") {
|
||||||
|
throw new Error("Endpoint must be a string: " + endpoint);
|
||||||
|
}
|
||||||
|
if (typeof(eventName) !== "string") {
|
||||||
|
throw new Error("Event name must be a string");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (endpoint === ALL_ENDPOINTS) { // Send to all endpoints
|
||||||
|
log.debug("agent", "Sending to all endpoints: " + eventName);
|
||||||
|
socket.instanceManager.emitToAllEndpoints(eventName, ...args);
|
||||||
|
|
||||||
|
} else if (!endpoint || endpoint === socket.endpoint) { // Direct connection or matching endpoint
|
||||||
|
log.debug("agent", "Matched endpoint: " + eventName);
|
||||||
|
agentSocket.call(eventName, ...args);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
log.debug("agent", "Proxying request to " + endpoint + " for " + eventName);
|
||||||
|
await socket.instanceManager.emitToEndpoint(endpoint, eventName, ...args);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
if (e instanceof Error) {
|
||||||
|
log.warn("agent", e.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
create(socket : DockgeSocket, server : DockgeServer) {
|
||||||
|
throw new Error("Method not implemented. Please use create2 instead.");
|
||||||
|
}
|
||||||
|
}
|
@ -1,3 +1,5 @@
|
|||||||
|
// @ts-ignore
|
||||||
|
import composerize from "composerize";
|
||||||
import { SocketHandler } from "../socket-handler.js";
|
import { SocketHandler } from "../socket-handler.js";
|
||||||
import { DockgeServer } from "../dockge-server";
|
import { DockgeServer } from "../dockge-server";
|
||||||
import { log } from "../log";
|
import { log } from "../log";
|
||||||
@ -5,7 +7,14 @@ import { R } from "redbean-node";
|
|||||||
import { loginRateLimiter, twoFaRateLimiter } from "../rate-limiter";
|
import { loginRateLimiter, twoFaRateLimiter } from "../rate-limiter";
|
||||||
import { generatePasswordHash, needRehashPassword, shake256, SHAKE256_LENGTH, verifyPassword } from "../password-hash";
|
import { generatePasswordHash, needRehashPassword, shake256, SHAKE256_LENGTH, verifyPassword } from "../password-hash";
|
||||||
import { User } from "../models/user";
|
import { User } from "../models/user";
|
||||||
import { checkLogin, DockgeSocket, doubleCheckPassword, JWTDecoded } from "../util-server";
|
import {
|
||||||
|
callbackError,
|
||||||
|
checkLogin,
|
||||||
|
DockgeSocket,
|
||||||
|
doubleCheckPassword,
|
||||||
|
JWTDecoded,
|
||||||
|
ValidationError
|
||||||
|
} from "../util-server";
|
||||||
import { passwordStrength } from "check-password-strength";
|
import { passwordStrength } from "check-password-strength";
|
||||||
import jwt from "jsonwebtoken";
|
import jwt from "jsonwebtoken";
|
||||||
import { Settings } from "../settings";
|
import { Settings } from "../settings";
|
||||||
@ -211,6 +220,8 @@ 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.",
|
||||||
@ -260,8 +271,6 @@ export class MainSocketHandler extends SocketHandler {
|
|||||||
await doubleCheckPassword(socket, currentPassword);
|
await doubleCheckPassword(socket, currentPassword);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(data);
|
|
||||||
|
|
||||||
await Settings.setSettings("general", data);
|
await Settings.setSettings("general", data);
|
||||||
|
|
||||||
callback({
|
callback({
|
||||||
@ -280,6 +289,37 @@ 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// composerize
|
||||||
|
socket.on("composerize", async (dockerRunCommand : unknown, callback) => {
|
||||||
|
try {
|
||||||
|
checkLogin(socket);
|
||||||
|
|
||||||
|
if (typeof(dockerRunCommand) !== "string") {
|
||||||
|
throw new ValidationError("dockerRunCommand must be a string");
|
||||||
|
}
|
||||||
|
|
||||||
|
const composeTemplate = composerize(dockerRunCommand);
|
||||||
|
callback({
|
||||||
|
ok: true,
|
||||||
|
composeTemplate,
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
callbackError(e, callback);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async login(username : string, password : string) : Promise<User | null> {
|
async login(username : string, password : string) : Promise<User | null> {
|
||||||
|
70
backend/socket-handlers/manage-agent-socket-handler.ts
Normal file
70
backend/socket-handlers/manage-agent-socket-handler.ts
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
import { SocketHandler } from "../socket-handler.js";
|
||||||
|
import { DockgeServer } from "../dockge-server";
|
||||||
|
import { log } from "../log";
|
||||||
|
import { callbackError, callbackResult, checkLogin, DockgeSocket } from "../util-server";
|
||||||
|
import { LooseObject } from "../../common/util-common";
|
||||||
|
|
||||||
|
export class ManageAgentSocketHandler extends SocketHandler {
|
||||||
|
|
||||||
|
create(socket : DockgeSocket, server : DockgeServer) {
|
||||||
|
// addAgent
|
||||||
|
socket.on("addAgent", async (requestData : unknown, callback : unknown) => {
|
||||||
|
try {
|
||||||
|
log.debug("manage-agent-socket-handler", "addAgent");
|
||||||
|
checkLogin(socket);
|
||||||
|
|
||||||
|
if (typeof(requestData) !== "object") {
|
||||||
|
throw new Error("Data must be an object");
|
||||||
|
}
|
||||||
|
|
||||||
|
let data = requestData as LooseObject;
|
||||||
|
let manager = socket.instanceManager;
|
||||||
|
await manager.test(data.url, data.username, data.password);
|
||||||
|
await manager.add(data.url, data.username, data.password);
|
||||||
|
|
||||||
|
// connect to the agent
|
||||||
|
manager.connect(data.url, data.username, data.password);
|
||||||
|
|
||||||
|
// Refresh another sockets
|
||||||
|
// It is a bit difficult to control another browser sessions to connect/disconnect agents, so force them to refresh the page will be easier.
|
||||||
|
server.disconnectAllSocketClients(undefined, socket.id);
|
||||||
|
manager.sendAgentList();
|
||||||
|
|
||||||
|
callbackResult({
|
||||||
|
ok: true,
|
||||||
|
msg: "agentAddedSuccessfully",
|
||||||
|
msgi18n: true,
|
||||||
|
}, callback);
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
callbackError(e, callback);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// removeAgent
|
||||||
|
socket.on("removeAgent", async (url : unknown, callback : unknown) => {
|
||||||
|
try {
|
||||||
|
log.debug("manage-agent-socket-handler", "removeAgent");
|
||||||
|
checkLogin(socket);
|
||||||
|
|
||||||
|
if (typeof(url) !== "string") {
|
||||||
|
throw new Error("URL must be a string");
|
||||||
|
}
|
||||||
|
|
||||||
|
let manager = socket.instanceManager;
|
||||||
|
await manager.remove(url);
|
||||||
|
|
||||||
|
server.disconnectAllSocketClients(undefined, socket.id);
|
||||||
|
manager.sendAgentList();
|
||||||
|
|
||||||
|
callbackResult({
|
||||||
|
ok: true,
|
||||||
|
msg: "agentRemovedSuccessfully",
|
||||||
|
msgi18n: true,
|
||||||
|
}, callback);
|
||||||
|
} catch (e) {
|
||||||
|
callbackError(e, callback);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -5,6 +5,7 @@ import yaml from "yaml";
|
|||||||
import { DockgeSocket, fileExists, ValidationError } from "./util-server";
|
import { DockgeSocket, fileExists, ValidationError } from "./util-server";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import {
|
import {
|
||||||
|
acceptedComposeFileNames,
|
||||||
COMBINED_TERMINAL_COLS,
|
COMBINED_TERMINAL_COLS,
|
||||||
COMBINED_TERMINAL_ROWS,
|
COMBINED_TERMINAL_ROWS,
|
||||||
CREATED_FILE,
|
CREATED_FILE,
|
||||||
@ -14,9 +15,10 @@ import {
|
|||||||
PROGRESS_TERMINAL_ROWS,
|
PROGRESS_TERMINAL_ROWS,
|
||||||
RUNNING, TERMINAL_ROWS,
|
RUNNING, TERMINAL_ROWS,
|
||||||
UNKNOWN
|
UNKNOWN
|
||||||
} from "./util-common";
|
} from "../common/util-common";
|
||||||
import { InteractiveTerminal, Terminal } from "./terminal";
|
import { InteractiveTerminal, Terminal } from "./terminal";
|
||||||
import childProcessAsync from "promisify-child-process";
|
import childProcessAsync from "promisify-child-process";
|
||||||
|
import { Settings } from "./settings";
|
||||||
|
|
||||||
export class Stack {
|
export class Stack {
|
||||||
|
|
||||||
@ -40,8 +42,7 @@ export class Stack {
|
|||||||
|
|
||||||
if (!skipFSOperations) {
|
if (!skipFSOperations) {
|
||||||
// Check if compose file name is different from compose.yaml
|
// Check if compose file name is different from compose.yaml
|
||||||
const supportedFileNames = [ "compose.yaml", "compose.yml", "docker-compose.yml", "docker-compose.yaml" ];
|
for (const filename of acceptedComposeFileNames) {
|
||||||
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;
|
||||||
@ -50,22 +51,41 @@ export class Stack {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
toJSON() : object {
|
async toJSON(endpoint : string) : Promise<object> {
|
||||||
let obj = this.toSimpleJSON();
|
|
||||||
|
// Since we have multiple agents now, embed primary hostname in the stack object too.
|
||||||
|
let primaryHostname = await Settings.get("primaryHostname");
|
||||||
|
if (!primaryHostname) {
|
||||||
|
if (!endpoint) {
|
||||||
|
primaryHostname = "localhost";
|
||||||
|
} else {
|
||||||
|
// Use the endpoint as the primary hostname
|
||||||
|
try {
|
||||||
|
primaryHostname = (new URL("https://" + endpoint).hostname);
|
||||||
|
} catch (e) {
|
||||||
|
// Just in case if the endpoint is in a incorrect format
|
||||||
|
primaryHostname = "localhost";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let obj = this.toSimpleJSON(endpoint);
|
||||||
return {
|
return {
|
||||||
...obj,
|
...obj,
|
||||||
composeYAML: this.composeYAML,
|
composeYAML: this.composeYAML,
|
||||||
composeENV: this.composeENV,
|
composeENV: this.composeENV,
|
||||||
|
primaryHostname,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
toSimpleJSON() : object {
|
toSimpleJSON(endpoint : string) : object {
|
||||||
return {
|
return {
|
||||||
name: this.name,
|
name: this.name,
|
||||||
status: this._status,
|
status: this._status,
|
||||||
tags: [],
|
tags: [],
|
||||||
isManagedByDockge: this.isManagedByDockge,
|
isManagedByDockge: this.isManagedByDockge,
|
||||||
composeFileName: this._composeFileName,
|
composeFileName: this._composeFileName,
|
||||||
|
endpoint,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -186,8 +206,8 @@ export class Stack {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async deploy(socket? : DockgeSocket) : Promise<number> {
|
async deploy(socket : DockgeSocket) : Promise<number> {
|
||||||
const terminalName = getComposeTerminalName(this.name);
|
const terminalName = getComposeTerminalName(socket.endpoint, this.name);
|
||||||
let exitCode = await Terminal.exec(this.server, socket, terminalName, "docker", [ "compose", "up", "-d", "--remove-orphans" ], this.path);
|
let exitCode = await Terminal.exec(this.server, socket, terminalName, "docker", [ "compose", "up", "-d", "--remove-orphans" ], this.path);
|
||||||
if (exitCode !== 0) {
|
if (exitCode !== 0) {
|
||||||
throw new Error("Failed to deploy, please check the terminal output for more information.");
|
throw new Error("Failed to deploy, please check the terminal output for more information.");
|
||||||
@ -195,8 +215,8 @@ export class Stack {
|
|||||||
return exitCode;
|
return exitCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
async delete(socket?: DockgeSocket) : Promise<number> {
|
async delete(socket: DockgeSocket) : Promise<number> {
|
||||||
const terminalName = getComposeTerminalName(this.name);
|
const terminalName = getComposeTerminalName(socket.endpoint, this.name);
|
||||||
let exitCode = await Terminal.exec(this.server, socket, terminalName, "docker", [ "compose", "down", "--remove-orphans" ], this.path);
|
let exitCode = await Terminal.exec(this.server, socket, terminalName, "docker", [ "compose", "down", "--remove-orphans" ], this.path);
|
||||||
if (exitCode !== 0) {
|
if (exitCode !== 0) {
|
||||||
throw new Error("Failed to delete, please check the terminal output for more information.");
|
throw new Error("Failed to delete, please check the terminal output for more information.");
|
||||||
@ -222,6 +242,26 @@ export class Stack {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if a compose file exists in the specified directory.
|
||||||
|
* @async
|
||||||
|
* @static
|
||||||
|
* @param {string} stacksDir - The directory of the stack.
|
||||||
|
* @param {string} filename - The name of the directory to check for the compose file.
|
||||||
|
* @returns {Promise<boolean>} A promise that resolves to a boolean indicating whether any compose file exists.
|
||||||
|
*/
|
||||||
|
static async composeFileExists(stacksDir : string, filename : string) : Promise<boolean> {
|
||||||
|
let filenamePath = path.join(stacksDir, filename);
|
||||||
|
// Check if any compose file exists
|
||||||
|
for (const filename of acceptedComposeFileNames) {
|
||||||
|
let composeFile = path.join(filenamePath, filename);
|
||||||
|
if (await fileExists(composeFile)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
static async getStackList(server : DockgeServer, useCacheForManaged = false) : Promise<Map<string, Stack>> {
|
static async getStackList(server : DockgeServer, useCacheForManaged = false) : Promise<Map<string, Stack>> {
|
||||||
let stacksDir = server.stacksDir;
|
let stacksDir = server.stacksDir;
|
||||||
let stackList : Map<string, Stack>;
|
let stackList : Map<string, Stack>;
|
||||||
@ -242,6 +282,10 @@ export class Stack {
|
|||||||
if (!stat.isDirectory()) {
|
if (!stat.isDirectory()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
// If no compose file exists, skip it
|
||||||
|
if (!await Stack.composeFileExists(stacksDir, filename)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
let stack = await this.getStack(server, filename);
|
let stack = await this.getStack(server, filename);
|
||||||
stack._status = CREATED_FILE;
|
stack._status = CREATED_FILE;
|
||||||
stackList.set(filename, stack);
|
stackList.set(filename, stack);
|
||||||
@ -297,7 +341,12 @@ export class Stack {
|
|||||||
let res = await childProcessAsync.spawn("docker", [ "compose", "ls", "--all", "--format", "json" ], {
|
let res = await childProcessAsync.spawn("docker", [ "compose", "ls", "--all", "--format", "json" ], {
|
||||||
encoding: "utf-8",
|
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));
|
||||||
@ -359,7 +408,7 @@ export class Stack {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async start(socket: DockgeSocket) {
|
async start(socket: DockgeSocket) {
|
||||||
const terminalName = getComposeTerminalName(this.name);
|
const terminalName = getComposeTerminalName(socket.endpoint, this.name);
|
||||||
let exitCode = await Terminal.exec(this.server, socket, terminalName, "docker", [ "compose", "up", "-d", "--remove-orphans" ], this.path);
|
let exitCode = await Terminal.exec(this.server, socket, terminalName, "docker", [ "compose", "up", "-d", "--remove-orphans" ], this.path);
|
||||||
if (exitCode !== 0) {
|
if (exitCode !== 0) {
|
||||||
throw new Error("Failed to start, please check the terminal output for more information.");
|
throw new Error("Failed to start, please check the terminal output for more information.");
|
||||||
@ -368,7 +417,7 @@ export class Stack {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async stop(socket: DockgeSocket) : Promise<number> {
|
async stop(socket: DockgeSocket) : Promise<number> {
|
||||||
const terminalName = getComposeTerminalName(this.name);
|
const terminalName = getComposeTerminalName(socket.endpoint, this.name);
|
||||||
let exitCode = await Terminal.exec(this.server, socket, terminalName, "docker", [ "compose", "stop" ], this.path);
|
let exitCode = await Terminal.exec(this.server, socket, terminalName, "docker", [ "compose", "stop" ], this.path);
|
||||||
if (exitCode !== 0) {
|
if (exitCode !== 0) {
|
||||||
throw new Error("Failed to stop, please check the terminal output for more information.");
|
throw new Error("Failed to stop, please check the terminal output for more information.");
|
||||||
@ -377,7 +426,7 @@ export class Stack {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async restart(socket: DockgeSocket) : Promise<number> {
|
async restart(socket: DockgeSocket) : Promise<number> {
|
||||||
const terminalName = getComposeTerminalName(this.name);
|
const terminalName = getComposeTerminalName(socket.endpoint, this.name);
|
||||||
let exitCode = await Terminal.exec(this.server, socket, terminalName, "docker", [ "compose", "restart" ], this.path);
|
let exitCode = await Terminal.exec(this.server, socket, terminalName, "docker", [ "compose", "restart" ], this.path);
|
||||||
if (exitCode !== 0) {
|
if (exitCode !== 0) {
|
||||||
throw new Error("Failed to restart, please check the terminal output for more information.");
|
throw new Error("Failed to restart, please check the terminal output for more information.");
|
||||||
@ -386,7 +435,7 @@ export class Stack {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async down(socket: DockgeSocket) : Promise<number> {
|
async down(socket: DockgeSocket) : Promise<number> {
|
||||||
const terminalName = getComposeTerminalName(this.name);
|
const terminalName = getComposeTerminalName(socket.endpoint, this.name);
|
||||||
let exitCode = await Terminal.exec(this.server, socket, terminalName, "docker", [ "compose", "down" ], this.path);
|
let exitCode = await Terminal.exec(this.server, socket, terminalName, "docker", [ "compose", "down" ], this.path);
|
||||||
if (exitCode !== 0) {
|
if (exitCode !== 0) {
|
||||||
throw new Error("Failed to down, please check the terminal output for more information.");
|
throw new Error("Failed to down, please check the terminal output for more information.");
|
||||||
@ -395,7 +444,7 @@ export class Stack {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async update(socket: DockgeSocket) {
|
async update(socket: DockgeSocket) {
|
||||||
const terminalName = getComposeTerminalName(this.name);
|
const terminalName = getComposeTerminalName(socket.endpoint, this.name);
|
||||||
let exitCode = await Terminal.exec(this.server, socket, terminalName, "docker", [ "compose", "pull" ], this.path);
|
let exitCode = await Terminal.exec(this.server, socket, terminalName, "docker", [ "compose", "pull" ], this.path);
|
||||||
if (exitCode !== 0) {
|
if (exitCode !== 0) {
|
||||||
throw new Error("Failed to pull, please check the terminal output for more information.");
|
throw new Error("Failed to pull, please check the terminal output for more information.");
|
||||||
@ -416,7 +465,7 @@ export class Stack {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async joinCombinedTerminal(socket: DockgeSocket) {
|
async joinCombinedTerminal(socket: DockgeSocket) {
|
||||||
const terminalName = getCombinedTerminalName(this.name);
|
const terminalName = getCombinedTerminalName(socket.endpoint, this.name);
|
||||||
const terminal = Terminal.getOrCreateTerminal(this.server, terminalName, "docker", [ "compose", "logs", "-f", "--tail", "100" ], this.path);
|
const terminal = Terminal.getOrCreateTerminal(this.server, terminalName, "docker", [ "compose", "logs", "-f", "--tail", "100" ], this.path);
|
||||||
terminal.enableKeepAlive = true;
|
terminal.enableKeepAlive = true;
|
||||||
terminal.rows = COMBINED_TERMINAL_ROWS;
|
terminal.rows = COMBINED_TERMINAL_ROWS;
|
||||||
@ -426,7 +475,7 @@ export class Stack {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async leaveCombinedTerminal(socket: DockgeSocket) {
|
async leaveCombinedTerminal(socket: DockgeSocket) {
|
||||||
const terminalName = getCombinedTerminalName(this.name);
|
const terminalName = getCombinedTerminalName(socket.endpoint, this.name);
|
||||||
const terminal = Terminal.getTerminal(terminalName);
|
const terminal = Terminal.getTerminal(terminalName);
|
||||||
if (terminal) {
|
if (terminal) {
|
||||||
terminal.leave(socket);
|
terminal.leave(socket);
|
||||||
@ -434,7 +483,7 @@ export class Stack {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async joinContainerTerminal(socket: DockgeSocket, serviceName: string, shell : string = "sh", index: number = 0) {
|
async joinContainerTerminal(socket: DockgeSocket, serviceName: string, shell : string = "sh", index: number = 0) {
|
||||||
const terminalName = getContainerExecTerminalName(this.name, serviceName, index);
|
const terminalName = getContainerExecTerminalName(socket.endpoint, this.name, serviceName, index);
|
||||||
let terminal = Terminal.getTerminal(terminalName);
|
let terminal = Terminal.getTerminal(terminalName);
|
||||||
|
|
||||||
if (!terminal) {
|
if (!terminal) {
|
||||||
|
@ -4,11 +4,10 @@ import * as pty from "@homebridge/node-pty-prebuilt-multiarch";
|
|||||||
import { LimitQueue } from "./utils/limit-queue";
|
import { LimitQueue } from "./utils/limit-queue";
|
||||||
import { DockgeSocket } from "./util-server";
|
import { DockgeSocket } from "./util-server";
|
||||||
import {
|
import {
|
||||||
allowedCommandList, allowedRawKeys,
|
|
||||||
PROGRESS_TERMINAL_ROWS,
|
PROGRESS_TERMINAL_ROWS,
|
||||||
TERMINAL_COLS,
|
TERMINAL_COLS,
|
||||||
TERMINAL_ROWS
|
TERMINAL_ROWS
|
||||||
} from "./util-common";
|
} from "../common/util-common";
|
||||||
import { sync as commandExistsSync } from "command-exists";
|
import { sync as commandExistsSync } from "command-exists";
|
||||||
import { log } from "./log";
|
import { log } from "./log";
|
||||||
|
|
||||||
@ -16,7 +15,6 @@ import { log } from "./log";
|
|||||||
* Terminal for running commands, no user interaction
|
* Terminal for running commands, no user interaction
|
||||||
*/
|
*/
|
||||||
export class Terminal {
|
export class Terminal {
|
||||||
|
|
||||||
protected static terminalMap : Map<string, Terminal> = new Map();
|
protected static terminalMap : Map<string, Terminal> = new Map();
|
||||||
|
|
||||||
protected _ptyProcess? : pty.IPty;
|
protected _ptyProcess? : pty.IPty;
|
||||||
@ -34,6 +32,9 @@ export class Terminal {
|
|||||||
|
|
||||||
public enableKeepAlive : boolean = false;
|
public enableKeepAlive : boolean = false;
|
||||||
protected keepAliveInterval? : NodeJS.Timeout;
|
protected keepAliveInterval? : NodeJS.Timeout;
|
||||||
|
protected kickDisconnectedClientsInterval? : NodeJS.Timeout;
|
||||||
|
|
||||||
|
protected socketList : Record<string, DockgeSocket> = {};
|
||||||
|
|
||||||
constructor(server : DockgeServer, name : string, file : string, args : string | string[], cwd : string) {
|
constructor(server : DockgeServer, name : string, file : string, args : string | string[], cwd : string) {
|
||||||
this.server = server;
|
this.server = server;
|
||||||
@ -67,6 +68,7 @@ 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) {
|
||||||
@ -81,13 +83,22 @@ export class Terminal {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.kickDisconnectedClientsInterval = setInterval(() => {
|
||||||
|
for (const socketID in this.socketList) {
|
||||||
|
const socket = this.socketList[socketID];
|
||||||
|
if (!socket.connected) {
|
||||||
|
log.debug("Terminal", "Kicking disconnected client " + socket.id + " from terminal " + this.name);
|
||||||
|
this.leave(socket);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, 60 * 1000);
|
||||||
|
|
||||||
if (this.enableKeepAlive) {
|
if (this.enableKeepAlive) {
|
||||||
log.debug("Terminal", "Keep alive enabled for terminal " + this.name);
|
log.debug("Terminal", "Keep alive enabled for terminal " + this.name);
|
||||||
|
|
||||||
// Close if there is no clients
|
// Close if there is no clients
|
||||||
this.keepAliveInterval = setInterval(() => {
|
this.keepAliveInterval = setInterval(() => {
|
||||||
const clients = this.server.io.sockets.adapter.rooms.get(this.name);
|
const numClients = Object.keys(this.socketList).length;
|
||||||
const numClients = clients ? clients.size : 0;
|
|
||||||
|
|
||||||
if (numClients === 0) {
|
if (numClients === 0) {
|
||||||
log.debug("Terminal", "Terminal " + this.name + " has no client, closing...");
|
log.debug("Terminal", "Terminal " + this.name + " has no client, closing...");
|
||||||
@ -111,8 +122,10 @@ export class Terminal {
|
|||||||
// On Data
|
// On Data
|
||||||
this._ptyProcess.onData((data) => {
|
this._ptyProcess.onData((data) => {
|
||||||
this.buffer.pushItem(data);
|
this.buffer.pushItem(data);
|
||||||
if (this.server.io) {
|
|
||||||
this.server.io.to(this.name).emit("terminalWrite", this.name, data);
|
for (const socketID in this.socketList) {
|
||||||
|
const socket = this.socketList[socketID];
|
||||||
|
socket.emitAgent("terminalWrite", this.name, data);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -136,15 +149,19 @@ export class Terminal {
|
|||||||
* @param res
|
* @param res
|
||||||
*/
|
*/
|
||||||
protected exit = (res : {exitCode: number, signal?: number | undefined}) => {
|
protected exit = (res : {exitCode: number, signal?: number | undefined}) => {
|
||||||
this.server.io.to(this.name).emit("terminalExit", this.name, res.exitCode);
|
for (const socketID in this.socketList) {
|
||||||
|
const socket = this.socketList[socketID];
|
||||||
|
socket.emitAgent("terminalExit", this.name, res.exitCode);
|
||||||
|
}
|
||||||
|
|
||||||
// Remove room
|
// Remove all clients
|
||||||
this.server.io.in(this.name).socketsLeave(this.name);
|
this.socketList = {};
|
||||||
|
|
||||||
Terminal.terminalMap.delete(this.name);
|
Terminal.terminalMap.delete(this.name);
|
||||||
log.debug("Terminal", "Terminal " + this.name + " exited with code " + res.exitCode);
|
log.debug("Terminal", "Terminal " + this.name + " exited with code " + res.exitCode);
|
||||||
|
|
||||||
clearInterval(this.keepAliveInterval);
|
clearInterval(this.keepAliveInterval);
|
||||||
|
clearInterval(this.kickDisconnectedClientsInterval);
|
||||||
|
|
||||||
if (this.callback) {
|
if (this.callback) {
|
||||||
this.callback(res.exitCode);
|
this.callback(res.exitCode);
|
||||||
@ -156,11 +173,11 @@ export class Terminal {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public join(socket : DockgeSocket) {
|
public join(socket : DockgeSocket) {
|
||||||
socket.join(this.name);
|
this.socketList[socket.id] = socket;
|
||||||
}
|
}
|
||||||
|
|
||||||
public leave(socket : DockgeSocket) {
|
public leave(socket : DockgeSocket) {
|
||||||
socket.leave(this.name);
|
delete this.socketList[socket.id];
|
||||||
}
|
}
|
||||||
|
|
||||||
public get ptyProcess() {
|
public get ptyProcess() {
|
||||||
@ -253,6 +270,11 @@ export class MainTerminal extends InteractiveTerminal {
|
|||||||
constructor(server : DockgeServer, name : string) {
|
constructor(server : DockgeServer, name : string) {
|
||||||
let shell;
|
let shell;
|
||||||
|
|
||||||
|
// Throw an error if console is not enabled
|
||||||
|
if (!server.config.enableConsole) {
|
||||||
|
throw new Error("Console is not enabled.");
|
||||||
|
}
|
||||||
|
|
||||||
if (os.platform() === "win32") {
|
if (os.platform() === "win32") {
|
||||||
if (commandExistsSync("pwsh.exe")) {
|
if (commandExistsSync("pwsh.exe")) {
|
||||||
shell = "pwsh.exe";
|
shell = "pwsh.exe";
|
||||||
@ -266,21 +288,6 @@ export class MainTerminal extends InteractiveTerminal {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public write(input : string) {
|
public write(input : string) {
|
||||||
// For like Ctrl + C
|
|
||||||
if (allowedRawKeys.includes(input)) {
|
|
||||||
super.write(input);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if the command is allowed
|
|
||||||
const cmdParts = input.split(" ");
|
|
||||||
const executable = cmdParts[0].trim();
|
|
||||||
log.debug("console", "Executable: " + executable);
|
|
||||||
log.debug("console", "Executable length: " + executable.length);
|
|
||||||
|
|
||||||
if (!allowedCommandList.includes(executable)) {
|
|
||||||
throw new Error("Command not allowed.");
|
|
||||||
}
|
|
||||||
super.write(input);
|
super.write(input);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,10 +2,11 @@ import { Socket } from "socket.io";
|
|||||||
import { Terminal } from "./terminal";
|
import { Terminal } from "./terminal";
|
||||||
import { randomBytes } from "crypto";
|
import { randomBytes } from "crypto";
|
||||||
import { log } from "./log";
|
import { log } from "./log";
|
||||||
import { ERROR_TYPE_VALIDATION } from "./util-common";
|
import { ERROR_TYPE_VALIDATION } from "../common/util-common";
|
||||||
import { R } from "redbean-node";
|
import { R } from "redbean-node";
|
||||||
import { verifyPassword } from "./password-hash";
|
import { verifyPassword } from "./password-hash";
|
||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
|
import { AgentManager } from "./agent-manager";
|
||||||
|
|
||||||
export interface JWTDecoded {
|
export interface JWTDecoded {
|
||||||
username : string;
|
username : string;
|
||||||
@ -15,6 +16,9 @@ export interface JWTDecoded {
|
|||||||
export interface DockgeSocket extends Socket {
|
export interface DockgeSocket extends Socket {
|
||||||
userID: number;
|
userID: number;
|
||||||
consoleTerminal? : Terminal;
|
consoleTerminal? : Terminal;
|
||||||
|
instanceManager : AgentManager;
|
||||||
|
endpoint : string;
|
||||||
|
emitAgent : (eventName : string, ...args : unknown[]) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
// For command line arguments, so they are nullable
|
// For command line arguments, so they are nullable
|
||||||
@ -26,6 +30,7 @@ export interface Arguments {
|
|||||||
hostname? : string;
|
hostname? : string;
|
||||||
dataDir? : string;
|
dataDir? : string;
|
||||||
stacksDir? : string;
|
stacksDir? : string;
|
||||||
|
enableConsole? : boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Some config values are required
|
// Some config values are required
|
||||||
@ -56,18 +61,28 @@ export function callbackError(error : unknown, callback : unknown) {
|
|||||||
callback({
|
callback({
|
||||||
ok: false,
|
ok: false,
|
||||||
msg: error.message,
|
msg: error.message,
|
||||||
|
msgi18n: true,
|
||||||
});
|
});
|
||||||
} else if (error instanceof ValidationError) {
|
} else if (error instanceof ValidationError) {
|
||||||
callback({
|
callback({
|
||||||
ok: false,
|
ok: false,
|
||||||
type: ERROR_TYPE_VALIDATION,
|
type: ERROR_TYPE_VALIDATION,
|
||||||
msg: error.message,
|
msg: error.message,
|
||||||
|
msgi18n: true,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
log.debug("console", "Unknown error: " + error);
|
log.debug("console", "Unknown error: " + error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function callbackResult(result : unknown, callback : unknown) {
|
||||||
|
if (typeof(callback) !== "function") {
|
||||||
|
log.error("console", "Callback is not a function");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
callback(result);
|
||||||
|
}
|
||||||
|
|
||||||
export async function doubleCheckPassword(socket : DockgeSocket, currentPassword : unknown) {
|
export async function doubleCheckPassword(socket : DockgeSocket, currentPassword : unknown) {
|
||||||
if (typeof currentPassword !== "string") {
|
if (typeof currentPassword !== "string") {
|
||||||
throw new Error("Wrong data type?");
|
throw new Error("Wrong data type?");
|
||||||
|
15
common/agent-socket.ts
Normal file
15
common/agent-socket.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
export class AgentSocket {
|
||||||
|
|
||||||
|
eventList : Map<string, (...args : unknown[]) => void> = new Map();
|
||||||
|
|
||||||
|
on(event : string, callback : (...args : unknown[]) => void) {
|
||||||
|
this.eventList.set(event, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
call(eventName : string, ...args : unknown[]) {
|
||||||
|
const callback = this.eventList.get(eventName);
|
||||||
|
if (callback) {
|
||||||
|
callback(...args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,13 +1,17 @@
|
|||||||
/*
|
/*
|
||||||
* Common utilities for backend and frontend
|
* Common utilities for backend and frontend
|
||||||
*/
|
*/
|
||||||
import { Document } from "yaml";
|
import yaml, { Document, Pair, Scalar } 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);
|
||||||
@ -17,6 +21,11 @@ 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();
|
||||||
|
|
||||||
@ -34,6 +43,8 @@ async function initRandomBytes() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const ALL_ENDPOINTS = "##ALL_DOCKGE_ENDPOINTS##";
|
||||||
|
|
||||||
// Stack Status
|
// Stack Status
|
||||||
export const UNKNOWN = 0;
|
export const UNKNOWN = 0;
|
||||||
export const CREATED_FILE = 1;
|
export const CREATED_FILE = 1;
|
||||||
@ -96,15 +107,11 @@ export const COMBINED_TERMINAL_ROWS = 20;
|
|||||||
|
|
||||||
export const ERROR_TYPE_VALIDATION = 1;
|
export const ERROR_TYPE_VALIDATION = 1;
|
||||||
|
|
||||||
export const allowedCommandList : string[] = [
|
export const acceptedComposeFileNames = [
|
||||||
"docker",
|
"compose.yaml",
|
||||||
"ls",
|
"docker-compose.yaml",
|
||||||
"cd",
|
"docker-compose.yml",
|
||||||
"dir",
|
"compose.yml",
|
||||||
];
|
|
||||||
|
|
||||||
export const allowedRawKeys = [
|
|
||||||
"\u0003", // Ctrl + C
|
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -190,20 +197,20 @@ export function getCryptoRandomInt(min: number, max: number):number {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getComposeTerminalName(stack : string) {
|
export function getComposeTerminalName(endpoint : string, stack : string) {
|
||||||
return "compose-" + stack;
|
return "compose-" + endpoint + "-" + stack;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getCombinedTerminalName(stack : string) {
|
export function getCombinedTerminalName(endpoint : string, stack : string) {
|
||||||
return "combined-" + stack;
|
return "combined-" + endpoint + "-" + stack;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getContainerTerminalName(container : string) {
|
export function getContainerTerminalName(endpoint : string, container : string) {
|
||||||
return "container-" + container;
|
return "container-" + endpoint + "-" + container;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getContainerExecTerminalName(stackName : string, container : string, index : number) {
|
export function getContainerExecTerminalName(endpoint : string, stackName : string, container : string, index : number) {
|
||||||
return "container-exec-" + stackName + "-" + container + "-" + index;
|
return "container-exec-" + endpoint + "-" + stackName + "-" + container + "-" + index;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function copyYAMLComments(doc : Document, src : Document) {
|
export function copyYAMLComments(doc : Document, src : Document) {
|
||||||
@ -218,42 +225,63 @@ export function copyYAMLComments(doc : Document, src : Document) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Copy yaml comments from srcItems to items
|
* Copy yaml comments from srcItems to items
|
||||||
* Typescript is super annoying here, so I have to use any here
|
* Attempts to preserve comments by matching content rather than just array indices
|
||||||
* TODO: Since comments are belong to the array index, the comments will be lost if the order of the items is changed or removed or added.
|
|
||||||
*/
|
*/
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
function copyYAMLCommentsItems(items : any, srcItems : any) {
|
function copyYAMLCommentsItems(items: any, srcItems: any) {
|
||||||
if (!items || !srcItems) {
|
if (!items || !srcItems) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// First pass - try to match items by their content
|
||||||
for (let i = 0; i < items.length; i++) {
|
for (let i = 0; i < items.length; i++) {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
const item : any = items[i];
|
const item: any = items[i];
|
||||||
|
|
||||||
|
// Try to find matching source item by content
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
const srcItem : any = srcItems[i];
|
const srcIndex = srcItems.findIndex((srcItem: any) =>
|
||||||
|
JSON.stringify(srcItem.value) === JSON.stringify(item.value) &&
|
||||||
|
JSON.stringify(srcItem.key) === JSON.stringify(item.key)
|
||||||
|
);
|
||||||
|
|
||||||
if (!srcItem) {
|
if (srcIndex !== -1) {
|
||||||
continue;
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
}
|
const srcItem: any = srcItems[srcIndex];
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
const nextSrcItem: any = srcItems[srcIndex + 1];
|
||||||
|
|
||||||
if (item.key && srcItem.key) {
|
if (item.key && srcItem.key) {
|
||||||
item.key.comment = srcItem.key.comment;
|
item.key.comment = srcItem.key.comment;
|
||||||
item.key.commentBefore = srcItem.key.commentBefore;
|
item.key.commentBefore = srcItem.key.commentBefore;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (srcItem.comment) {
|
if (srcItem.comment) {
|
||||||
item.comment = srcItem.comment;
|
item.comment = srcItem.comment;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (item.value && srcItem.value) {
|
// Handle comments between array items
|
||||||
if (typeof item.value === "object" && typeof srcItem.value === "object") {
|
if (nextSrcItem && nextSrcItem.commentBefore) {
|
||||||
item.value.comment = srcItem.value.comment;
|
if (items[i + 1]) {
|
||||||
item.value.commentBefore = srcItem.value.commentBefore;
|
items[i + 1].commentBefore = nextSrcItem.commentBefore;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (item.value.items && srcItem.value.items) {
|
// Handle trailing comments after array items
|
||||||
copyYAMLCommentsItems(item.value.items, srcItem.value.items);
|
if (srcItem.value && srcItem.value.comment) {
|
||||||
|
if (item.value) {
|
||||||
|
item.value.comment = srcItem.value.comment;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.value && srcItem.value) {
|
||||||
|
if (typeof item.value === "object" && typeof srcItem.value === "object") {
|
||||||
|
item.value.comment = srcItem.value.comment;
|
||||||
|
item.value.commentBefore = srcItem.value.commentBefore;
|
||||||
|
|
||||||
|
if (item.value.items && srcItem.value.items) {
|
||||||
|
copyYAMLCommentsItems(item.value.items, srcItem.value.items);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -273,10 +301,9 @@ function copyYAMLCommentsItems(items : any, srcItems : any) {
|
|||||||
* - "127.0.0.1:5000-5010:5000-5010"
|
* - "127.0.0.1:5000-5010:5000-5010"
|
||||||
* - "6060:6060/udp"
|
* - "6060:6060/udp"
|
||||||
* @param input
|
* @param input
|
||||||
* @param defaultHostname
|
* @param hostname
|
||||||
*/
|
*/
|
||||||
export function parseDockerPort(input : string, defaultHostname : string = "localhost") {
|
export function parseDockerPort(input : string, hostname : string) {
|
||||||
let hostname = defaultHostname;
|
|
||||||
let port;
|
let port;
|
||||||
let display;
|
let display;
|
||||||
|
|
||||||
@ -340,3 +367,53 @@ 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,3 @@
|
|||||||
version: "3.8"
|
|
||||||
services:
|
services:
|
||||||
dockge:
|
dockge:
|
||||||
image: louislam/dockge:1
|
image: louislam/dockge:1
|
||||||
|
@ -1,7 +1,4 @@
|
|||||||
# Due to the bug of #145, Node.js's version cannot be changed, unless upstream is fixed.
|
FROM node:22-bookworm-slim
|
||||||
FROM node:18.17.1-bookworm-slim
|
|
||||||
ENV PNPM_HOME="/pnpm"
|
|
||||||
ENV PATH="$PNPM_HOME:$PATH"
|
|
||||||
RUN apt update && apt install --yes --no-install-recommends \
|
RUN apt update && apt install --yes --no-install-recommends \
|
||||||
curl \
|
curl \
|
||||||
ca-certificates \
|
ca-certificates \
|
||||||
@ -20,5 +17,4 @@ RUN apt update && apt install --yes --no-install-recommends \
|
|||||||
docker-ce-cli \
|
docker-ce-cli \
|
||||||
docker-compose-plugin \
|
docker-compose-plugin \
|
||||||
&& rm -rf /var/lib/apt/lists/* \
|
&& rm -rf /var/lib/apt/lists/* \
|
||||||
&& npm install pnpm -g \
|
&& npm install -g tsx
|
||||||
&& pnpm install -g tsx
|
|
||||||
|
@ -9,8 +9,8 @@ FROM louislam/dockge:build-healthcheck AS build_healthcheck
|
|||||||
FROM louislam/dockge:base AS build
|
FROM louislam/dockge:base AS build
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY --chown=node:node ./package.json ./package.json
|
COPY --chown=node:node ./package.json ./package.json
|
||||||
COPY --chown=node:node ./pnpm-lock.yaml ./pnpm-lock.yaml
|
COPY --chown=node:node ./package-lock.json ./package-lock.json
|
||||||
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --prod --frozen-lockfile
|
RUN npm ci
|
||||||
|
|
||||||
############################################
|
############################################
|
||||||
# ⭐ Main Image
|
# ⭐ Main Image
|
||||||
@ -22,6 +22,13 @@ COPY --from=build /app/node_modules /app/node_modules
|
|||||||
COPY --chown=node:node . .
|
COPY --chown=node:node . .
|
||||||
RUN mkdir ./data
|
RUN mkdir ./data
|
||||||
|
|
||||||
|
|
||||||
|
# It is just for safe, as by default, it is disabled in the latest Node.js now.
|
||||||
|
# Read more:
|
||||||
|
# - https://github.com/sagemathinc/cocalc/issues/6963
|
||||||
|
# - https://github.com/microsoft/node-pty/issues/630#issuecomment-1987212447
|
||||||
|
ENV UV_USE_IO_URING=0
|
||||||
|
|
||||||
VOLUME /app/data
|
VOLUME /app/data
|
||||||
EXPOSE 5001
|
EXPOSE 5001
|
||||||
HEALTHCHECK --interval=60s --timeout=30s --start-period=60s --retries=5 CMD extra/healthcheck
|
HEALTHCHECK --interval=60s --timeout=30s --start-period=60s --retries=5 CMD extra/healthcheck
|
||||||
@ -32,4 +39,4 @@ CMD ["tsx", "./backend/index.ts"]
|
|||||||
# Mark as Nightly
|
# Mark as Nightly
|
||||||
############################################
|
############################################
|
||||||
FROM release AS nightly
|
FROM release AS nightly
|
||||||
RUN pnpm run mark-as-nightly
|
RUN npm run mark-as-nightly
|
||||||
|
@ -1,19 +1,37 @@
|
|||||||
// Generate on GitHub
|
// Generate on GitHub
|
||||||
const input = `
|
const input = `
|
||||||
* Add Korean translation by @Alanimdeo in https://github.com/louislam/dockge/pull/86
|
* Fixed envsubst issue by @louislam in https://github.com/louislam/dockge/pull/301
|
||||||
|
* Fix: Only adding folders to stack with a compose file. by @Ozy-Viking in https://github.com/louislam/dockge/pull/299
|
||||||
|
* Terminal text cols adjusts to terminal container. by @Ozy-Viking in https://github.com/louislam/dockge/pull/285
|
||||||
|
* Update Docker Dompose plugin to 2.23.3 by @louislam in https://github.com/louislam/dockge/pull/303
|
||||||
|
* Translations update from Kuma Weblate by @UptimeKumaBot in https://github.com/louislam/dockge/pull/302
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const template = `
|
const template = `
|
||||||
|
|
||||||
|
> [!WARNING]
|
||||||
|
>
|
||||||
|
|
||||||
### 🆕 New Features
|
### 🆕 New Features
|
||||||
|
-
|
||||||
|
|
||||||
### Improvements
|
### ⬆️ Improvements
|
||||||
|
-
|
||||||
|
|
||||||
### 🐞 Bug Fixes
|
### 🐛 Bug Fixes
|
||||||
|
-
|
||||||
|
|
||||||
### 🦎 Translation Contributions
|
### 🦎 Translation Contributions
|
||||||
|
-
|
||||||
|
|
||||||
|
### ⬆️ Security Fixes
|
||||||
|
-
|
||||||
|
|
||||||
### Others
|
### Others
|
||||||
- Other small changes, code refactoring and comment/doc updates in this repo:
|
- Other small changes, code refactoring and comment/doc updates in this repo:
|
||||||
|
-
|
||||||
|
|
||||||
|
Please let me know if your username is missing, if your pull request has been merged in this version, or your commit has been included in one of the pull requests.
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const lines = input.split("\n").filter((line) => line.trim() !== "");
|
const lines = input.split("\n").filter((line) => line.trim() !== "");
|
||||||
@ -37,6 +55,12 @@ 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);
|
||||||
|
@ -4,6 +4,8 @@ import readline from "readline";
|
|||||||
import { User } from "../backend/models/user";
|
import { User } from "../backend/models/user";
|
||||||
import { DockgeServer } from "../backend/dockge-server";
|
import { DockgeServer } from "../backend/dockge-server";
|
||||||
import { log } from "../backend/log";
|
import { log } from "../backend/log";
|
||||||
|
import { io } from "socket.io-client";
|
||||||
|
import { BaseRes } from "../common/util-common";
|
||||||
|
|
||||||
console.log("== Dockge Reset Password Tool ==");
|
console.log("== Dockge Reset Password Tool ==");
|
||||||
|
|
||||||
@ -12,11 +14,10 @@ const rl = readline.createInterface({
|
|||||||
output: process.stdout
|
output: process.stdout
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const server = new DockgeServer();
|
||||||
|
|
||||||
export const main = async () => {
|
export const main = async () => {
|
||||||
const server = new DockgeServer();
|
|
||||||
|
|
||||||
// Check if
|
// Check if
|
||||||
|
|
||||||
console.log("Connecting the database");
|
console.log("Connecting the database");
|
||||||
try {
|
try {
|
||||||
await Database.init(server);
|
await Database.init(server);
|
||||||
@ -47,12 +48,16 @@ export const main = async () => {
|
|||||||
// Reset all sessions by reset jwt secret
|
// Reset all sessions by reset jwt secret
|
||||||
await server.initJWTSecret();
|
await server.initJWTSecret();
|
||||||
|
|
||||||
|
console.log("Password reset successfully.");
|
||||||
|
|
||||||
|
// Disconnect all other socket clients of the user
|
||||||
|
await disconnectAllSocketClients(user.username, password);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
} else {
|
} else {
|
||||||
console.log("Passwords do not match, please try again.");
|
console.log("Passwords do not match, please try again.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
console.log("Password reset successfully.");
|
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e instanceof Error) {
|
if (e instanceof Error) {
|
||||||
@ -79,6 +84,46 @@ function question(question : string) : Promise<string> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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, {
|
||||||
|
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) {
|
if (!process.env.TEST_BACKEND) {
|
||||||
main();
|
main();
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
version: "3.8"
|
|
||||||
services:
|
services:
|
||||||
mariadb:
|
mariadb:
|
||||||
image: mariadb:latest
|
image: mariadb:latest
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
version: '3.8'
|
|
||||||
services:
|
services:
|
||||||
nginx-proxy-manager:
|
nginx-proxy-manager:
|
||||||
image: 'jc21/nginx-proxy-manager:latest'
|
image: 'jc21/nginx-proxy-manager:latest'
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
version: '3.8'
|
|
||||||
services:
|
services:
|
||||||
uptime-kuma:
|
uptime-kuma:
|
||||||
image: louislam/uptime-kuma:1
|
image: louislam/uptime-kuma:1
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
<button class="btn btn-normal btn-sm mt-3" @click="addField">{{ $t("addListItem", [ displayName ]) }}</button>
|
<button class="btn btn-normal btn-sm mt-3" @click="addField">{{ $t("addListItem", [ displayName ]) }}</button>
|
||||||
</div>
|
</div>
|
||||||
<div v-else>
|
<div v-else>
|
||||||
Long syntax is not supported here. Please use the YAML editor.
|
{{ $t("LongSyntaxNotSupported") }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
<ul v-if="isArrayInited" class="list-group">
|
<ul v-if="isArrayInited" class="list-group">
|
||||||
<li v-for="(value, index) in array" :key="index" class="list-group-item">
|
<li v-for="(value, index) in array" :key="index" class="list-group-item">
|
||||||
<select v-model="array[index]" class="no-bg domain-input">
|
<select v-model="array[index]" class="no-bg domain-input">
|
||||||
<option value="">Select a network...</option>
|
<option value="">{{ $t(`Select a network...`) }}</option>
|
||||||
<option v-for="option in options" :key="option" :value="option">{{ option }}</option>
|
<option v-for="option in options" :key="option" :value="option">{{ option }}</option>
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
|
@ -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 service.ports" :key="port" :href="parsePort(port).url" target="_blank">
|
<a v-for="port in envsubstService.ports" :key="port" :href="parsePort(port).url" target="_blank">
|
||||||
<span class="badge me-1 bg-secondary">{{ parsePort(port).display }}</span>
|
<span class="badge me-1 bg-secondary">{{ parsePort(port).display }}</span>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
@ -116,7 +116,7 @@
|
|||||||
</label>
|
</label>
|
||||||
|
|
||||||
<div v-if="networkList.length === 0 && service.networks && service.networks.length > 0" class="text-warning mb-3">
|
<div v-if="networkList.length === 0 && service.networks && service.networks.length > 0" class="text-warning mb-3">
|
||||||
No networks available. You need to add internal networks or enable external networks in the right side first.
|
{{ $t("NoNetworksAvailable") }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ArraySelect name="networks" :display-name="$t('network')" placeholder="Network Name" :options="networkList" />
|
<ArraySelect name="networks" :display-name="$t('network')" placeholder="Network Name" :options="networkList" />
|
||||||
@ -127,7 +127,7 @@
|
|||||||
<label class="form-label">
|
<label class="form-label">
|
||||||
{{ $t("dependsOn") }}
|
{{ $t("dependsOn") }}
|
||||||
</label>
|
</label>
|
||||||
<ArrayInput name="depends_on" :display-name="$t('dependsOn')" placeholder="Container Name" />
|
<ArrayInput name="depends_on" :display-name="$t('dependsOn')" :placeholder="$t(`containerName`)" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</transition>
|
</transition>
|
||||||
@ -137,7 +137,7 @@
|
|||||||
<script>
|
<script>
|
||||||
import { defineComponent } from "vue";
|
import { defineComponent } from "vue";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
|
||||||
import { parseDockerPort } from "../../../backend/util-common";
|
import { parseDockerPort } from "../../../common/util-common";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: {
|
components: {
|
||||||
@ -189,14 +189,34 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
|
|
||||||
terminalRouteLink() {
|
terminalRouteLink() {
|
||||||
return {
|
if (this.endpoint) {
|
||||||
name: "containerTerminal",
|
return {
|
||||||
params: {
|
name: "containerTerminalEndpoint",
|
||||||
stackName: this.stackName,
|
params: {
|
||||||
serviceName: this.name,
|
endpoint: this.endpoint,
|
||||||
type: "bash",
|
stackName: this.stackName,
|
||||||
},
|
serviceName: this.name,
|
||||||
};
|
type: "bash",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
name: "containerTerminal",
|
||||||
|
params: {
|
||||||
|
stackName: this.stackName,
|
||||||
|
serviceName: this.name,
|
||||||
|
type: "bash",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
endpoint() {
|
||||||
|
return this.$parent.$parent.endpoint;
|
||||||
|
},
|
||||||
|
|
||||||
|
stack() {
|
||||||
|
return this.$parent.$parent.stack;
|
||||||
},
|
},
|
||||||
|
|
||||||
stackName() {
|
stackName() {
|
||||||
@ -213,16 +233,29 @@ 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.service.image) {
|
if (this.envsubstService.image) {
|
||||||
return this.service.image.split(":")[0];
|
return this.envsubstService.image.split(":")[0];
|
||||||
} else {
|
} else {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
imageTag() {
|
imageTag() {
|
||||||
if (this.service.image) {
|
if (this.envsubstService.image) {
|
||||||
let tag = this.service.image.split(":")[1];
|
let tag = this.envsubstService.image.split(":")[1];
|
||||||
|
|
||||||
if (tag) {
|
if (tag) {
|
||||||
return tag;
|
return tag;
|
||||||
@ -241,8 +274,12 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
parsePort(port) {
|
parsePort(port) {
|
||||||
let hostname = this.$root.info.primaryHostname || location.hostname;
|
if (this.stack.endpoint) {
|
||||||
return parseDockerPort(port, hostname);
|
return parseDockerPort(port, this.stack.primaryHostname);
|
||||||
|
} else {
|
||||||
|
let hostname = this.$root.info.primaryHostname || location.hostname;
|
||||||
|
return parseDockerPort(port, hostname);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
remove() {
|
remove() {
|
||||||
delete this.jsonObject.services[this.name];
|
delete this.jsonObject.services[this.name];
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
<h5>{{ $t("Internal Networks") }}</h5>
|
<h5>{{ $t("Internal Networks") }}</h5>
|
||||||
<ul class="list-group">
|
<ul class="list-group">
|
||||||
<li v-for="(networkRow, index) in networkList" :key="index" class="list-group-item">
|
<li v-for="(networkRow, index) in networkList" :key="index" class="list-group-item">
|
||||||
<input v-model="networkRow.key" type="text" class="no-bg domain-input" placeholder="Network name..." />
|
<input v-model="networkRow.key" type="text" class="no-bg domain-input" :placeholder="$t(`Network name...`)" />
|
||||||
<font-awesome-icon icon="times" class="action remove ms-2 me-3 text-danger" @click="remove(index)" />
|
<font-awesome-icon icon="times" class="action remove ms-2 me-3 text-danger" @click="remove(index)" />
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
@ -65,6 +65,10 @@ export default {
|
|||||||
editorFocus() {
|
editorFocus() {
|
||||||
return this.$parent.$parent.editorFocus;
|
return this.$parent.$parent.editorFocus;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
endpoint() {
|
||||||
|
return this.$parent.$parent.endpoint;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
"jsonConfig.networks": {
|
"jsonConfig.networks": {
|
||||||
@ -134,7 +138,7 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
loadExternalNetworkList() {
|
loadExternalNetworkList() {
|
||||||
this.$root.getSocket().emit("getDockerNetworkList", (res) => {
|
this.$root.emitAgent(this.endpoint, "getDockerNetworkList", (res) => {
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
this.externalNetworkList = res.dockerNetworkList.filter((n) => {
|
this.externalNetworkList = res.dockerNetworkList.filter((n) => {
|
||||||
// Filter out this stack networks
|
// Filter out this stack networks
|
||||||
|
@ -43,7 +43,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div ref="stackList" class="stack-list" :class="{ scrollbar: scrollbar }" :style="stackListStyle">
|
<div ref="stackList" class="stack-list" :class="{ scrollbar: scrollbar }" :style="stackListStyle">
|
||||||
<div v-if="Object.keys($root.stackList).length === 0" class="text-center mt-3">
|
<div v-if="Object.keys(sortedStackList).length === 0" class="text-center mt-3">
|
||||||
<router-link to="/compose">{{ $t("addFirstStackMsg") }}</router-link>
|
<router-link to="/compose">{{ $t("addFirstStackMsg") }}</router-link>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -67,7 +67,7 @@
|
|||||||
<script>
|
<script>
|
||||||
import Confirm from "../components/Confirm.vue";
|
import Confirm from "../components/Confirm.vue";
|
||||||
import StackListItem from "../components/StackListItem.vue";
|
import StackListItem from "../components/StackListItem.vue";
|
||||||
import { CREATED_FILE, CREATED_STACK, EXITED, RUNNING, UNKNOWN } from "../../../backend/util-common";
|
import { CREATED_FILE, CREATED_STACK, EXITED, RUNNING, UNKNOWN } from "../../../common/util-common";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
@ -120,7 +120,7 @@ export default {
|
|||||||
* @returns {Array} The sorted list of stacks.
|
* @returns {Array} The sorted list of stacks.
|
||||||
*/
|
*/
|
||||||
sortedStackList() {
|
sortedStackList() {
|
||||||
let result = Object.values(this.$root.stackList);
|
let result = Object.values(this.$root.completeStackList);
|
||||||
|
|
||||||
result = result.filter(stack => {
|
result = result.filter(stack => {
|
||||||
// filter by search text
|
// filter by search text
|
||||||
@ -160,6 +160,7 @@ export default {
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// sort by status
|
||||||
if (m1.status !== m2.status) {
|
if (m1.status !== m2.status) {
|
||||||
if (m2.status === RUNNING) {
|
if (m2.status === RUNNING) {
|
||||||
return 1;
|
return 1;
|
||||||
|
@ -1,12 +1,14 @@
|
|||||||
<template>
|
<template>
|
||||||
<router-link :to="`/compose/${stack.name}`" :class="{ 'dim' : !stack.isManagedByDockge }" class="item">
|
<router-link :to="url" :class="{ 'dim' : !stack.isManagedByDockge }" class="item">
|
||||||
<Uptime :stack="stack" :fixed-width="true" class="me-2" />
|
<Uptime :stack="stack" :fixed-width="true" class="me-2" />
|
||||||
<span class="title">{{ stackName }}</span>
|
<div class="title">
|
||||||
|
<span>{{ stackName }}</span>
|
||||||
|
<div v-if="$root.agentCount > 1" class="endpoint">{{ endpointDisplay }}</div>
|
||||||
|
</div>
|
||||||
</router-link>
|
</router-link>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
import Uptime from "./Uptime.vue";
|
import Uptime from "./Uptime.vue";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@ -51,6 +53,16 @@ export default {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
endpointDisplay() {
|
||||||
|
return this.$root.endpointDisplayFunction(this.stack.endpoint);
|
||||||
|
},
|
||||||
|
url() {
|
||||||
|
if (this.stack.endpoint) {
|
||||||
|
return `/compose/${this.stack.name}/${this.stack.endpoint}`;
|
||||||
|
} else {
|
||||||
|
return `/compose/${this.stack.name}`;
|
||||||
|
}
|
||||||
|
},
|
||||||
depthMargin() {
|
depthMargin() {
|
||||||
return {
|
return {
|
||||||
marginLeft: `${31 * this.depth}px`,
|
marginLeft: `${31 * this.depth}px`,
|
||||||
@ -117,16 +129,31 @@ export default {
|
|||||||
padding-right: 2px !important;
|
padding-right: 2px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
// .stack-item {
|
.item {
|
||||||
// width: 100%;
|
text-decoration: none;
|
||||||
// }
|
|
||||||
|
|
||||||
.tags {
|
|
||||||
margin-top: 4px;
|
|
||||||
padding-left: 67px;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
align-items: center;
|
||||||
gap: 0;
|
min-height: 52px;
|
||||||
|
border-radius: 10px;
|
||||||
|
transition: all ease-in-out 0.15s;
|
||||||
|
width: 100%;
|
||||||
|
padding: 5px 8px;
|
||||||
|
&.disabled {
|
||||||
|
opacity: 0.3;
|
||||||
|
}
|
||||||
|
&:hover {
|
||||||
|
background-color: $highlight-white;
|
||||||
|
}
|
||||||
|
&.active {
|
||||||
|
background-color: #cdf8f4;
|
||||||
|
}
|
||||||
|
.title {
|
||||||
|
margin-top: -4px;
|
||||||
|
}
|
||||||
|
.endpoint {
|
||||||
|
font-size: 12px;
|
||||||
|
color: $dark-font-color3;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.collapsed {
|
.collapsed {
|
||||||
|
@ -5,9 +5,9 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { Terminal } from "xterm";
|
import { Terminal } from "@xterm/xterm";
|
||||||
import { WebLinksAddon } from "xterm-addon-web-links";
|
import { FitAddon } from "@xterm/addon-fit";
|
||||||
import { TERMINAL_COLS, TERMINAL_ROWS } from "../../../backend/util-common";
|
import { TERMINAL_COLS, TERMINAL_ROWS } from "../../../common/util-common";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
/**
|
/**
|
||||||
@ -23,6 +23,11 @@ export default {
|
|||||||
require: true,
|
require: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
endpoint: {
|
||||||
|
type: String,
|
||||||
|
require: true,
|
||||||
|
},
|
||||||
|
|
||||||
// Require if mode is interactive
|
// Require if mode is interactive
|
||||||
stackName: {
|
stackName: {
|
||||||
type: String,
|
type: String,
|
||||||
@ -109,37 +114,39 @@ export default {
|
|||||||
|
|
||||||
// Create a new Terminal
|
// Create a new Terminal
|
||||||
if (this.mode === "mainTerminal") {
|
if (this.mode === "mainTerminal") {
|
||||||
this.$root.getSocket().emit("mainTerminal", this.name, (res) => {
|
this.$root.emitAgent(this.endpoint, "mainTerminal", this.name, (res) => {
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
this.$root.toastRes(res);
|
this.$root.toastRes(res);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else if (this.mode === "interactive") {
|
} else if (this.mode === "interactive") {
|
||||||
console.debug("Create Interactive terminal:", this.name);
|
console.debug("Create Interactive terminal:", this.name);
|
||||||
this.$root.getSocket().emit("interactiveTerminal", this.stackName, this.serviceName, this.shell, (res) => {
|
this.$root.emitAgent(this.endpoint, "interactiveTerminal", this.stackName, this.serviceName, this.shell, (res) => {
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
this.$root.toastRes(res);
|
this.$root.toastRes(res);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
// Fit the terminal width to the div container size after terminal is created.
|
||||||
|
this.updateTerminalSize();
|
||||||
},
|
},
|
||||||
|
|
||||||
unmounted() {
|
unmounted() {
|
||||||
|
window.removeEventListener("resize", this.onResizeEvent); // Remove the resize event listener from the window object.
|
||||||
this.$root.unbindTerminal(this.name);
|
this.$root.unbindTerminal(this.name);
|
||||||
this.terminal.dispose();
|
this.terminal.dispose();
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
bind(name) {
|
bind(endpoint, name) {
|
||||||
// Workaround: normally this.name should be set, but it is not sometimes, so we use the parameter, but eventually this.name and name must be the same name
|
// Workaround: normally this.name should be set, but it is not sometimes, so we use the parameter, but eventually this.name and name must be the same name
|
||||||
if (name) {
|
if (name) {
|
||||||
this.$root.unbindTerminal(name);
|
this.$root.unbindTerminal(name);
|
||||||
this.$root.bindTerminal(name, this.terminal);
|
this.$root.bindTerminal(endpoint, name, this.terminal);
|
||||||
console.debug("Terminal bound via parameter: " + name);
|
console.debug("Terminal bound via parameter: " + name);
|
||||||
} else if (this.name) {
|
} else if (this.name) {
|
||||||
this.$root.unbindTerminal(this.name);
|
this.$root.unbindTerminal(this.name);
|
||||||
this.$root.bindTerminal(this.name, this.terminal);
|
this.$root.bindTerminal(this.endpoint, this.name, this.terminal);
|
||||||
console.debug("Terminal bound: " + this.name);
|
console.debug("Terminal bound: " + this.name);
|
||||||
} else {
|
} else {
|
||||||
console.debug("Terminal name not set");
|
console.debug("Terminal name not set");
|
||||||
@ -170,7 +177,7 @@ export default {
|
|||||||
// Remove the input from the terminal
|
// Remove the input from the terminal
|
||||||
this.removeInput();
|
this.removeInput();
|
||||||
|
|
||||||
this.$root.getSocket().emit("terminalInput", this.name, buffer + e.key, (err) => {
|
this.$root.emitAgent(this.endpoint, "terminalInput", this.name, buffer + e.key, (err) => {
|
||||||
this.$root.toastError(err.msg);
|
this.$root.toastError(err.msg);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -189,12 +196,11 @@ export default {
|
|||||||
// TODO
|
// TODO
|
||||||
} else if (e.key === "\u0003") { // Ctrl + C
|
} else if (e.key === "\u0003") { // Ctrl + C
|
||||||
console.debug("Ctrl + C");
|
console.debug("Ctrl + C");
|
||||||
this.$root.getSocket().emit("terminalInput", this.name, e.key);
|
this.$root.emitAgent(this.endpoint, "terminalInput", this.name, e.key);
|
||||||
this.removeInput();
|
this.removeInput();
|
||||||
} else {
|
} else {
|
||||||
this.cursorPosition++;
|
this.cursorPosition++;
|
||||||
this.terminalInputBuffer += e.key;
|
this.terminalInputBuffer += e.key;
|
||||||
console.log(this.terminalInputBuffer);
|
|
||||||
this.terminal.write(e.key);
|
this.terminal.write(e.key);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -202,12 +208,36 @@ export default {
|
|||||||
|
|
||||||
interactiveTerminalConfig() {
|
interactiveTerminalConfig() {
|
||||||
this.terminal.onKey(e => {
|
this.terminal.onKey(e => {
|
||||||
this.$root.getSocket().emit("terminalInput", this.name, e.key, (res) => {
|
this.$root.emitAgent(this.endpoint, "terminalInput", this.name, e.key, (res) => {
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
this.$root.toastRes(res);
|
this.$root.toastRes(res);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the terminal size to fit the container size.
|
||||||
|
*
|
||||||
|
* If the terminalFitAddOn is not created, creates it, loads it and then fits the terminal to the appropriate size.
|
||||||
|
* It then addes an event listener to the window object to listen for resize events and calls the fit method of the terminalFitAddOn.
|
||||||
|
*/
|
||||||
|
updateTerminalSize() {
|
||||||
|
if (!Object.hasOwn(this, "terminalFitAddOn")) {
|
||||||
|
this.terminalFitAddOn = new FitAddon();
|
||||||
|
this.terminal.loadAddon(this.terminalFitAddOn);
|
||||||
|
window.addEventListener("resize", this.onResizeEvent);
|
||||||
|
}
|
||||||
|
this.terminalFitAddOn.fit();
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Handles the resize event of the terminal component.
|
||||||
|
*/
|
||||||
|
onResizeEvent() {
|
||||||
|
this.terminalFitAddOn.fit();
|
||||||
|
let rows = this.terminal.rows;
|
||||||
|
let cols = this.terminal.cols;
|
||||||
|
this.$root.emitAgent(this.endpoint, "terminalResize", this.name, rows, cols);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -216,13 +246,12 @@ export default {
|
|||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
.main-terminal {
|
.main-terminal {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
overflow-x: scroll;
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.terminal {
|
.terminal {
|
||||||
padding: 10px 15px;
|
background-color: black !important;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { statusColor, statusNameShort } from "../../../backend/util-common";
|
import { statusColor, statusNameShort } from "../../../common/util-common";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
|
@ -47,7 +47,7 @@
|
|||||||
<input
|
<input
|
||||||
v-model="settings.primaryHostname"
|
v-model="settings.primaryHostname"
|
||||||
class="form-control"
|
class="form-control"
|
||||||
placeholder="(Unset: Follow current hostname)"
|
:placeholder="$t(`CurrentHostname`)"
|
||||||
/>
|
/>
|
||||||
<button class="btn btn-outline-primary" type="button" @click="autoGetPrimaryHostname">
|
<button class="btn btn-outline-primary" type="button" @click="autoGetPrimaryHostname">
|
||||||
{{ $t("autoGet") }}
|
{{ $t("autoGet") }}
|
||||||
|
@ -27,6 +27,11 @@ const languageList = {
|
|||||||
"ja": "日本語",
|
"ja": "日本語",
|
||||||
"nl": "Nederlands",
|
"nl": "Nederlands",
|
||||||
"ro": "Română",
|
"ro": "Română",
|
||||||
|
"id": "Bahasa Indonesia (Indonesian)",
|
||||||
|
"vi": "Tiếng Việt",
|
||||||
|
"hu": "Magyar",
|
||||||
|
"ca": "Català",
|
||||||
|
"ga": "Gaeilge",
|
||||||
};
|
};
|
||||||
|
|
||||||
let messages = {
|
let messages = {
|
||||||
@ -39,7 +44,7 @@ for (let lang in languageList) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const rtlLangs = [ "fa", "ar-SY", "ur" ];
|
const rtlLangs = [ "fa", "ar-SY", "ur", "ar" ];
|
||||||
|
|
||||||
export const currentLocale = () => localStorage.locale
|
export const currentLocale = () => localStorage.locale
|
||||||
|| languageList[navigator.language] && navigator.language
|
|| languageList[navigator.language] && navigator.language
|
||||||
|
@ -35,7 +35,7 @@
|
|||||||
"restartPolicyAlways": "دائماً",
|
"restartPolicyAlways": "دائماً",
|
||||||
"restartPolicyOnFailure": "عند الفشل",
|
"restartPolicyOnFailure": "عند الفشل",
|
||||||
"restartPolicyNo": "لا",
|
"restartPolicyNo": "لا",
|
||||||
"environmentVariable": "متغير البيئة | متغيرات البيئة",
|
"environmentVariable": "متغير | متغيرات",
|
||||||
"restartPolicy": "سياسة إعادة التشغيل",
|
"restartPolicy": "سياسة إعادة التشغيل",
|
||||||
"containerName": "اسم الحاوية",
|
"containerName": "اسم الحاوية",
|
||||||
"port": "منفذ | منافذ",
|
"port": "منفذ | منافذ",
|
||||||
@ -98,5 +98,16 @@
|
|||||||
"url": "رابط | روابط",
|
"url": "رابط | روابط",
|
||||||
"extra": "إضافات",
|
"extra": "إضافات",
|
||||||
"reverseProxyMsg1": "هل تستدخم خادم عكسي؟",
|
"reverseProxyMsg1": "هل تستدخم خادم عكسي؟",
|
||||||
"connecting...": "جاري الاتصال بخادم المقبس…"
|
"connecting...": "جاري الاتصال بخادم المقبس…",
|
||||||
|
"newUpdate": "تحديث جديد",
|
||||||
|
"currentEndpoint": "السياق: الوكيل الحالي",
|
||||||
|
"dockgeURL": "رابط Dockge (مثلا http://127.0.0.1:5001)",
|
||||||
|
"agentOnline": "متصل",
|
||||||
|
"agentOffline": "غير متصل",
|
||||||
|
"connecting": "جاري الإتصال",
|
||||||
|
"connect": "ارتبط",
|
||||||
|
"dockgeAgent": "سيرفر Dockge",
|
||||||
|
"removeAgent": "حذف الوكيل",
|
||||||
|
"removeAgentMsg": "هل انت متأكد من حذف هذا الوكيل؟",
|
||||||
|
"LongSyntaxNotSupported": "كتابة النصوص المدعومة غير المدعومة هنا. الرجاء استخدام محرر YAML."
|
||||||
}
|
}
|
||||||
|
116
frontend/src/lang/be.json
Normal file
116
frontend/src/lang/be.json
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
{
|
||||||
|
"active": "акт.",
|
||||||
|
"LongSyntaxNotSupported": "Доўгі сінтаксіс тут не падтрымліваецца. Выкарыстоўвайце рэдактар YAML.",
|
||||||
|
"removeAgentMsg": "Вы ўпэўнены, што хочаце выдаліць гэтага агента?",
|
||||||
|
"languageName": "Беларуская",
|
||||||
|
"Create your admin account": "Стварыце ўліковы запіс адміністратара",
|
||||||
|
"authIncorrectCreds": "Няправільны лагін ці пароль.",
|
||||||
|
"PasswordsDoNotMatch": "Паролі не супадаюць.",
|
||||||
|
"Repeat Password": "Паўтарыце пароль",
|
||||||
|
"Create": "Стварыць",
|
||||||
|
"signedInDisp": "Аўтарызаваны як {0}",
|
||||||
|
"signedInDispDisabled": "Аўтарызацыя выключана.",
|
||||||
|
"home": "Галоўная",
|
||||||
|
"console": "Кансоль",
|
||||||
|
"registry": "Рэестр (Registry)",
|
||||||
|
"compose": "Compose",
|
||||||
|
"addFirstStackMsg": "Стварыце свой першы стэк!",
|
||||||
|
"stackName": "Назва стэка",
|
||||||
|
"deployStack": "Разгарнуць",
|
||||||
|
"deleteStack": "Выдаліць",
|
||||||
|
"stopStack": "Спыніць",
|
||||||
|
"restartStack": "Перазапусціць",
|
||||||
|
"updateStack": "Абнавіць",
|
||||||
|
"startStack": "Запусціць",
|
||||||
|
"downStack": "Спыніць і дэактываваць",
|
||||||
|
"editStack": "Рэдагаваць",
|
||||||
|
"discardStack": "Скасаваць",
|
||||||
|
"saveStackDraft": "Захаваць",
|
||||||
|
"notAvailableShort": "Н/Д",
|
||||||
|
"deleteStackMsg": "Вы ўпэўнены, што хочаце выдаліць гэты стэк?",
|
||||||
|
"stackNotManagedByDockgeMsg": "Дадзены стэк не кіруецца Dockge.",
|
||||||
|
"primaryHostname": "Імя хоста",
|
||||||
|
"general": "Агульныя",
|
||||||
|
"container": "Кантэйнер | Кантэйнеры",
|
||||||
|
"scanFolder": "Сканаваць папку стэкаў",
|
||||||
|
"dockerImage": "Вобраз",
|
||||||
|
"restartPolicyUnlessStopped": "Пакуль не будзе спынены",
|
||||||
|
"restartPolicyAlways": "Заўсёды",
|
||||||
|
"restartPolicyOnFailure": "Пры падзенні",
|
||||||
|
"restartPolicyNo": "Ніколі",
|
||||||
|
"environmentVariable": "Зменная асяроддзя | Зменныя асяроддзя",
|
||||||
|
"restartPolicy": "Палітыка рэстарту",
|
||||||
|
"containerName": "Імя кантэйнера",
|
||||||
|
"port": "Порт | Порты",
|
||||||
|
"volume": "Сховішча | Сховішчы",
|
||||||
|
"network": "Сетка | Сеткі",
|
||||||
|
"dependsOn": "Залежнасць кантэйнера | Залежнасці кантэйнера",
|
||||||
|
"addListItem": "Дадаць {0}",
|
||||||
|
"deleteContainer": "Выдаліць",
|
||||||
|
"addContainer": "Дадаць кантэйнер",
|
||||||
|
"addNetwork": "Дадаць сетку",
|
||||||
|
"disableauth.message1": "Вы ўпэўнены, што хочаце <strong>адключыць аўтэнтыфікацыю</strong>?",
|
||||||
|
"Show update if available": "Паказаць абнаўленне, калі яно даступна",
|
||||||
|
"Also check beta release": "Атрымліваць бэта-версіі",
|
||||||
|
"disableauth.message2": "Гэта прызначана для сцэнарыяў, <strong>калі вы збіраецеся выкарыстоўваць староннюю аўтэнтыфікацыю</strong> перад Dockge, напрыклад, Cloudflare Access, Authelia або іншыя механізмы аўтэнтыфікацыі.",
|
||||||
|
"passwordNotMatchMsg": "Паўторны пароль не супадае.",
|
||||||
|
"autoGet": "Аўта",
|
||||||
|
"add": "Дадаць",
|
||||||
|
"Edit": "Змяніць",
|
||||||
|
"applyToYAML": "Ужыць да YAML",
|
||||||
|
"createExternalNetwork": "Стварыць",
|
||||||
|
"addInternalNetwork": "Дадаць",
|
||||||
|
"Save": "Захаваць",
|
||||||
|
"Language": "Мова",
|
||||||
|
"Current User": "Бягучы карыстальнік",
|
||||||
|
"Change Password": "Змяніць пароль",
|
||||||
|
"Current Password": "Бягучы пароль",
|
||||||
|
"New Password": "Новы пароль",
|
||||||
|
"Repeat New Password": "Паўтарыце новы пароль",
|
||||||
|
"Update Password": "Абнавіць пароль",
|
||||||
|
"Advanced": "Пашыраныя",
|
||||||
|
"Please use this option carefully!": "Выкарыстоўвайце гэтую опцыю асцярожна!",
|
||||||
|
"Enable Auth": "Уключыць аўтэнтыфікацыю",
|
||||||
|
"Disable Auth": "Адключыць аўтэнтыфікацыю",
|
||||||
|
"I understand, please disable": "Я разумею, адключыце",
|
||||||
|
"Leave": "Пакінуць",
|
||||||
|
"Frontend Version": "Версія знешняга інтэрфейсу",
|
||||||
|
"Check Update On GitHub": "Праверыць абнаўленні на GitHub",
|
||||||
|
"Remember me": "Запомніць мяне",
|
||||||
|
"Login": "Лагін",
|
||||||
|
"Username": "Імя карыстальніка",
|
||||||
|
"Password": "Пароль",
|
||||||
|
"Settings": "Налады",
|
||||||
|
"Logout": "Выйсці",
|
||||||
|
"Lowercase only": "Толькі ніжні рэгістр",
|
||||||
|
"Convert to Compose": "Пераўтварыць у Compose",
|
||||||
|
"Docker Run": "Docker Run",
|
||||||
|
"exited": "спын.",
|
||||||
|
"inactive": "неакт.",
|
||||||
|
"Appearance": "Знешні выгляд",
|
||||||
|
"Security": "Бяспека",
|
||||||
|
"About": "Аб праграме",
|
||||||
|
"Allowed commands:": "Дазволеныя каманды:",
|
||||||
|
"Internal Networks": "Унутраныя сеткі",
|
||||||
|
"External Networks": "Знешнія сеткі",
|
||||||
|
"No External Networks": "Няма знешніх сетак",
|
||||||
|
"reverseProxyMsg1": "Выкарыстоўваеце зваротны проксі?",
|
||||||
|
"reverseProxyMsg2": "Праверце, як наладзіць яго для WebSocket",
|
||||||
|
"Cannot connect to the socket server.": "Не ўдалося падключыцца да сокет-сервера.",
|
||||||
|
"reconnecting...": "Перападключэнне…",
|
||||||
|
"connecting...": "Падключэнне да сокет-сервера…",
|
||||||
|
"url": "URL-адрас | URL-адрасы",
|
||||||
|
"extra": "Дадаткова",
|
||||||
|
"newUpdate": "Даступна абнаўленне",
|
||||||
|
"dockgeAgent": "Агент Dockge | Агенты Dockge",
|
||||||
|
"currentEndpoint": "Бягучы",
|
||||||
|
"dockgeURL": "URL-адрас Dockge (напрыклад: http://127.0.0.1:5001)",
|
||||||
|
"agentOnline": "У сетцы",
|
||||||
|
"agentOffline": "Не ў сетцы",
|
||||||
|
"connecting": "Падключэнне",
|
||||||
|
"connect": "Падключыць",
|
||||||
|
"addAgent": "Дадаць Агента",
|
||||||
|
"agentAddedSuccessfully": "Агент паспяхова дададзены.",
|
||||||
|
"agentRemovedSuccessfully": "Агент паспяхова выдалены.",
|
||||||
|
"removeAgent": "Выдаліць агента"
|
||||||
|
}
|
@ -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,5 +90,43 @@
|
|||||||
"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...": "Свързване със сокет сървъра…",
|
||||||
|
"newUpdate": "Нова актуализация",
|
||||||
|
"currentEndpoint": "Текущ",
|
||||||
|
"dockgeURL": "Dockge URL адрес (напр. http://127.0.0.1:5001)",
|
||||||
|
"agentOnline": "Онлайн",
|
||||||
|
"agentOffline": "Офлайн",
|
||||||
|
"connect": "Свържи",
|
||||||
|
"addAgent": "Добави агент",
|
||||||
|
"agentAddedSuccessfully": "Агентът е добавен успешно.",
|
||||||
|
"removeAgent": "Премахни агент",
|
||||||
|
"removeAgentMsg": "Сигурни ли сте, че желаете да премахнете този агент?",
|
||||||
|
"dockgeAgent": "Dockge агент | Dockge агенти",
|
||||||
|
"connecting": "Свързване",
|
||||||
|
"agentRemovedSuccessfully": "Агентът е премахнат успешно.",
|
||||||
|
"LongSyntaxNotSupported": "Дългият синтаксис не се поддържа тук. Моля, използвайте YAML редактора.",
|
||||||
|
"Started": "Стартиран",
|
||||||
|
"Updated": "Актуализиран",
|
||||||
|
"Deleted": "Изтрит",
|
||||||
|
"Deployed": "Внедрен",
|
||||||
|
"Stopped": "Спрян",
|
||||||
|
"Restarted": "Рестартиран",
|
||||||
|
"Switch to sh": "Превключи на \"sh\"",
|
||||||
|
"terminal": "Терминал",
|
||||||
|
"New Container Name...": "Ново име на контейнер...",
|
||||||
|
"Network name...": "Име на мрежата...",
|
||||||
|
"Select a network...": "Изберете мрежа...",
|
||||||
|
"Lost connection to the socket server. Reconnecting...": "Изгубена връзка със сокет сървъра. Повторно свързване...",
|
||||||
|
"Saved": "Запазено",
|
||||||
|
"Downed": "Свален",
|
||||||
|
"CurrentHostname": "(Не е зададено: Следвай текущото име на хост)",
|
||||||
|
"NoNetworksAvailable": "Няма налични мрежи. Първо трябва да добавите вътрешни мрежи или да активирате външни мрежи в дясната страна."
|
||||||
}
|
}
|
||||||
|
116
frontend/src/lang/ca.json
Normal file
116
frontend/src/lang/ca.json
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
{
|
||||||
|
"Create your admin account": "Crea el teu compte d'administrador",
|
||||||
|
"Repeat Password": "Repeteix la contrasenya",
|
||||||
|
"Create": "Crea",
|
||||||
|
"signedInDisp": "S'ha iniciat sessió com a {0}",
|
||||||
|
"home": "Inici",
|
||||||
|
"console": "Consola",
|
||||||
|
"registry": "Registre",
|
||||||
|
"compose": "Compondre",
|
||||||
|
"addFirstStackMsg": "Compondre la teva primera pila!",
|
||||||
|
"stackName": "Nom de la pila",
|
||||||
|
"deployStack": "Desplegar",
|
||||||
|
"deleteStack": "Eliminar",
|
||||||
|
"stopStack": "Aturar",
|
||||||
|
"restartStack": "Reiniciar",
|
||||||
|
"updateStack": "Actualitzar",
|
||||||
|
"startStack": "Inicia",
|
||||||
|
"downStack": "Atura i inactiva",
|
||||||
|
"languageName": "Català",
|
||||||
|
"authIncorrectCreds": "Usuari o contrasenya incorrecte.",
|
||||||
|
"PasswordsDoNotMatch": "Les contrasenyes no coincideixen.",
|
||||||
|
"signedInDispDisabled": "Autenticació deshabilitada.",
|
||||||
|
"discardStack": "Descartar",
|
||||||
|
"saveStackDraft": "Guardar",
|
||||||
|
"notAvailableShort": "N/D",
|
||||||
|
"primaryHostname": "Nom del host primari",
|
||||||
|
"general": "General",
|
||||||
|
"container": "Contenidor | Contenidors",
|
||||||
|
"scanFolder": "Escaneja la carpeta de piles",
|
||||||
|
"dockerImage": "Imatge",
|
||||||
|
"restartPolicyAlways": "Sempre",
|
||||||
|
"restartPolicyOnFailure": "En cas de fallada",
|
||||||
|
"restartPolicyNo": "No",
|
||||||
|
"environmentVariable": "Variable d'entorn | Variables d'entorn",
|
||||||
|
"restartPolicy": "Política de reinici",
|
||||||
|
"containerName": "Nom del contenidor",
|
||||||
|
"port": "Port | Ports",
|
||||||
|
"volume": "Volum | Volums",
|
||||||
|
"network": "Xarxa | Xarxes",
|
||||||
|
"addListItem": "Afegir {0}",
|
||||||
|
"deleteContainer": "Eliminar",
|
||||||
|
"addContainer": "Afegir contenidor",
|
||||||
|
"addNetwork": "Afegir xarxa",
|
||||||
|
"passwordNotMatchMsg": "La contrasenya repetida no coincideix.",
|
||||||
|
"autoGet": "Obtenir automàticament",
|
||||||
|
"add": "Afegir",
|
||||||
|
"Edit": "Editar",
|
||||||
|
"applyToYAML": "Aplicar a YAML",
|
||||||
|
"createExternalNetwork": "Crear",
|
||||||
|
"addInternalNetwork": "Afegir",
|
||||||
|
"Save": "Guardar",
|
||||||
|
"Language": "Idioma",
|
||||||
|
"Current User": "Usuari actual",
|
||||||
|
"Change Password": "Canviar la contrasenya",
|
||||||
|
"Current Password": "Contrasenya actual",
|
||||||
|
"New Password": "Nova contrasenya",
|
||||||
|
"stackNotManagedByDockgeMsg": "Aquesta pila no està gestionada per Dockge.",
|
||||||
|
"Update Password": "Actualitzar contrasenya",
|
||||||
|
"Advanced": "Avançat",
|
||||||
|
"Disable Auth": "Deshabilitar autenticació",
|
||||||
|
"Leave": "Sortir",
|
||||||
|
"Frontend Version": "Versió del frontend",
|
||||||
|
"Check Update On GitHub": "Comprova les actualitzacions a GitHub",
|
||||||
|
"Show update if available": "Mostra si hi ha disponible una nova actualització",
|
||||||
|
"Also check beta release": "Comprovar també la versió beta",
|
||||||
|
"Remember me": "Recorda'm",
|
||||||
|
"Login": "Inici de sesió",
|
||||||
|
"Username": "Usuari",
|
||||||
|
"Settings": "Configuració",
|
||||||
|
"Logout": "Tanca sessió",
|
||||||
|
"Lowercase only": "Només minúscules",
|
||||||
|
"Convert to Compose": "Convertir a Compose",
|
||||||
|
"Docker Run": "Executar Docker",
|
||||||
|
"active": "actiu",
|
||||||
|
"exited": "finalitzat",
|
||||||
|
"inactive": "inactiu",
|
||||||
|
"Appearance": "Aparença",
|
||||||
|
"Security": "Seguretat",
|
||||||
|
"About": "Sobre",
|
||||||
|
"Allowed commands:": "Comandes permeses:",
|
||||||
|
"Internal Networks": "Xarxes internes",
|
||||||
|
"External Networks": "Xarxes externes",
|
||||||
|
"No External Networks": "No hi ha xarxes externes",
|
||||||
|
"reverseProxyMsg1": "Estàs fent servir un proxy invers?",
|
||||||
|
"reverseProxyMsg2": "Comproveu com configurar-lo per a WebSocket",
|
||||||
|
"Cannot connect to the socket server.": "No es pot connectar al servidor del socket.",
|
||||||
|
"reconnecting...": "S'està tornant a connectar…",
|
||||||
|
"connecting...": "S'està connectant al servidor del socket…",
|
||||||
|
"url": "URL | URLs",
|
||||||
|
"extra": "Extra",
|
||||||
|
"newUpdate": "Nova actualització",
|
||||||
|
"dockgeAgent": "Agent Dockge | Agents Dockge",
|
||||||
|
"currentEndpoint": "Actual",
|
||||||
|
"dockgeURL": "URL de Dockge (ex. http://127.0.0.1:5001)",
|
||||||
|
"agentOnline": "En línia",
|
||||||
|
"agentOffline": "Fora de línia",
|
||||||
|
"connecting": "Connectant",
|
||||||
|
"connect": "Connectar",
|
||||||
|
"addAgent": "Afegir agent",
|
||||||
|
"agentAddedSuccessfully": "Agent afegit correctament.",
|
||||||
|
"agentRemovedSuccessfully": "Agent eliminat correctament.",
|
||||||
|
"removeAgent": "Eliminar agent",
|
||||||
|
"removeAgentMsg": "Esteu segur que voleu eliminar aquest agent?",
|
||||||
|
"editStack": "Editar",
|
||||||
|
"deleteStackMsg": "Estàs segur que vols eliminar aquesta pila?",
|
||||||
|
"restartPolicyUnlessStopped": "A menys que s'aturi",
|
||||||
|
"dependsOn": "Dependència del contenidor | Dependències del contenidor",
|
||||||
|
"disableauth.message1": "Esteu segur que voleu <strong>desactivar l'autenticació</strong>?",
|
||||||
|
"disableauth.message2": "Està dissenyat per a escenaris <strong>on voleu implementar l'autenticació de tercers</strong> per davant de Dockge, com ara Cloudflare Access, Authelia o altres mecanismes d'autenticació.",
|
||||||
|
"Repeat New Password": "Repetiu la nova contrasenya",
|
||||||
|
"Please use this option carefully!": "Si us plau, utilitzeu aquesta opció amb cura!",
|
||||||
|
"Enable Auth": "Habilitar autenticació",
|
||||||
|
"I understand, please disable": "Ho entenc, si us plau deshabilita",
|
||||||
|
"Password": "Contrasenya",
|
||||||
|
"LongSyntaxNotSupported": "La sintaxi llarga no està suportada aquí. Si us plau, fes servir l'editor YAML."
|
||||||
|
}
|
@ -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": "Opakujte heslo",
|
"Repeat Password": "Napište Heslo Znovu",
|
||||||
"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": "Compose",
|
"compose": "Komponovat",
|
||||||
"addFirstStackMsg": "Vytvořte svůj první stack!",
|
"addFirstStackMsg": "Vytvořte svůj první zásobník!",
|
||||||
"stackName": "Název stacku",
|
"stackName": "Název Zásobníku",
|
||||||
"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 a vypnout",
|
"downStack": "Zastavit & Zneaktivnit",
|
||||||
"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 stack?",
|
"deleteStackMsg": "Opravdu chcete smazat tento zásobník?",
|
||||||
"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 stacky",
|
"scanFolder": "Prohledat složku se zásobníky",
|
||||||
"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,5 +91,39 @@
|
|||||||
"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 .",
|
||||||
|
"Lost connection to the socket server. Reconnecting...": "Ztraceno spojení se serverem. Obnovuji spojení...",
|
||||||
|
"newUpdate": "Nová aktualizace",
|
||||||
|
"dockgeAgent": "Dockge Agent | Dockge Agenti",
|
||||||
|
"agentOnline": "Online",
|
||||||
|
"connecting": "Připojování",
|
||||||
|
"agentOffline": "Offline",
|
||||||
|
"dockgeURL": "Dockge URL (např. http://127.0.0.1:5001)",
|
||||||
|
"LongSyntaxNotSupported": "Dlouhá syntaxe zde není podporována. Použijte, prosím, YAML editor.",
|
||||||
|
"connecting...": "Připojování k socket serveru…",
|
||||||
|
"connect": "Připojit",
|
||||||
|
"addAgent": "Přidat Agenta",
|
||||||
|
"agentAddedSuccessfully": "Agent byl úspěšně přidán.",
|
||||||
|
"agentRemovedSuccessfully": "Agend byl úspěšně odebrán.",
|
||||||
|
"removeAgent": "Odebrat Agenta",
|
||||||
|
"removeAgentMsg": "Opravdu chcete tohoto agenta odebrat?",
|
||||||
|
"Saved": "Uloženo",
|
||||||
|
"Deployed": "Nasazeno",
|
||||||
|
"Deleted": "Odstraněno",
|
||||||
|
"Updated": "Aktualizovat",
|
||||||
|
"Started": "Spuštěno",
|
||||||
|
"Stopped": "Zastaveno",
|
||||||
|
"Restarted": "Restartováno",
|
||||||
|
"Switch to sh": "Přepnout na sh shell",
|
||||||
|
"terminal": "Terminál",
|
||||||
|
"New Container Name...": "Název nového kontejneru...",
|
||||||
|
"Network name...": "Název sítě...",
|
||||||
|
"Select a network...": "Vyberte síť...",
|
||||||
|
"NoNetworksAvailable": "Žádná síť není dostupná. Musíte přidat interní síť nebo povolit externí sítě v pravé části."
|
||||||
}
|
}
|
||||||
|
@ -1,32 +1,32 @@
|
|||||||
{
|
{
|
||||||
"languageName": "Dansk",
|
"languageName": "Dansk",
|
||||||
"authIncorrectCreds": "Forkert brugernavn eller adgangskode.",
|
"authIncorrectCreds": "Forkert brugernavn eller adgangskode.",
|
||||||
"PasswordsDoNotMatch": "Adgangskode stemmer ikke overens.",
|
"PasswordsDoNotMatch": "Adgangskoder stemmer ikke overens.",
|
||||||
"Repeat Password": "Gentag adgangskode",
|
"Repeat Password": "Gentag adgangskode",
|
||||||
"Create": "Opret",
|
"Create": "Opret",
|
||||||
"signedInDisp": "Logget ind som {0}",
|
"signedInDisp": "Logget ind som {0}",
|
||||||
"signedInDispDisabled": "Auth Deaktiveret.",
|
"signedInDispDisabled": "Godkendelse deaktiveret.",
|
||||||
"home": "Hjem",
|
"home": "Hjem",
|
||||||
"console": "Konsol",
|
"console": "Konsol",
|
||||||
"registry": "Registry",
|
"registry": "Register",
|
||||||
"compose": "Compose",
|
"compose": "Komponer",
|
||||||
"stackName": "Stack-navn",
|
"stackName": "Stak-navn",
|
||||||
"deployStack": "Udrulle",
|
"deployStack": "Udrulle",
|
||||||
"deleteStack": "Slet",
|
"deleteStack": "Slet",
|
||||||
"stopStack": "Stop",
|
"stopStack": "Stop",
|
||||||
"restartStack": "Genstart",
|
"restartStack": "Genstart",
|
||||||
"updateStack": "Opdatere",
|
"updateStack": "Opdater",
|
||||||
"startStack": "Start",
|
"startStack": "Start",
|
||||||
"downStack": "Stop & Sluk",
|
"downStack": "Stop & Deaktiver",
|
||||||
"editStack": "Editere",
|
"editStack": "Rediger",
|
||||||
"discardStack": "Annuller",
|
"discardStack": "Kassér",
|
||||||
"saveStackDraft": "Gem",
|
"saveStackDraft": "Gem",
|
||||||
"notAvailableShort": "Ugyldig",
|
"notAvailableShort": "N/A",
|
||||||
"stackNotManagedByDockgeMsg": "Denne stack administreres ikke af Dockge.",
|
"stackNotManagedByDockgeMsg": "Denne stak administreres ikke af Dockge.",
|
||||||
"primaryHostname": "Primært værtsnavn",
|
"primaryHostname": "Primært værtsnavn",
|
||||||
"general": "Generelt",
|
"general": "Generelt",
|
||||||
"container": "Container | Containere",
|
"container": "Container | Containere",
|
||||||
"scanFolder": "Scan Stack-mappe",
|
"scanFolder": "Scan Stak-mappe",
|
||||||
"dockerImage": "Billede",
|
"dockerImage": "Billede",
|
||||||
"restartPolicyUnlessStopped": "Medmindre stoppet",
|
"restartPolicyUnlessStopped": "Medmindre stoppet",
|
||||||
"restartPolicyAlways": "Altid",
|
"restartPolicyAlways": "Altid",
|
||||||
@ -37,7 +37,7 @@
|
|||||||
"port": "Port | Porte",
|
"port": "Port | Porte",
|
||||||
"volume": "Volumen | Voluminer",
|
"volume": "Volumen | Voluminer",
|
||||||
"network": "Netværk | Netværker",
|
"network": "Netværk | Netværker",
|
||||||
"dependsOn": "Container Dependency | Container Dependencies",
|
"dependsOn": "Containerafhængighed | Containerafhængigheder",
|
||||||
"addListItem": "Tilføj {0}",
|
"addListItem": "Tilføj {0}",
|
||||||
"deleteContainer": "Slet",
|
"deleteContainer": "Slet",
|
||||||
"addNetwork": "Tilføj Netværk",
|
"addNetwork": "Tilføj Netværk",
|
||||||
@ -46,7 +46,7 @@
|
|||||||
"add": "Tilføj",
|
"add": "Tilføj",
|
||||||
"Edit": "Redigere",
|
"Edit": "Redigere",
|
||||||
"applyToYAML": "Anvend til YAML",
|
"applyToYAML": "Anvend til YAML",
|
||||||
"createExternalNetwork": "Skabe",
|
"createExternalNetwork": "Skab",
|
||||||
"addInternalNetwork": "Tilføj",
|
"addInternalNetwork": "Tilføj",
|
||||||
"Save": "Gem",
|
"Save": "Gem",
|
||||||
"Language": "Sprog",
|
"Language": "Sprog",
|
||||||
@ -58,11 +58,11 @@
|
|||||||
"Update Password": "Opdater adgangskode",
|
"Update Password": "Opdater adgangskode",
|
||||||
"Advanced": "Avanceret",
|
"Advanced": "Avanceret",
|
||||||
"Please use this option carefully!": "Brug venligst denne indstilling forsigtigt!",
|
"Please use this option carefully!": "Brug venligst denne indstilling forsigtigt!",
|
||||||
"Enable Auth": "Aktiver Auth",
|
"Enable Auth": "Aktiver godkendelse",
|
||||||
"Disable Auth": "Deaktiver Auth",
|
"Disable Auth": "Deaktiver godkendelse",
|
||||||
"I understand, please disable": "Jeg forstår, venligst deaktiver",
|
"I understand, please disable": "Jeg forstår, venligst deaktiver",
|
||||||
"Leave": "Forlad",
|
"Leave": "Forlad",
|
||||||
"Frontend Version": "Frontend Version",
|
"Frontend Version": "Version",
|
||||||
"Check Update On GitHub": "Tjek opdatering på GitHub",
|
"Check Update On GitHub": "Tjek opdatering på GitHub",
|
||||||
"Also check beta release": "Tjek også betaversionen",
|
"Also check beta release": "Tjek også betaversionen",
|
||||||
"Remember me": "Husk mig",
|
"Remember me": "Husk mig",
|
||||||
@ -72,7 +72,7 @@
|
|||||||
"Settings": "Indstillinger",
|
"Settings": "Indstillinger",
|
||||||
"Logout": "Log ud",
|
"Logout": "Log ud",
|
||||||
"Convert to Compose": "Konverter til Compose",
|
"Convert to Compose": "Konverter til Compose",
|
||||||
"active": "aktive",
|
"active": "aktiv",
|
||||||
"exited": "forladt",
|
"exited": "forladt",
|
||||||
"inactive": "inaktive",
|
"inactive": "inaktive",
|
||||||
"Appearance": "Udseende",
|
"Appearance": "Udseende",
|
||||||
@ -91,12 +91,36 @@
|
|||||||
"url": "URL | URL'er",
|
"url": "URL | URL'er",
|
||||||
"extra": "Ekstra",
|
"extra": "Ekstra",
|
||||||
"Create your admin account": "Opret din administratorkonto",
|
"Create your admin account": "Opret din administratorkonto",
|
||||||
"addFirstStackMsg": "Compose din første stack!",
|
"addFirstStackMsg": "Komponer din første stak!",
|
||||||
"deleteStackMsg": "Er du sikker på, at du vil slette denne stack?",
|
"deleteStackMsg": "Er du sikker på, at du vil slette denne stak?",
|
||||||
"environmentVariable": "Miljøvariabel | miljøvariabler",
|
"environmentVariable": "Miljøvariabel | miljøvariabler",
|
||||||
"addContainer": "Tilføj Container",
|
"addContainer": "Tilføj Container",
|
||||||
"disableauth.message1": "Er du sikker på, at du vil <strong>deaktivere godkendelse</strong>?",
|
"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.",
|
"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",
|
"Show update if available": "Vis opdatering, hvis tilgængelig",
|
||||||
"Lowercase only": "Kun små bogstaver"
|
"Lowercase only": "Kun små bogstaver",
|
||||||
|
"newUpdate": "Ny Opdatering",
|
||||||
|
"dockgeAgent": "Dockge Agent | Dockge Agenter",
|
||||||
|
"currentEndpoint": "Nuværende",
|
||||||
|
"dockgeURL": "Dockge URL (f.eks. http://127.0.0.1:5001)",
|
||||||
|
"agentOnline": "Online",
|
||||||
|
"agentOffline": "Offline",
|
||||||
|
"connecting": "Tilslutter",
|
||||||
|
"connect": "Tilslut",
|
||||||
|
"addAgent": "Tilføj agent",
|
||||||
|
"agentAddedSuccessfully": "Agent succesfuld tilføjet.",
|
||||||
|
"agentRemovedSuccessfully": "Agent succesfuld fjernet.",
|
||||||
|
"removeAgent": "Fjern agent",
|
||||||
|
"removeAgentMsg": "Er du sikker på at du vil fjerne denne agent?",
|
||||||
|
"LongSyntaxNotSupported": "Langt syntaks er ikke understøttet her. Forsøg venligst med YAML-editoren.",
|
||||||
|
"Saved": "Gemt",
|
||||||
|
"Deleted": "Slettet",
|
||||||
|
"Updated": "Opdateret",
|
||||||
|
"Started": "Startet",
|
||||||
|
"Stopped": "Stoppet",
|
||||||
|
"Restarted": "Genstartet",
|
||||||
|
"terminal": "Terminal",
|
||||||
|
"Network name...": "Netværksnavn...",
|
||||||
|
"Select a network...": "Vælg et netværk...",
|
||||||
|
"Deployed": "Udrullet"
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
"home": "Startseite",
|
"home": "Startseite",
|
||||||
"console": "Konsole",
|
"console": "Konsole",
|
||||||
"registry": "Container Registry",
|
"registry": "Container Registry",
|
||||||
"compose": "",
|
"compose": "Compose",
|
||||||
"addFirstStackMsg": "Stelle deinen ersten Stack zusammen!",
|
"addFirstStackMsg": "Stelle deinen ersten Stack zusammen!",
|
||||||
"stackName": "Stack-Name",
|
"stackName": "Stack-Name",
|
||||||
"deployStack": "Deployen",
|
"deployStack": "Deployen",
|
||||||
@ -72,15 +72,15 @@
|
|||||||
"Check Update On GitHub": "Update auf GitHub überprüfen",
|
"Check Update On GitHub": "Update auf GitHub überprüfen",
|
||||||
"Show update if available": "Update anzeigen, wenn verfügbar",
|
"Show update if available": "Update anzeigen, wenn verfügbar",
|
||||||
"Also check beta release": "Auch Beta-Version überprüfen",
|
"Also check beta release": "Auch Beta-Version überprüfen",
|
||||||
"Remember me": "Anmeldung beibehalten",
|
"Remember me": "Angemeldet bleiben",
|
||||||
"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 ausführen",
|
"Docker Run": "Docker Run",
|
||||||
"active": "aktiv",
|
"active": "aktiv",
|
||||||
"exited": "beendet",
|
"exited": "beendet",
|
||||||
"inactive": "inaktiv",
|
"inactive": "inaktiv",
|
||||||
@ -94,9 +94,39 @@
|
|||||||
"Cannot connect to the socket server.": "Keine Verbindung zum Socket Server.",
|
"Cannot connect to the socket server.": "Keine Verbindung zum Socket Server.",
|
||||||
"reverseProxyMsg1": "Wird ein Reverse Proxy genutzt?",
|
"reverseProxyMsg1": "Wird ein Reverse Proxy genutzt?",
|
||||||
"reconnecting...": "Erneuter Verbindungsaufbau…",
|
"reconnecting...": "Erneuter Verbindungsaufbau…",
|
||||||
"downStack": "Stoppen & Aus",
|
"downStack": "Stoppen & Deaktivieren",
|
||||||
"extra": "Extra",
|
"extra": "Extra",
|
||||||
"url": "URL / URLs",
|
"url": "URL / URLs",
|
||||||
"reverseProxyMsg2": "Lerne wie dieser für WebSockets zu konfigurieren ist.",
|
"reverseProxyMsg2": "Lerne wie dieser für WebSockets zu konfigurieren ist.",
|
||||||
"connecting...": "Verbindungsaufbau zum Socket Server…"
|
"connecting...": "Verbindungsaufbau zum Socket Server…",
|
||||||
|
"newUpdate": "Neues Update",
|
||||||
|
"dockgeAgent": "Dockge Agent | Dockge Agenten",
|
||||||
|
"currentEndpoint": "Aktuell",
|
||||||
|
"dockgeURL": "Dockge URL (z. B. http://127.0.0.1:5001)",
|
||||||
|
"agentOnline": "Online",
|
||||||
|
"agentOffline": "Offline",
|
||||||
|
"connecting": "Verbinden",
|
||||||
|
"connect": "Verbinden",
|
||||||
|
"addAgent": "Agent Hinzufügen",
|
||||||
|
"agentAddedSuccessfully": "Agent erfolgreich hinzugefügt.",
|
||||||
|
"agentRemovedSuccessfully": "Agent erfolgreich entfernt.",
|
||||||
|
"removeAgent": "Agent Entfernen",
|
||||||
|
"removeAgentMsg": "Bist Du sicher, dass Du diesen Agent entfernen möchtest?",
|
||||||
|
"LongSyntaxNotSupported": "Lange Syntax wird nicht unterstützt. Bitte verwende den YAML-Editor.",
|
||||||
|
"Lost connection to the socket server. Reconnecting...": "Verbindung zu Socket Server verloren. Verbinden...",
|
||||||
|
"Saved": "Gespeichert",
|
||||||
|
"Deleted": "Gelöscht",
|
||||||
|
"Started": "Gestartet",
|
||||||
|
"Stopped": "Gestoppt",
|
||||||
|
"Restarted": "Neugestartet",
|
||||||
|
"New Container Name...": "Neuer Container Name...",
|
||||||
|
"Network name...": "Netzwerkname...",
|
||||||
|
"Select a network...": "Netzwerk auswählen...",
|
||||||
|
"Updated": "Aktualisiert",
|
||||||
|
"Deployed": "Deployed",
|
||||||
|
"Switch to sh": "Zu sh wechseln",
|
||||||
|
"terminal": "Terminal",
|
||||||
|
"CurrentHostname": "(nicht gesetzt: verwende aktuellen Hostname)",
|
||||||
|
"Downed": "Heruntergefahren",
|
||||||
|
"NoNetworksAvailable": "Keine Netzwerke verfügbar. Du musst zunächst interne Netzwerke hinzufügen oder externe Netzwerke auf der rechten Seite aktivieren."
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,7 @@
|
|||||||
"restartStack": "Restart",
|
"restartStack": "Restart",
|
||||||
"updateStack": "Update",
|
"updateStack": "Update",
|
||||||
"startStack": "Start",
|
"startStack": "Start",
|
||||||
"downStack": "Stop & Down",
|
"downStack": "Stop & Inactive",
|
||||||
"editStack": "Edit",
|
"editStack": "Edit",
|
||||||
"discardStack": "Discard",
|
"discardStack": "Discard",
|
||||||
"saveStackDraft": "Save",
|
"saveStackDraft": "Save",
|
||||||
@ -95,8 +95,38 @@
|
|||||||
"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.",
|
||||||
|
"Lost connection to the socket server. Reconnecting...": "Lost connection to the socket server. Reconnecting...",
|
||||||
"reconnecting...": "Reconnecting…",
|
"reconnecting...": "Reconnecting…",
|
||||||
"connecting...": "Connecting to the socket server…",
|
"connecting...": "Connecting to the socket server…",
|
||||||
"url": "URL | URLs",
|
"url": "URL | URLs",
|
||||||
"extra": "Extra"
|
"extra": "Extra",
|
||||||
|
"newUpdate": "New Update",
|
||||||
|
"dockgeAgent": "Dockge Agent | Dockge Agents",
|
||||||
|
"currentEndpoint": "Current",
|
||||||
|
"dockgeURL": "Dockge URL (e.g. http://127.0.0.1:5001)",
|
||||||
|
"agentOnline": "Online",
|
||||||
|
"agentOffline": "Offline",
|
||||||
|
"connecting": "Connecting",
|
||||||
|
"connect": "Connect",
|
||||||
|
"addAgent": "Add Agent",
|
||||||
|
"agentAddedSuccessfully": "Agent added successfully.",
|
||||||
|
"agentRemovedSuccessfully": "Agent removed successfully.",
|
||||||
|
"removeAgent": "Remove Agent",
|
||||||
|
"removeAgentMsg": "Are you sure you want to remove this agent?",
|
||||||
|
"LongSyntaxNotSupported": "Long syntax is not supported here. Please use the YAML editor.",
|
||||||
|
"Saved": "Saved",
|
||||||
|
"Deployed": "Deployed",
|
||||||
|
"Deleted": "Deleted",
|
||||||
|
"Updated": "Updated",
|
||||||
|
"Started": "Started",
|
||||||
|
"Stopped": "Stopped",
|
||||||
|
"Restarted": "Restarted",
|
||||||
|
"Downed": "Downed",
|
||||||
|
"Switch to sh": "Switch to sh",
|
||||||
|
"terminal": "Terminal",
|
||||||
|
"CurrentHostname": "(Unset: Follow current hostname)",
|
||||||
|
"New Container Name...": "New Container Name...",
|
||||||
|
"Network name...": "Network name...",
|
||||||
|
"Select a network...": "Select a network...",
|
||||||
|
"NoNetworksAvailable": "No networks available. You need to add internal networks or enable external networks in the right side first."
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"languageName": "Español",
|
"languageName": "Inglés",
|
||||||
"Create your admin account": "Crea tu cuenta de administrador",
|
"Create your admin account": "Crea tu cuenta de administrador",
|
||||||
"authIncorrectCreds": "Nombre de usuario o contraseña incorrectos.",
|
"authIncorrectCreds": "Nombre de usuario o contraseña incorrectos.",
|
||||||
"PasswordsDoNotMatch": "Las contraseñas no coinciden.",
|
"PasswordsDoNotMatch": "Las contraseñas no coinciden.",
|
||||||
@ -12,7 +12,7 @@
|
|||||||
"registry": "Registro",
|
"registry": "Registro",
|
||||||
"compose": "Componer",
|
"compose": "Componer",
|
||||||
"addFirstStackMsg": "¡Compón tu primera pila!",
|
"addFirstStackMsg": "¡Compón tu primera pila!",
|
||||||
"stackName" : "Nombre de la Pila",
|
"stackName": "Nombre de la Pila",
|
||||||
"deployStack": "Desplegar",
|
"deployStack": "Desplegar",
|
||||||
"deleteStack": "Eliminar",
|
"deleteStack": "Eliminar",
|
||||||
"stopStack": "Detener",
|
"stopStack": "Detener",
|
||||||
@ -22,7 +22,7 @@
|
|||||||
"editStack": "Editar",
|
"editStack": "Editar",
|
||||||
"discardStack": "Descartar",
|
"discardStack": "Descartar",
|
||||||
"saveStackDraft": "Guardar",
|
"saveStackDraft": "Guardar",
|
||||||
"notAvailableShort" : "N/D",
|
"notAvailableShort": "N/D",
|
||||||
"deleteStackMsg": "¿Estás seguro de que quieres eliminar esta pila?",
|
"deleteStackMsg": "¿Estás seguro de que quieres eliminar esta pila?",
|
||||||
"stackNotManagedByDockgeMsg": "Esta pila no está gestionada por Dockge.",
|
"stackNotManagedByDockgeMsg": "Esta pila no está gestionada por Dockge.",
|
||||||
"primaryHostname": "Nombre de Host Primario",
|
"primaryHostname": "Nombre de Host Primario",
|
||||||
@ -90,5 +90,43 @@
|
|||||||
"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": "Sin Redes Externas"
|
"No External Networks": "Sin Redes Externas",
|
||||||
|
"reverseProxyMsg1": "¿Usando un proxy inverso?",
|
||||||
|
"reverseProxyMsg2": "Compruebe cómo configurarlo para WebSocket",
|
||||||
|
"newUpdate": "Nueva actualización",
|
||||||
|
"downStack": "Detener y desactivar",
|
||||||
|
"Cannot connect to the socket server.": "No se puede conectar al servidor del socket.",
|
||||||
|
"reconnecting...": "Reconectando…",
|
||||||
|
"connecting...": "Conectando al servidor del socket…",
|
||||||
|
"url": "Dirección URL | Direcciones URLs",
|
||||||
|
"extra": "Addicional",
|
||||||
|
"currentEndpoint": "Actual",
|
||||||
|
"dockgeURL": "URL de Dockge (ej. http://127.0.0.1:5001)",
|
||||||
|
"agentOnline": "En línea",
|
||||||
|
"agentOffline": "Desconectado",
|
||||||
|
"connect": "Conectar",
|
||||||
|
"addAgent": "Añadir Agente",
|
||||||
|
"agentAddedSuccessfully": "Agente añadido satisfactoriamente.",
|
||||||
|
"removeAgent": "Eliminar Agente",
|
||||||
|
"removeAgentMsg": "¿Estás seguro que deseas eliminar este agente?",
|
||||||
|
"dockgeAgent": "Agentes Dockge",
|
||||||
|
"connecting": "Conectando",
|
||||||
|
"agentRemovedSuccessfully": "Agente eliminado satisfactoriamente.",
|
||||||
|
"LongSyntaxNotSupported": "No hay soporte para la sintaxis larga. Por favor use el editor de YAML.",
|
||||||
|
"Lost connection to the socket server. Reconnecting...": "Se ha perdido la conexión con el servidor de socket. Reconectando...",
|
||||||
|
"Saved": "Guardado",
|
||||||
|
"Deployed": "Desplegado",
|
||||||
|
"Deleted": "Eliminado",
|
||||||
|
"Updated": "Actualizado",
|
||||||
|
"Started": "Arrancado",
|
||||||
|
"Stopped": "Parado",
|
||||||
|
"Restarted": "Reseteado",
|
||||||
|
"Downed": "Caído",
|
||||||
|
"Switch to sh": "Cambiar a sh",
|
||||||
|
"terminal": "Terminal",
|
||||||
|
"CurrentHostname": "(Vacío: Seguir hostname actual)",
|
||||||
|
"New Container Name...": "Nombre del nuevo Container...",
|
||||||
|
"Network name...": "Nombre de la red...",
|
||||||
|
"Select a network...": "Selecciona una red...",
|
||||||
|
"NoNetworksAvailable": "No hay redes disponibles. Primero debes agregar redes internas o habilitar redes externas en el lado derecho."
|
||||||
}
|
}
|
||||||
|
@ -95,8 +95,38 @@
|
|||||||
"connecting...": "Connexion au serveur socket…",
|
"connecting...": "Connexion au serveur socket…",
|
||||||
"url": "URL | URLs",
|
"url": "URL | URLs",
|
||||||
"extra": "Supplémentaire",
|
"extra": "Supplémentaire",
|
||||||
"downStack": "Arrêter et désactiver",
|
"downStack": "Arrêtez et rendre inactif",
|
||||||
"reverseProxyMsg1": "Utilisez vous un proxy inverse ?",
|
"reverseProxyMsg1": "Utilisez vous un proxy inverse ?",
|
||||||
"Cannot connect to the socket server.": "Impossible de se connecter au serveur socket.",
|
"Cannot connect to the socket server.": "Impossible de se connecter au serveur socket.",
|
||||||
"reconnecting...": "Reconnexion…"
|
"reconnecting...": "Reconnexion…",
|
||||||
|
"newUpdate": "Nouvelle mise à jour",
|
||||||
|
"dockgeURL": "URL de Dockge (e.g. http://127.0.0.1:5001)",
|
||||||
|
"agentOnline": "En ligne",
|
||||||
|
"agentOffline": "Hors ligne",
|
||||||
|
"connecting": "Connexion",
|
||||||
|
"addAgent": "Ajouter un agent",
|
||||||
|
"agentAddedSuccessfully": "Agent ajouté avec succès.",
|
||||||
|
"agentRemovedSuccessfully": "Agent supprimé avec succès.",
|
||||||
|
"removeAgent": "Supprimer l'agent",
|
||||||
|
"dockgeAgent": "Dockge Agent | Dockge Agents",
|
||||||
|
"currentEndpoint": "Actuel",
|
||||||
|
"connect": "Connecter",
|
||||||
|
"removeAgentMsg": "Êtes-vous sûr de vouloir supprimer cet agent ?",
|
||||||
|
"LongSyntaxNotSupported": "La syntaxe longue n'est pas prise en charge ici. Veuillez utiliser l'éditeur YAML.",
|
||||||
|
"Saved": "Enregistré",
|
||||||
|
"Deployed": "Déployé",
|
||||||
|
"Deleted": "Supprimé",
|
||||||
|
"Updated": "Mis à jour",
|
||||||
|
"Started": "démarrer",
|
||||||
|
"Stopped": "Arrêté",
|
||||||
|
"Restarted": "Redémarré",
|
||||||
|
"Switch to sh": "Passer à sh",
|
||||||
|
"terminal": "Terminal",
|
||||||
|
"New Container Name...": "Nouveau nom du conteneur...",
|
||||||
|
"Network name...": "Nom du réseau...",
|
||||||
|
"Select a network...": "Sélectionnez un réseau...",
|
||||||
|
"Downed": "Abattu",
|
||||||
|
"Lost connection to the socket server. Reconnecting...": "Connexion au serveur socket perdue. Reconnexion...",
|
||||||
|
"CurrentHostname": "(Non défini : suivre le nom d'hôte actuel)",
|
||||||
|
"NoNetworksAvailable": "Aucun réseau disponible. Vous devez d'abord ajouter des réseaux internes ou activer les réseaux externes sur le côté droit."
|
||||||
}
|
}
|
||||||
|
132
frontend/src/lang/ga.json
Normal file
132
frontend/src/lang/ga.json
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
{
|
||||||
|
"Create your admin account": "Cruthaigh do chuntas riaracháin",
|
||||||
|
"authIncorrectCreds": "Ainm úsáideora nó pasfhocal mícheart.",
|
||||||
|
"PasswordsDoNotMatch": "Níl na pasfhocail comhthráthacha.",
|
||||||
|
"Repeat Password": "Athscríobh an Pasfhocal",
|
||||||
|
"Create": "Cruthaigh",
|
||||||
|
"signedInDisp": "Sínithe isteach mar {0}",
|
||||||
|
"languageName": "Gaeilge",
|
||||||
|
"console": "Consól",
|
||||||
|
"registry": "Clárlann",
|
||||||
|
"compose": "Scríobh",
|
||||||
|
"stackName": "Ainm an Staca",
|
||||||
|
"deployStack": "Deighil",
|
||||||
|
"deleteStack": "Scrios",
|
||||||
|
"stopStack": "Stad",
|
||||||
|
"restartStack": "Atosaigh",
|
||||||
|
"updateStack": "Nuashonraigh",
|
||||||
|
"startStack": "Tosaigh",
|
||||||
|
"downStack": "Stad & Neamhghníomhach",
|
||||||
|
"editStack": "Cuir in Eagar",
|
||||||
|
"discardStack": "Caith amach",
|
||||||
|
"saveStackDraft": "Sábháil",
|
||||||
|
"deleteStackMsg": "An bhfuil tú cinnte go bhfuil tú ag iarraidh an staca seo a scriosadh?",
|
||||||
|
"primaryHostname": "Príomhainm óstáin",
|
||||||
|
"general": "Ginearálta",
|
||||||
|
"container": "Coimeádán | Coimeádáin",
|
||||||
|
"scanFolder": "Scanáil Fillteáin na dStacanna",
|
||||||
|
"dockerImage": "Íomha",
|
||||||
|
"restartPolicyUnlessStopped": "Mura Stadfar",
|
||||||
|
"restartPolicyAlways": "I gcónaí",
|
||||||
|
"restartPolicyOnFailure": "Ar theip",
|
||||||
|
"restartPolicyNo": "Níl",
|
||||||
|
"environmentVariable": "Athróg Timpeallacht | Athróga Timpeallacht",
|
||||||
|
"restartPolicy": "Polasaí Atosaigh",
|
||||||
|
"port": "Port | Portanna",
|
||||||
|
"volume": "Toirt | Toirteanna",
|
||||||
|
"network": "Líonra | Líonraí",
|
||||||
|
"dependsOn": "Spleáchas Coimeádán | Spleáchais Coimeádán",
|
||||||
|
"addListItem": "Cuir {0}",
|
||||||
|
"deleteContainer": "Scrios",
|
||||||
|
"addContainer": "Cuir Coimeádán leis",
|
||||||
|
"addNetwork": "Cuir Líonra leis",
|
||||||
|
"add": "Cuir",
|
||||||
|
"Edit": "Cuir in eagar",
|
||||||
|
"applyToYAML": "Déan iarratas ar YAML",
|
||||||
|
"createExternalNetwork": "Cruthaigh",
|
||||||
|
"disableauth.message1": "An bhfuil tú cinnte gur mhaith leat <strong>fíordheimhniú a dhíchumasú</strong>?",
|
||||||
|
"passwordNotMatchMsg": "Ní hionann an pasfhocal athfhillteach.",
|
||||||
|
"autoGet": "Faigh Uathoibríoch",
|
||||||
|
"addInternalNetwork": "Cuir",
|
||||||
|
"Save": "Sábháil",
|
||||||
|
"Language": "Teanga",
|
||||||
|
"Current User": "Úsáideoir Reatha",
|
||||||
|
"New Password": "Pasfhocal Nua",
|
||||||
|
"Current Password": "Pasfhocal Reatha",
|
||||||
|
"Change Password": "Athraigh do Phasfhocal",
|
||||||
|
"Repeat New Password": "Déan Pasfhocal Nua arís",
|
||||||
|
"Update Password": "Nuashonraigh Pasfhocal",
|
||||||
|
"Advanced": "Ardleibhéal",
|
||||||
|
"Please use this option carefully!": "Bain úsáid as an rogha seo go cúramach, le do thoil!",
|
||||||
|
"Enable Auth": "Cumasaigh Auth",
|
||||||
|
"Disable Auth": "Auth dhíchumasú",
|
||||||
|
"I understand, please disable": "Tuigim, le do thoil, múch",
|
||||||
|
"Leave": "Fág",
|
||||||
|
"Frontend Version": "Leagan Frontend",
|
||||||
|
"Check Update On GitHub": "Seiceáil an Nuashonrú ar GitHub",
|
||||||
|
"Show update if available": "Taispeáin an Nuashonrú más ar fáil",
|
||||||
|
"Also check beta release": "Seiceáil an scaoileadh beta freisin",
|
||||||
|
"Remember me": "Cuimhnigh orm",
|
||||||
|
"Login": "Logáil isteach",
|
||||||
|
"Username": "Ainm úsáideora",
|
||||||
|
"Password": "Pasfhocal",
|
||||||
|
"Logout": "Logáil Amach",
|
||||||
|
"Lowercase only": "Cás íochtair amháin",
|
||||||
|
"Convert to Compose": "Tiontaigh go Compóidh",
|
||||||
|
"Docker Run": "Docker Rith",
|
||||||
|
"exited": "scoir",
|
||||||
|
"inactive": "neamhghníomhach",
|
||||||
|
"Appearance": "Dealramh",
|
||||||
|
"Security": "Slándáil",
|
||||||
|
"About": "Maidir le",
|
||||||
|
"Allowed commands:": "Orduithe ceadaithe:",
|
||||||
|
"Internal Networks": "Líonraí Inmheánacha",
|
||||||
|
"External Networks": "Líonraí Seachtracha",
|
||||||
|
"No External Networks": "Gan Líonraí Seachtracha",
|
||||||
|
"reverseProxyMsg1": "Ag Úsáid Seachfhreastalaí Réabhlóideach?",
|
||||||
|
"reverseProxyMsg2": "Seiceáil conas é a shocraigh don WebSocket",
|
||||||
|
"Cannot connect to the socket server.": "Ní féidir ceangal a dhéanamh leis an freastalaí soicéad.",
|
||||||
|
"reconnecting...": "Ag athcheangal…",
|
||||||
|
"connecting...": "Ag nascadh leis an freastalaí soicéad…",
|
||||||
|
"url": "URL | URLanna",
|
||||||
|
"extra": "Breise",
|
||||||
|
"newUpdate": "Nuashonrú Nua",
|
||||||
|
"dockgeAgent": "Aighne Dockge | Aighnithe Dockge",
|
||||||
|
"currentEndpoint": "Reatha",
|
||||||
|
"dockgeURL": "Dockge URL (e.g. http://127.0.0.1:5001)",
|
||||||
|
"agentOnline": "Ar Líne",
|
||||||
|
"agentOffline": "As Líne",
|
||||||
|
"connecting": "Ag Nascadh",
|
||||||
|
"connect": "Ceangail",
|
||||||
|
"addAgent": "Cuir Aighne",
|
||||||
|
"agentAddedSuccessfully": "Aighne curtha leis go rathúil.",
|
||||||
|
"agentRemovedSuccessfully": "Aighne bhaint as go rathúil.",
|
||||||
|
"removeAgent": "Bain Aighne",
|
||||||
|
"removeAgentMsg": "An bhfuil tú cinnte gur mhaith leat an t-aighne seo a bhaint?",
|
||||||
|
"LongSyntaxNotSupported": "Ní thacaítear leis an níochán fada anseo. Úsáid an Eagarthóir YAML, le do thoil.",
|
||||||
|
"signedInDispDisabled": "Auth Díchumasaithe.",
|
||||||
|
"home": "Abhaile",
|
||||||
|
"addFirstStackMsg": "Scríobh do chéad stac!",
|
||||||
|
"notAvailableShort": "Níl ar Fáil",
|
||||||
|
"stackNotManagedByDockgeMsg": "Ní bhainistítear an staca seo ag Dockge.",
|
||||||
|
"containerName": "Ainm na gCoimeádán",
|
||||||
|
"disableauth.message2": "Tá sé deartha do chúinsí <strong>ina bhfuil sé beartaithe agat tríú páirtí athbhreithniú a chur i bhfeidhm</strong> os comhair Dockge cosúil le Rochtain Cloudflare, Authelia nó múnlaí deimhniú eile.",
|
||||||
|
"Settings": "Socruithe",
|
||||||
|
"active": "gníomhach",
|
||||||
|
"Lost connection to the socket server. Reconnecting...": "Ceangal caillte leis an bhfreastalaí soicéad. Ag athcheangal...",
|
||||||
|
"Saved": "Shábháil",
|
||||||
|
"Deployed": "Imlonnaithe",
|
||||||
|
"Deleted": "Scriosta",
|
||||||
|
"Updated": "Nuashonraithe",
|
||||||
|
"Started": "Thosaigh",
|
||||||
|
"Stopped": "Stopadh",
|
||||||
|
"Restarted": "Atosaigh",
|
||||||
|
"Downed": "Tugtha anuas",
|
||||||
|
"Switch to sh": "Athraigh go sh",
|
||||||
|
"terminal": "Teirminéal",
|
||||||
|
"CurrentHostname": "(Díshuiteáil: Lean an t-óstainm reatha)",
|
||||||
|
"New Container Name...": "Ainm Gabhdáin Nua...",
|
||||||
|
"Network name...": "Ainm líonra...",
|
||||||
|
"Select a network...": "Roghnaigh líonra...",
|
||||||
|
"NoNetworksAvailable": "Níl líonraí ar fáil. Ní mór duit líonraí inmheánacha a chur leis nó líonraí seachtracha a chumasú ar an taobh deas ar dtús."
|
||||||
|
}
|
132
frontend/src/lang/hu.json
Normal file
132
frontend/src/lang/hu.json
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
{
|
||||||
|
"languageName": "Angol",
|
||||||
|
"Repeat Password": "Jelszó Ismétlése",
|
||||||
|
"Create": "Létrehozás",
|
||||||
|
"signedInDisp": "Bejelentkezve {0}-ként",
|
||||||
|
"home": "Főképernyő",
|
||||||
|
"registry": "Bejegyzések",
|
||||||
|
"compose": "Összeállít",
|
||||||
|
"addFirstStackMsg": "Állítsd össze az első stack-odat!",
|
||||||
|
"stackName": "Stack Neve",
|
||||||
|
"deployStack": "Telepítés",
|
||||||
|
"deleteStack": "Törlés",
|
||||||
|
"stopStack": "Leállítás",
|
||||||
|
"restartStack": "Újraindítás",
|
||||||
|
"downStack": "Leállítva",
|
||||||
|
"editStack": "Szerkesztés",
|
||||||
|
"discardStack": "Eldobás",
|
||||||
|
"saveStackDraft": "Mentés",
|
||||||
|
"notAvailableShort": "N/A",
|
||||||
|
"stackNotManagedByDockgeMsg": "Ez a stack nem a Dockge kezelése alatt áll.",
|
||||||
|
"primaryHostname": "Elsődleges Gazdagépnév",
|
||||||
|
"general": "Általános",
|
||||||
|
"container": "Konténer | Konténerek",
|
||||||
|
"scanFolder": "Stack Mappa Beolvasása",
|
||||||
|
"dockerImage": "Applikáció-kép",
|
||||||
|
"restartPolicyNo": "Nem",
|
||||||
|
"environmentVariable": "Környezeti Változó | Környezeti Változók",
|
||||||
|
"containerName": "Konténer Neve",
|
||||||
|
"port": "Port | Portok",
|
||||||
|
"volume": "Tárhely | Tárhelyek",
|
||||||
|
"network": "Hálózat | Hálózatok",
|
||||||
|
"addListItem": "{0} Hozzáadása",
|
||||||
|
"deleteContainer": "Törlés",
|
||||||
|
"addContainer": "Konténer Hozzáadása",
|
||||||
|
"addNetwork": "Hálózat Hozzáadása",
|
||||||
|
"add": "Hozzáadás",
|
||||||
|
"Edit": "Szerkesztés",
|
||||||
|
"applyToYAML": "Alkalmazás YAML Formátumba",
|
||||||
|
"addInternalNetwork": "Hozzáadás",
|
||||||
|
"Save": "Mentés",
|
||||||
|
"Language": "Nyelv",
|
||||||
|
"Current User": "Jelenlegi Felhasználó",
|
||||||
|
"Change Password": "Jelszó Módosítása",
|
||||||
|
"Current Password": "Jelenlegi Jelszó",
|
||||||
|
"New Password": "Új Jelszó",
|
||||||
|
"Update Password": "Jelszó Cserélése",
|
||||||
|
"Advanced": "Fejlett",
|
||||||
|
"Enable Auth": "Hitelesítés Bekapcsolása",
|
||||||
|
"Disable Auth": "Hitelesítés Kikapcsolása",
|
||||||
|
"I understand, please disable": "Megértettem, kérem kapcsolja ki",
|
||||||
|
"Leave": "Kilépés",
|
||||||
|
"Frontend Version": "Frontend Verzió",
|
||||||
|
"Also check beta release": "Béta kiadás is",
|
||||||
|
"Remember me": "Emlékezz rám",
|
||||||
|
"Login": "Belépés",
|
||||||
|
"Username": "Felhasználónév",
|
||||||
|
"Password": "Jelszó",
|
||||||
|
"Settings": "Beállítások",
|
||||||
|
"Convert to Compose": "Átalakítás Compose-ra",
|
||||||
|
"Docker Run": "Docker Futtatása",
|
||||||
|
"active": "aktív",
|
||||||
|
"inactive": "inaktív",
|
||||||
|
"Appearance": "Megjelenés",
|
||||||
|
"Security": "Biztonság",
|
||||||
|
"Allowed commands:": "Megengedett parancsok:",
|
||||||
|
"Internal Networks": "Belső Hálózatok",
|
||||||
|
"External Networks": "Kölső Hálózatok",
|
||||||
|
"No External Networks": "Nincs Külső Hálózat",
|
||||||
|
"reverseProxyMsg1": "Proxy-t használ?",
|
||||||
|
"reverseProxyMsg2": "Javasolt WebSocket konfiguráció megtekintése",
|
||||||
|
"reconnecting...": "Újracsatlakozás…",
|
||||||
|
"extra": "Extra",
|
||||||
|
"newUpdate": "Új Frissítés",
|
||||||
|
"currentEndpoint": "Jelenlegi",
|
||||||
|
"agentOnline": "Online",
|
||||||
|
"dockgeAgent": "Dockge Felügyelő | Dockge Felügyelők",
|
||||||
|
"agentOffline": "Offline",
|
||||||
|
"connecting": "Csatlakozás",
|
||||||
|
"connect": "Csatlakozás",
|
||||||
|
"agentAddedSuccessfully": "Felügyelő hozzáadva.",
|
||||||
|
"agentRemovedSuccessfully": "Felügyelő törölve.",
|
||||||
|
"removeAgent": "Felügyelő Törlése",
|
||||||
|
"addAgent": "Felügyelő Hozzáadása",
|
||||||
|
"removeAgentMsg": "Biztos hogy törli ezt a Felügyelőt?",
|
||||||
|
"Create your admin account": "Adminisztrátor felhasználó létrehozása",
|
||||||
|
"authIncorrectCreds": "Helytelen felhasználónév vagy jelszó.",
|
||||||
|
"PasswordsDoNotMatch": "Jelszavak nem eggyeznek.",
|
||||||
|
"signedInDispDisabled": "Hitelesítés Kikapcsolva.",
|
||||||
|
"console": "Konzol",
|
||||||
|
"updateStack": "Frissítés",
|
||||||
|
"startStack": "Indítás",
|
||||||
|
"deleteStackMsg": "Biztos hogy törli ezt a stack-ot?",
|
||||||
|
"restartPolicyUnlessStopped": "Ha Nincs Leállítva",
|
||||||
|
"restartPolicyAlways": "Mindig",
|
||||||
|
"restartPolicyOnFailure": "Hibába Futáskor",
|
||||||
|
"restartPolicy": "Újraindítási Elv",
|
||||||
|
"dependsOn": "Konténer Függés | Konténer Függései",
|
||||||
|
"disableauth.message1": "Biztos hogy szeretné a <strong>hitelesítést kikapcsolni</strong>?",
|
||||||
|
"disableauth.message2": "Olyan esetekre ahol <strong>harmadfél általi hitelesítést szeretne alkalmazni</strong> a Dockge elött, mint például Cloudflare Access, Authelia vagy egyéb hitelesítő program.",
|
||||||
|
"passwordNotMatchMsg": "Az ismételt jelszó nem eggyezik.",
|
||||||
|
"autoGet": "Automatikus Megszerzés",
|
||||||
|
"createExternalNetwork": "Készítés",
|
||||||
|
"Repeat New Password": "Új Jelszó Megerősítése",
|
||||||
|
"Please use this option carefully!": "Ezt a lehetőséget használja óvatosan!",
|
||||||
|
"Check Update On GitHub": "Fríssítés Keresése Github-on",
|
||||||
|
"Show update if available": "Elérhető frissítések megjelenítése",
|
||||||
|
"Logout": "Kilépés",
|
||||||
|
"Lowercase only": "Csak kisbetűvel",
|
||||||
|
"exited": "végzett",
|
||||||
|
"About": "Az Alkalmazásról",
|
||||||
|
"Cannot connect to the socket server.": "A Socket csatlakozás nem elérhető.",
|
||||||
|
"connecting...": "Csatlakozás a socket szerver-hez…",
|
||||||
|
"url": "URL | URL-ek",
|
||||||
|
"dockgeURL": "Dockge URL (pl. http://127.0.0.1:5001)",
|
||||||
|
"LongSyntaxNotSupported": "A hosszú szintaxis itt nem támogatott. Használja a YAML szerkesztőt.",
|
||||||
|
"Lost connection to the socket server. Reconnecting...": "Megszakadt a kapcsolat a socket szerverrel. Újracsatlakozás...",
|
||||||
|
"Saved": "Elmentve",
|
||||||
|
"Deployed": "Telepítve",
|
||||||
|
"Deleted": "Törölve",
|
||||||
|
"Updated": "Frissítve",
|
||||||
|
"Started": "Indult",
|
||||||
|
"Stopped": "Megállítva",
|
||||||
|
"Restarted": "Újraindítva",
|
||||||
|
"Downed": "Leállított",
|
||||||
|
"Switch to sh": "Váltás shell-re",
|
||||||
|
"terminal": "Terminál",
|
||||||
|
"CurrentHostname": "(Nincs beállítva: Az aktuális gazdagépnév követése)",
|
||||||
|
"New Container Name...": "Új konténer név...",
|
||||||
|
"Network name...": "Hálózat név...",
|
||||||
|
"Select a network...": "Válasszon ki egy hálózatot...",
|
||||||
|
"NoNetworksAvailable": "Nincs elérhető hálózat. Először hozzá kell adnia belső hálózatokat, vagy engedélyeznie kell a külső hálózatokat a jobb oldalon."
|
||||||
|
}
|
122
frontend/src/lang/id.json
Normal file
122
frontend/src/lang/id.json
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
{
|
||||||
|
"Create your admin account": "Buat akun admin Anda",
|
||||||
|
"PasswordsDoNotMatch": "Kata sandi tidak sama.",
|
||||||
|
"Repeat Password": "Ulangi Kata Sandi",
|
||||||
|
"Create": "Buat",
|
||||||
|
"signedInDisp": "Masuk sebagai {0}",
|
||||||
|
"signedInDispDisabled": "Otentikasi Dinonaktifkan.",
|
||||||
|
"home": "Beranda",
|
||||||
|
"console": "Konsol",
|
||||||
|
"registry": "Registri",
|
||||||
|
"compose": "Menyusun",
|
||||||
|
"addFirstStackMsg": "Buat tumpukan pertama Anda!",
|
||||||
|
"stackName": "Nama Tumpukan",
|
||||||
|
"deployStack": "Terapkan",
|
||||||
|
"stopStack": "Hentikan",
|
||||||
|
"restartStack": "Mulai ulang",
|
||||||
|
"updateStack": "Pembaruan",
|
||||||
|
"downStack": "Hentikan & Tidak aktif",
|
||||||
|
"editStack": "Sunting",
|
||||||
|
"discardStack": "Buang",
|
||||||
|
"saveStackDraft": "Simpan",
|
||||||
|
"notAvailableShort": "T/A",
|
||||||
|
"stackNotManagedByDockgeMsg": "Tumpukan ini tidak dikelola oleh Dockge.",
|
||||||
|
"primaryHostname": "Nama Host Utama",
|
||||||
|
"general": "Umum",
|
||||||
|
"container": "Kontainer | Wadah",
|
||||||
|
"scanFolder": "Pindai Folder Tumpukan",
|
||||||
|
"restartPolicyUnlessStopped": "Kecuali Dihentikan",
|
||||||
|
"restartPolicyAlways": "Selalu",
|
||||||
|
"restartPolicyNo": "Tidak",
|
||||||
|
"environmentVariable": "Variabel Lingkungan | Variabel Lingkungan",
|
||||||
|
"dockerImage": "Image",
|
||||||
|
"startStack": "Mulai",
|
||||||
|
"restartPolicy": "Kebijakan Mulai Ulang",
|
||||||
|
"containerName": "Nama Kontainer",
|
||||||
|
"network": "Jaringan",
|
||||||
|
"dependsOn": "Ketergantungan Kontainer",
|
||||||
|
"addListItem": "Tambah {0}",
|
||||||
|
"deleteContainer": "Hapus",
|
||||||
|
"addContainer": "Tambah Kontainer",
|
||||||
|
"addNetwork": "Tambah Jaringan",
|
||||||
|
"disableauth.message1": "Apakah Anda yakin untuk <strong>mematikan otentikasi</strong>?",
|
||||||
|
"passwordNotMatchMsg": "Kata sandi berulang tidak cocok.",
|
||||||
|
"autoGet": "Otomatis Dapatkan",
|
||||||
|
"add": "Tambah",
|
||||||
|
"Edit": "Sunting",
|
||||||
|
"port": "Port",
|
||||||
|
"volume": "Volume",
|
||||||
|
"createExternalNetwork": "Buat",
|
||||||
|
"addInternalNetwork": "Tambah",
|
||||||
|
"Save": "Simpan",
|
||||||
|
"Language": "Bahasa",
|
||||||
|
"Change Password": "Ubah Kata Sandi",
|
||||||
|
"Current Password": "Ubah Kata Sandi",
|
||||||
|
"New Password": "Kata Sandi Baru",
|
||||||
|
"Repeat New Password": "Ulangi Kata Sandi",
|
||||||
|
"Update Password": "Perbarui Kata Sandi",
|
||||||
|
"Advanced": "Lanjutan",
|
||||||
|
"Enable Auth": "Hidupkan Otentikasi",
|
||||||
|
"Disable Auth": "Matikan Otentikasi",
|
||||||
|
"I understand, please disable": "Saya mengerti, tolong nonaktifkan",
|
||||||
|
"Leave": "Pergi",
|
||||||
|
"Frontend Version": "Versi Antarmuka",
|
||||||
|
"Check Update On GitHub": "Cek pembaruan di Github",
|
||||||
|
"Show update if available": "Tampilkan pembaruan jika tersedia",
|
||||||
|
"Remember me": "Ingat saya",
|
||||||
|
"Login": "Masuk",
|
||||||
|
"Username": "Nama Pengguna",
|
||||||
|
"Password": "Kata Sandi",
|
||||||
|
"Settings": "Pengaturan",
|
||||||
|
"Logout": "Keluar",
|
||||||
|
"Lowercase only": "Huruf kecil saja",
|
||||||
|
"Convert to Compose": "Ubah ke Tumpukan",
|
||||||
|
"active": "aktif",
|
||||||
|
"exited": "keluar",
|
||||||
|
"inactive": "nonaktif",
|
||||||
|
"Appearance": "Tampilan",
|
||||||
|
"Security": "Keamanan",
|
||||||
|
"About": "Tentang",
|
||||||
|
"Internal Networks": "Jaringan internal",
|
||||||
|
"External Networks": "Jaringan eksternal",
|
||||||
|
"No External Networks": "Tanpa Jaringan Eksternal",
|
||||||
|
"reverseProxyMsg1": "Menggunakan Reverse Proxy ?",
|
||||||
|
"Cannot connect to the socket server.": "Tidak bisa terhubung dengan server socket.",
|
||||||
|
"reconnecting...": "Menghubungkan kembali…",
|
||||||
|
"connecting...": "Menyambungkan ke server socket…",
|
||||||
|
"url": "TAUTAN",
|
||||||
|
"extra": "Ekstra",
|
||||||
|
"Docker Run": "Jalankan Docker",
|
||||||
|
"newUpdate": "Pembaruan Baru",
|
||||||
|
"languageName": "Bahasa Indonesia (Indonesian)",
|
||||||
|
"authIncorrectCreds": "Nama pengguna atau sandi salah.",
|
||||||
|
"deleteStack": "Hapus",
|
||||||
|
"deleteStackMsg": "Apakah Anda yakin Anda ingin menghapus tumpukan ini ?",
|
||||||
|
"restartPolicyOnFailure": "Ketika Gagal",
|
||||||
|
"disableauth.message2": "Ini dirancang untuk skenario <strong>di mana Anda bermaksud untuk mengimplementasikan otentikasi pihak ketiga</strong> di depan Dockge seperti Cloudflare Access, Authelia, atau mekanisme otentikasi lainnya.",
|
||||||
|
"applyToYAML": "Aplikasikan ke YAML",
|
||||||
|
"Current User": "Pengguna Saat Ini",
|
||||||
|
"Please use this option carefully!": "Mohon berhati - hati menggunakan opsi ini!",
|
||||||
|
"Also check beta release": "Juga cek keluaran beta",
|
||||||
|
"Allowed commands:": "Perintah yang diperbolehkan:",
|
||||||
|
"reverseProxyMsg2": "Lihat cara mengonfigurasinya untuk WebSocket",
|
||||||
|
"dockgeURL": "Alamat Dockge (cth. http://127.0.0.1:5001)",
|
||||||
|
"connecting": "Menghubungkan",
|
||||||
|
"addAgent": "Tambah Agen",
|
||||||
|
"agentAddedSuccessfully": "Agen sukses ditambahkan.",
|
||||||
|
"agentRemovedSuccessfully": "Agen sukses dihapus.",
|
||||||
|
"removeAgent": "Hapus Agen",
|
||||||
|
"connect": "Hubungkan",
|
||||||
|
"dockgeAgent": "Agen Dockge",
|
||||||
|
"currentEndpoint": "Sekarang",
|
||||||
|
"agentOnline": "Terhubung",
|
||||||
|
"agentOffline": "Terputus",
|
||||||
|
"removeAgentMsg": "Apakah anda yakin untuk menghapus agen ini?",
|
||||||
|
"LongSyntaxNotSupported": "Sintaks yang panjang tidak didukung di sini. Silakan gunakan editor YAML.",
|
||||||
|
"Saved": "Tersimpan",
|
||||||
|
"Deleted": "Terhapus",
|
||||||
|
"Updated": "Telah diperbaharui",
|
||||||
|
"Started": "Aplikasi dimulai",
|
||||||
|
"Stopped": "Aplikasi dihentikan",
|
||||||
|
"Restarted": "Aplikasi memuat kembali"
|
||||||
|
}
|
@ -10,16 +10,16 @@
|
|||||||
"home": "Home",
|
"home": "Home",
|
||||||
"console": "Console",
|
"console": "Console",
|
||||||
"registry": "Registro",
|
"registry": "Registro",
|
||||||
"compose": "Compose",
|
"compose": "Componi",
|
||||||
"addFirstStackMsg": "Componi il tuo primo stack!",
|
"addFirstStackMsg": "Componi il tuo primo stack!",
|
||||||
"stackName": "Nome dello stack",
|
"stackName": "Nome dello stack",
|
||||||
"deployStack": "Deploy",
|
"deployStack": "Rilascia",
|
||||||
"deleteStack": "Cancella",
|
"deleteStack": "Cancella",
|
||||||
"stopStack": "Stop",
|
"stopStack": "Stop",
|
||||||
"restartStack": "Riavvia",
|
"restartStack": "Riavvia",
|
||||||
"updateStack": "Aggiorna",
|
"updateStack": "Aggiorna",
|
||||||
"startStack": "Avvia",
|
"startStack": "Avvia",
|
||||||
"downStack": "Stop & Down",
|
"downStack": "Stop & Inattivo",
|
||||||
"editStack": "Modifica",
|
"editStack": "Modifica",
|
||||||
"discardStack": "Annulla",
|
"discardStack": "Annulla",
|
||||||
"saveStackDraft": "Salva",
|
"saveStackDraft": "Salva",
|
||||||
@ -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": "Username",
|
"Username": "Nome Utente",
|
||||||
"Password": "Password",
|
"Password": "Password",
|
||||||
"Settings": "Impostazioni",
|
"Settings": "Impostazioni",
|
||||||
"Logout": "Logout",
|
"Logout": "Logout",
|
||||||
@ -92,10 +92,25 @@
|
|||||||
"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?",
|
"reverseProxyMsg1": "Stai usando Reverse Proxy?",
|
||||||
"reverseProxyMsg2": "Controlla come configurarlo per WebSocket",
|
"reverseProxyMsg2": "Verifica come configurarlo per il WebSocket",
|
||||||
"Cannot connect to the socket server.": "Impossibile connettersi al server socket.",
|
"Cannot connect to the socket server.": "impossibile collegarsi al socket server",
|
||||||
"connecting...": "Connessione al server socket…",
|
"connecting...": "connettendosi al socket server…",
|
||||||
"extra": "Extra",
|
"extra": "Extra",
|
||||||
"reconnecting...": "Riconnessione…"
|
"reconnecting...": "Riconnessione…",
|
||||||
|
"url": "URL | URLs",
|
||||||
|
"newUpdate": "Nuovo aggiornamento",
|
||||||
|
"dockgeAgent": "Agente Dockge | Agenti Dockge",
|
||||||
|
"currentEndpoint": "Corrente",
|
||||||
|
"agentOnline": "Online",
|
||||||
|
"agentOffline": "Offline",
|
||||||
|
"connecting": "In connessione",
|
||||||
|
"connect": "Connetti",
|
||||||
|
"dockgeURL": "Dockge URL (ad esempio http://127.0.0.1:5001)",
|
||||||
|
"agentRemovedSuccessfully": "Agente rimosso con successo.",
|
||||||
|
"removeAgent": "Rimuovi Agente",
|
||||||
|
"removeAgentMsg": "Sei sicuro di voler rimuovere questo agente?",
|
||||||
|
"addAgent": "Aggungi Agente",
|
||||||
|
"agentAddedSuccessfully": "Agente aggiunto correttamente.",
|
||||||
|
"LongSyntaxNotSupported": "La sintassi lunga non è supportata qui. Utilizzare l'editor YAML."
|
||||||
}
|
}
|
||||||
|
@ -34,7 +34,7 @@
|
|||||||
"primaryHostname": "主ホスト名",
|
"primaryHostname": "主ホスト名",
|
||||||
"container": "コンテナ",
|
"container": "コンテナ",
|
||||||
"dependsOn": "コンテナ依存関係",
|
"dependsOn": "コンテナ依存関係",
|
||||||
"downStack": "停止して削除",
|
"downStack": "停止して非アクティブ化",
|
||||||
"notAvailableShort": "N/A",
|
"notAvailableShort": "N/A",
|
||||||
"restartPolicyUnlessStopped": "手動で停止されるまで",
|
"restartPolicyUnlessStopped": "手動で停止されるまで",
|
||||||
"restartPolicyAlways": "常時",
|
"restartPolicyAlways": "常時",
|
||||||
@ -94,5 +94,36 @@
|
|||||||
"I understand, please disable": "理解しました。無効化してください",
|
"I understand, please disable": "理解しました。無効化してください",
|
||||||
"Lowercase only": "小文字のみ",
|
"Lowercase only": "小文字のみ",
|
||||||
"reverseProxyMsg1": "リバースプロキシを使用していますか?",
|
"reverseProxyMsg1": "リバースプロキシを使用していますか?",
|
||||||
"connecting...": "ソケットサーバーに接続中…"
|
"connecting...": "ソケットサーバーに接続中…",
|
||||||
|
"newUpdate": "新しいバージョン",
|
||||||
|
"dockgeAgent": "Dockge エージェント",
|
||||||
|
"dockgeURL": "DockgeのURL (例: http://127.0.0.1:5001)",
|
||||||
|
"agentOnline": "オンライン",
|
||||||
|
"agentOffline": "オフライン",
|
||||||
|
"connecting": "接続中",
|
||||||
|
"connect": "接続",
|
||||||
|
"addAgent": "エージェントを追加",
|
||||||
|
"agentAddedSuccessfully": "エージェントが正常に追加されました。",
|
||||||
|
"agentRemovedSuccessfully": "エージェントは正常に削除されました。",
|
||||||
|
"removeAgent": "エージェントを削除",
|
||||||
|
"removeAgentMsg": "本当にこのエージェントを削除しますか?",
|
||||||
|
"url": "URL | URL",
|
||||||
|
"About": "Dockgeについて",
|
||||||
|
"Docker Run": "Docker Run to Compose",
|
||||||
|
"LongSyntaxNotSupported": "長い構文はここではサポートされていません。YAMLエディタを使用してください。",
|
||||||
|
"Lost connection to the socket server. Reconnecting...": "ソケットサーバーとの接続が失われました。再接続中です...",
|
||||||
|
"extra": "追加設定",
|
||||||
|
"Saved": "保存済み",
|
||||||
|
"Deployed": "デプロイ済み",
|
||||||
|
"Deleted": "削除済み",
|
||||||
|
"Updated": "アップデート済み",
|
||||||
|
"Started": "開始済み",
|
||||||
|
"Stopped": "停止済み",
|
||||||
|
"Restarted": "再起動済み",
|
||||||
|
"Switch to sh": "shに切り替え",
|
||||||
|
"terminal": "ターミナル",
|
||||||
|
"New Container Name...": "新しいコンテナ名...",
|
||||||
|
"Network name...": "ネットワーク名...",
|
||||||
|
"Select a network...": "ネットワークを選択...",
|
||||||
|
"NoNetworksAvailable": "利用可能なネットワークがありません。まず右側の内部ネットワークを追加するか、外部ネットワークを有効にする必要があります。"
|
||||||
}
|
}
|
||||||
|
@ -92,11 +92,25 @@
|
|||||||
"External Networks": "외부 네트워크",
|
"External Networks": "외부 네트워크",
|
||||||
"No External Networks": "외부 네트워크 없음",
|
"No External Networks": "외부 네트워크 없음",
|
||||||
"reverseProxyMsg2": "여기서 WebSocket을 위한 설정을 확인해 보세요",
|
"reverseProxyMsg2": "여기서 WebSocket을 위한 설정을 확인해 보세요",
|
||||||
"downStack": "정지 & Down",
|
"downStack": "정지 & 비활성화",
|
||||||
"reverseProxyMsg1": "리버스 프록시를 사용하고 계신가요?",
|
"reverseProxyMsg1": "리버스 프록시를 사용하고 계신가요?",
|
||||||
"Cannot connect to the socket server.": "소켓 서버에 연결하지 못했습니다.",
|
"Cannot connect to the socket server.": "소켓 서버에 연결하지 못했습니다.",
|
||||||
"connecting...": "소켓 서버에 연결하는 중…",
|
"connecting...": "소켓 서버에 연결하는 중…",
|
||||||
"extra": "기타",
|
"extra": "기타",
|
||||||
"url": "URL | URL",
|
"url": "URL | URL",
|
||||||
"reconnecting...": "재연결 중…"
|
"reconnecting...": "재연결 중…",
|
||||||
|
"newUpdate": "새 업데이트",
|
||||||
|
"dockgeURL": "Dockge URL (예. http://127.0.0.1:5001)",
|
||||||
|
"agentOnline": "온라인",
|
||||||
|
"agentOffline": "오프라인",
|
||||||
|
"connect": "연결",
|
||||||
|
"addAgent": "에이전트 추가",
|
||||||
|
"agentAddedSuccessfully": "에이전트를 성공적으로 추가했습니다.",
|
||||||
|
"removeAgent": "에이전트 삭제",
|
||||||
|
"removeAgentMsg": "정말로 이 에이전트를 삭제하시겠습니까?",
|
||||||
|
"dockgeAgent": "Dockge 에이전트",
|
||||||
|
"currentEndpoint": "현재",
|
||||||
|
"connecting": "연결 중",
|
||||||
|
"agentRemovedSuccessfully": "에이전트를 성공적으로 삭제했습니다.",
|
||||||
|
"LongSyntaxNotSupported": "긴 문법은 여기서 지원되지 않습니다. YAML 에디터를 사용하세요."
|
||||||
}
|
}
|
||||||
|
34
frontend/src/lang/nb_NO.json
Normal file
34
frontend/src/lang/nb_NO.json
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
{
|
||||||
|
"Create your admin account": "Lag din administrator konto",
|
||||||
|
"authIncorrectCreds": "Brukernavn eller passord stemmer ikke.",
|
||||||
|
"PasswordsDoNotMatch": "Passord stemmer ikke.",
|
||||||
|
"Repeat Password": "Gjenta passord",
|
||||||
|
"Create": "Lag",
|
||||||
|
"signedInDisp": "Logg in som {0}",
|
||||||
|
"signedInDispDisabled": "Auth deaktivert.",
|
||||||
|
"home": "Hjem",
|
||||||
|
"console": "Konsoll",
|
||||||
|
"registry": "Register",
|
||||||
|
"compose": "Skriv",
|
||||||
|
"addFirstStackMsg": "Lag din første stack!",
|
||||||
|
"stackName": "Navn på stack",
|
||||||
|
"deployStack": "Utplassere",
|
||||||
|
"deleteStack": "Slett",
|
||||||
|
"stopStack": "Stoppe",
|
||||||
|
"restartStack": "Omstart",
|
||||||
|
"updateStack": "Oppdater",
|
||||||
|
"downStack": "Stop & Inaktiver",
|
||||||
|
"editStack": "Rediger",
|
||||||
|
"discardStack": "Kast",
|
||||||
|
"saveStackDraft": "Lagre",
|
||||||
|
"notAvailableShort": "N/A",
|
||||||
|
"deleteStackMsg": "Er du sikker på at du vil slette denne stacken?",
|
||||||
|
"stackNotManagedByDockgeMsg": "Denne stacken er ikke styrt av Dockge.",
|
||||||
|
"primaryHostname": "Primært vertsnavn",
|
||||||
|
"general": "Generell",
|
||||||
|
"container": "Container | Containers",
|
||||||
|
"scanFolder": "Skann Stacks mappe",
|
||||||
|
"dockerImage": "Bilde",
|
||||||
|
"languageName": "Engelsk",
|
||||||
|
"startStack": "Start"
|
||||||
|
}
|
@ -1,14 +1,14 @@
|
|||||||
{
|
{
|
||||||
"languageName": "Nederlands",
|
"languageName": "Nederlands",
|
||||||
"authIncorrectCreds": "Onjuiste gebruikersnaam of wachtwoord.",
|
"authIncorrectCreds": "Onjuiste gebruikersnaam of wachtwoord.",
|
||||||
"PasswordsDoNotMatch": "Paswoorden komen niet overeen.",
|
"PasswordsDoNotMatch": "Wachtwoorden komen niet overeen.",
|
||||||
"Repeat Password": "Herhaal wachtwoord",
|
"Repeat Password": "Herhaal wachtwoord",
|
||||||
"Create": "Aanmaken",
|
"Create": "Aanmaken",
|
||||||
"signedInDisp": "Ingelogd als {0}",
|
"signedInDisp": "Ingelogd als {0}",
|
||||||
"home": "Startpagina",
|
"home": "Home",
|
||||||
"console": "Console",
|
"console": "Console",
|
||||||
"registry": "Register",
|
"registry": "Register",
|
||||||
"compose": "Samenstellen",
|
"compose": "Nieuwe stack",
|
||||||
"stackName": "Stack naam",
|
"stackName": "Stack naam",
|
||||||
"deployStack": "Opzetten",
|
"deployStack": "Opzetten",
|
||||||
"deleteStack": "Verwijder",
|
"deleteStack": "Verwijder",
|
||||||
@ -16,11 +16,11 @@
|
|||||||
"restartStack": "Herstart",
|
"restartStack": "Herstart",
|
||||||
"updateStack": "Update",
|
"updateStack": "Update",
|
||||||
"startStack": "Start",
|
"startStack": "Start",
|
||||||
"downStack": "Stop & Down",
|
"downStack": "Stop & Afsluiten",
|
||||||
"editStack": "Bewerken",
|
"editStack": "Bewerken",
|
||||||
"discardStack": "Verwijderen",
|
"discardStack": "Verwijderen",
|
||||||
"saveStackDraft": "Opslaan",
|
"saveStackDraft": "Opslaan",
|
||||||
"notAvailableShort": "NVT",
|
"notAvailableShort": "n.v.t.",
|
||||||
"stackNotManagedByDockgeMsg": "Deze stack wordt niet beheerd door Dockge.",
|
"stackNotManagedByDockgeMsg": "Deze stack wordt niet beheerd door Dockge.",
|
||||||
"primaryHostname": "Primaire hostnaam",
|
"primaryHostname": "Primaire hostnaam",
|
||||||
"general": "Algemeen",
|
"general": "Algemeen",
|
||||||
@ -83,9 +83,9 @@
|
|||||||
"reverseProxyMsg1": "Reverse proxy in gebruik?",
|
"reverseProxyMsg1": "Reverse proxy in gebruik?",
|
||||||
"reverseProxyMsg2": "Controleer hoe te configureren voor WebSocket",
|
"reverseProxyMsg2": "Controleer hoe te configureren voor WebSocket",
|
||||||
"Cannot connect to the socket server.": "Kan geen verbinding maken met de socket server.",
|
"Cannot connect to the socket server.": "Kan geen verbinding maken met de socket server.",
|
||||||
"reconnecting...": "Herverbinden...",
|
"reconnecting...": "Herverbinden…",
|
||||||
"connecting...": "Verbinden met de socket server...",
|
"connecting...": "Verbinden met de socket server…",
|
||||||
"url": "Url(s)",
|
"url": "Adres(sen)",
|
||||||
"extra": "Extra",
|
"extra": "Extra",
|
||||||
"Create your admin account": "Creëer je beheerders-account",
|
"Create your admin account": "Creëer je beheerders-account",
|
||||||
"addFirstStackMsg": "Maak je eerste stack!",
|
"addFirstStackMsg": "Maak je eerste stack!",
|
||||||
@ -98,5 +98,19 @@
|
|||||||
"Please use this option carefully!": "Wees voorzichtig met deze optie!",
|
"Please use this option carefully!": "Wees voorzichtig met deze optie!",
|
||||||
"Also check beta release": "Controleer ook op beta releases",
|
"Also check beta release": "Controleer ook op beta releases",
|
||||||
"Convert to Compose": "Converteer naar compose",
|
"Convert to Compose": "Converteer naar compose",
|
||||||
"External Networks": "Externe netwerken"
|
"External Networks": "Externe netwerken",
|
||||||
|
"newUpdate": "Nieuwe update",
|
||||||
|
"dockgeAgent": "Dockge Agent | Dockge Agenten",
|
||||||
|
"currentEndpoint": "Huidige",
|
||||||
|
"dockgeURL": "Dockge Adres (bijv. http://127.0.0.1:5001)",
|
||||||
|
"agentOnline": "Online",
|
||||||
|
"agentOffline": "Offline",
|
||||||
|
"connecting": "Verbinden",
|
||||||
|
"connect": "Verbind",
|
||||||
|
"addAgent": "Agent toevoegen",
|
||||||
|
"agentAddedSuccessfully": "Agent toegevoegd.",
|
||||||
|
"agentRemovedSuccessfully": "Agent verwijderd.",
|
||||||
|
"removeAgent": "Verwijder agent",
|
||||||
|
"removeAgentMsg": "Weet je zeker dat je deze agent wilt verwijderen?",
|
||||||
|
"LongSyntaxNotSupported": "Lange syntax wordt hier niet ondersteund. Gebruik de YAML editor."
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
"registry": "Rejestr",
|
"registry": "Rejestr",
|
||||||
"compose": "Stwórz",
|
"compose": "Stwórz",
|
||||||
"addFirstStackMsg": "Stwórz swój pierwszy stos!",
|
"addFirstStackMsg": "Stwórz swój pierwszy stos!",
|
||||||
"stackName" : "Nazwa stosu",
|
"stackName": "Nazwa stosu",
|
||||||
"deployStack": "Wdroż",
|
"deployStack": "Wdroż",
|
||||||
"deleteStack": "Usuń",
|
"deleteStack": "Usuń",
|
||||||
"stopStack": "Zatrzymaj",
|
"stopStack": "Zatrzymaj",
|
||||||
@ -22,7 +22,7 @@
|
|||||||
"editStack": "Edytuj",
|
"editStack": "Edytuj",
|
||||||
"discardStack": "Odrzuć",
|
"discardStack": "Odrzuć",
|
||||||
"saveStackDraft": "Zapisz",
|
"saveStackDraft": "Zapisz",
|
||||||
"notAvailableShort" : "N/A",
|
"notAvailableShort": "N/A",
|
||||||
"deleteStackMsg": "Czy na pewno chcesz usunąć ten stos?",
|
"deleteStackMsg": "Czy na pewno chcesz usunąć ten stos?",
|
||||||
"stackNotManagedByDockgeMsg": "Ten stos nie jest zarządzany przez Dockge.",
|
"stackNotManagedByDockgeMsg": "Ten stos nie jest zarządzany przez Dockge.",
|
||||||
"primaryHostname": "Podstawowa nazwa hosta",
|
"primaryHostname": "Podstawowa nazwa hosta",
|
||||||
@ -90,5 +90,43 @@
|
|||||||
"Allowed commands:": "Dozwolone polecenia:",
|
"Allowed commands:": "Dozwolone polecenia:",
|
||||||
"Internal Networks": "Sieci wewnętrzne",
|
"Internal Networks": "Sieci wewnętrzne",
|
||||||
"External Networks": "Sieci zewnętrzne",
|
"External Networks": "Sieci zewnętrzne",
|
||||||
"No External Networks": "Brak sieci zewnętrznych"
|
"No External Networks": "Brak sieci zewnętrznych",
|
||||||
|
"newUpdate": "Nowa Aktualizacja",
|
||||||
|
"dockgeAgent": "Agent Dockge | Agenci Dockge",
|
||||||
|
"currentEndpoint": "Obecny",
|
||||||
|
"connecting": "Łączenie",
|
||||||
|
"connect": "Połącz",
|
||||||
|
"addAgent": "Dodaj Agenta",
|
||||||
|
"agentAddedSuccessfully": "Agent pomyślnie dodany.",
|
||||||
|
"agentRemovedSuccessfully": "Agent pomyślnie usunięty.",
|
||||||
|
"removeAgent": "Usuń Agenta",
|
||||||
|
"removeAgentMsg": "Czy na pewno usunąć tego Agenta?",
|
||||||
|
"dockgeURL": "Dockge URL (e.g. http://127.0.0.1:5001)",
|
||||||
|
"agentOnline": "Online",
|
||||||
|
"agentOffline": "Offline",
|
||||||
|
"downStack": "Zatrzymaj i Dezaktywuj",
|
||||||
|
"reverseProxyMsg1": "Używasz Serwer Proxy?",
|
||||||
|
"reverseProxyMsg2": "Sprawdź w jaki sposób skonfigurować dla WebSocketów",
|
||||||
|
"Cannot connect to the socket server.": "Brak połączenia z socket serwera.",
|
||||||
|
"connecting...": "Łączenie z socketem serwera…",
|
||||||
|
"extra": "Ekstra",
|
||||||
|
"url": "URL | URLe",
|
||||||
|
"reconnecting...": "Wznawianie połączenia…",
|
||||||
|
"LongSyntaxNotSupported": "Nieobsługiwana składnia. Użyj edytora YAML.",
|
||||||
|
"Saved": "Zapisano",
|
||||||
|
"Switch to sh": "Przełącz na sh",
|
||||||
|
"terminal": "Terminal",
|
||||||
|
"Restarted": "Zrestartowano",
|
||||||
|
"Deployed": "Wdrożono",
|
||||||
|
"Deleted": "Usunięto",
|
||||||
|
"Updated": "Zaktualizowano",
|
||||||
|
"Started": "Uruchomiono",
|
||||||
|
"Stopped": "Zatrzymano",
|
||||||
|
"Downed": "Położono",
|
||||||
|
"Lost connection to the socket server. Reconnecting...": "Utracono połączenie z socketem serwera. Ponawiam połączenie...",
|
||||||
|
"New Container Name...": "Nazwa nowego kontenera...",
|
||||||
|
"Network name...": "Nazwa sieci...",
|
||||||
|
"Select a network...": "Wybierz sieć...",
|
||||||
|
"NoNetworksAvailable": "Brak dostępnych sieci. Musisz najpierw dodać sieć wewnętrzną lub włączyć sieci zewnętrzne po prawej stronie.",
|
||||||
|
"CurrentHostname": "(Odznacze: Podążaj za aktualnym hostem)"
|
||||||
}
|
}
|
||||||
|
@ -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,5 +90,43 @@
|
|||||||
"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 & Inativar",
|
||||||
|
"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…",
|
||||||
|
"newUpdate": "Nova Atualização",
|
||||||
|
"dockgeAgent": "Agente Dockge | Agentes Dockge",
|
||||||
|
"currentEndpoint": "Atual",
|
||||||
|
"dockgeURL": "Dockge URL (ex. http://127.0.0.1:5001)",
|
||||||
|
"agentOnline": "Online",
|
||||||
|
"agentOffline": "Offline",
|
||||||
|
"connecting": "Conectando",
|
||||||
|
"connect": "Conectar",
|
||||||
|
"addAgent": "Adicionar agente",
|
||||||
|
"agentAddedSuccessfully": "Agente adicionado com sucesso.",
|
||||||
|
"agentRemovedSuccessfully": "Agente removido com sucesso.",
|
||||||
|
"removeAgent": "Remover Agente",
|
||||||
|
"removeAgentMsg": "Tem certeza de que deseja remover este agente?",
|
||||||
|
"LongSyntaxNotSupported": "Sintaxe longa não é suportada aqui. Por favor, use o editor de YAML.",
|
||||||
|
"Lost connection to the socket server. Reconnecting...": "Conexão perdida com o servidor de socket. Reconectando...",
|
||||||
|
"Saved": "Salvo",
|
||||||
|
"Deployed": "Implantado",
|
||||||
|
"Deleted": "Excluído",
|
||||||
|
"Updated": "Alterado",
|
||||||
|
"Started": "Iniciado",
|
||||||
|
"Stopped": "Parado",
|
||||||
|
"Restarted": "Reiniciado",
|
||||||
|
"Downed": "Finalizado",
|
||||||
|
"Switch to sh": "Mudar para sh",
|
||||||
|
"terminal": "Terminal",
|
||||||
|
"CurrentHostname": "(Não definido: seguir o nome do host atual)",
|
||||||
|
"New Container Name...": "Nome do novo container...",
|
||||||
|
"Network name...": "Nome da rede...",
|
||||||
|
"Select a network...": "Selecione uma rede...",
|
||||||
|
"NoNetworksAvailable": "Nenhuma rede disponível. Você precisa adicionar redes internas ou habilitar redes externas no lado direito primeiro."
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
"registry": "Registro",
|
"registry": "Registro",
|
||||||
"compose": "Compor",
|
"compose": "Compor",
|
||||||
"addFirstStackMsg": "Componha sua primeira pilha!",
|
"addFirstStackMsg": "Componha sua primeira pilha!",
|
||||||
"stackName" : "Nome da Pilha",
|
"stackName": "Nome da Pilha",
|
||||||
"deployStack": "Implantar",
|
"deployStack": "Implantar",
|
||||||
"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 de que deseja excluir esta pilha?",
|
"deleteStackMsg": "Tem certeza de que deseja excluir esta pilha?",
|
||||||
"stackNotManagedByDockgeMsg": "Esta pilha não é gerenciada pelo Dockge.",
|
"stackNotManagedByDockgeMsg": "Esta pilha não é gerenciada pelo Dockge.",
|
||||||
"primaryHostname": "Nome do Host Primário",
|
"primaryHostname": "Nome do Host Primário",
|
||||||
@ -90,5 +90,27 @@
|
|||||||
"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",
|
||||||
|
"newUpdate": "Nova Atualização",
|
||||||
|
"currentEndpoint": "Atual",
|
||||||
|
"dockgeURL": "Dockge URL (e.g. http://127.0.0.1:5001)",
|
||||||
|
"agentOnline": "Online",
|
||||||
|
"agentOffline": "Offline",
|
||||||
|
"connecting": "Conectando",
|
||||||
|
"addAgent": "Adicionar Agente",
|
||||||
|
"agentAddedSuccessfully": "Agente adicionado com sucesso.",
|
||||||
|
"agentRemovedSuccessfully": "Agente removido com sucesso.",
|
||||||
|
"removeAgent": "Remover Agente",
|
||||||
|
"downStack": "Parar & Desativar",
|
||||||
|
"dockgeAgent": "Dockge Agente | Dockge Agentes",
|
||||||
|
"connect": "Conectar",
|
||||||
|
"removeAgentMsg": "Tem certeza de que deseja remover este agente?",
|
||||||
|
"reverseProxyMsg1": "Usando um Proxy Reverso?",
|
||||||
|
"reverseProxyMsg2": "Verifique para configurá-lo como WebSocket",
|
||||||
|
"Cannot connect to the socket server.": "Não é possível se conectar ao servidor socket.",
|
||||||
|
"url": "URL | URL's",
|
||||||
|
"extra": "Extra",
|
||||||
|
"reconnecting...": "Reconectando…",
|
||||||
|
"connecting...": "Conectando ao servidor de socket…",
|
||||||
|
"LongSyntaxNotSupported": "Sintaxes longas não são suportadas aqui. Por favor, utilize um editor YAML."
|
||||||
}
|
}
|
||||||
|
@ -86,11 +86,11 @@
|
|||||||
"reverseProxyMsg1": "Folosești un proxy invers?",
|
"reverseProxyMsg1": "Folosești un proxy invers?",
|
||||||
"reverseProxyMsg2": "Verificați cum să-l configurați pentru WebSocket",
|
"reverseProxyMsg2": "Verificați cum să-l configurați pentru WebSocket",
|
||||||
"Cannot connect to the socket server.": "Nu se poate conecta la serverul socket.",
|
"Cannot connect to the socket server.": "Nu se poate conecta la serverul socket.",
|
||||||
"reconnecting...": "Reconectare...",
|
"reconnecting...": "Reconectare…",
|
||||||
"connecting...": "Se conectează la serverul socket...",
|
"connecting...": "Se conectează la serverul socket…",
|
||||||
"url": "URL | URLs",
|
"url": "URL | URLs",
|
||||||
"extra": "Suplimentar",
|
"extra": "Suplimentar",
|
||||||
"downStack": "Opriți & Coborâți",
|
"downStack": "Opriți & Inactiv",
|
||||||
"saveStackDraft": "Salvați",
|
"saveStackDraft": "Salvați",
|
||||||
"restartPolicyUnlessStopped": "Dacă nu este oprit",
|
"restartPolicyUnlessStopped": "Dacă nu este oprit",
|
||||||
"environmentVariable": "Variabila de mediu | Variabile de mediu",
|
"environmentVariable": "Variabila de mediu | Variabile de mediu",
|
||||||
@ -98,5 +98,35 @@
|
|||||||
"Please use this option carefully!": "Vă rugăm să utilizați această opțiune cu atenție!",
|
"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ă",
|
"Show update if available": "Afișează actualizarea dacă este disponibilă",
|
||||||
"disableauth.message1": "Sigur doriți să <strong>dezactivați autentificarea</strong>?",
|
"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."
|
"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.",
|
||||||
|
"newUpdate": "Actualizare nouă",
|
||||||
|
"dockgeAgent": "Agent Dockge | Agenții Dockge",
|
||||||
|
"currentEndpoint": "Actual",
|
||||||
|
"dockgeURL": "Dockge URL (de ex. http://127.0.0.1:5001)",
|
||||||
|
"agentOnline": "Online",
|
||||||
|
"agentOffline": "Offline",
|
||||||
|
"connecting": "Se conectează",
|
||||||
|
"addAgent": "Adaugă Agent",
|
||||||
|
"connect": "Conectează",
|
||||||
|
"agentRemovedSuccessfully": "Agentul a fost eliminat cu succes.",
|
||||||
|
"removeAgent": "Șterge Agentul",
|
||||||
|
"removeAgentMsg": "Ești sigur că vrei să elimini acest agent?",
|
||||||
|
"LongSyntaxNotSupported": "Sintaxa lungă nu este acceptată aici. Vă rugăm să utilizați editorul YAML.",
|
||||||
|
"agentAddedSuccessfully": "Agentul a fost adăugat cu succes.",
|
||||||
|
"Lost connection to the socket server. Reconnecting...": "S-a pierdut conexiunea cu serverul socket. Se reconectează...",
|
||||||
|
"Saved": "Salvat",
|
||||||
|
"Deployed": "Lansat",
|
||||||
|
"Deleted": "Șters",
|
||||||
|
"Updated": "Actualizat",
|
||||||
|
"Started": "Pornit",
|
||||||
|
"Stopped": "Oprit",
|
||||||
|
"Restarted": "Repornit",
|
||||||
|
"Downed": "Coborât",
|
||||||
|
"Switch to sh": "Schimbă la sh",
|
||||||
|
"terminal": "Terminal",
|
||||||
|
"CurrentHostname": "(Nesetat: Urmăriți numele de host curent)",
|
||||||
|
"New Container Name...": "Nume nou de container...",
|
||||||
|
"Network name...": "Numele rețelei...",
|
||||||
|
"Select a network...": "Selectați o rețea...",
|
||||||
|
"NoNetworksAvailable": "Nu există rețele disponibile. Mai întâi trebuie să adăugați rețele interne sau să activați rețele externe în partea dreaptă."
|
||||||
}
|
}
|
||||||
|
@ -1,15 +1,15 @@
|
|||||||
{
|
{
|
||||||
"languageName": "Русский",
|
"languageName": "Русский",
|
||||||
"Create your admin account": "Создайте учетку администратора",
|
"Create your admin account": "Создайте учетную запись администратора",
|
||||||
"authIncorrectCreds": "Неверный логин или пароль.",
|
"authIncorrectCreds": "Неверный логин или пароль.",
|
||||||
"PasswordsDoNotMatch": "Пароль не совпадает.",
|
"PasswordsDoNotMatch": "Пароли не совпадают.",
|
||||||
"Repeat Password": "Повторите пароль",
|
"Repeat Password": "Повторите пароль",
|
||||||
"Create": "Создать",
|
"Create": "Создать",
|
||||||
"signedInDisp": "Авторизован как",
|
"signedInDisp": "Авторизован как {0}",
|
||||||
"signedInDispDisabled": "Авторизация выключена.",
|
"signedInDispDisabled": "Авторизация выключена.",
|
||||||
"home": "Главная",
|
"home": "Главная",
|
||||||
"console": "Консоль",
|
"console": "Консоль",
|
||||||
"registry": "Registry",
|
"registry": "Реестр (Registry)",
|
||||||
"compose": "Compose",
|
"compose": "Compose",
|
||||||
"addFirstStackMsg": "Создайте свой первый стек!",
|
"addFirstStackMsg": "Создайте свой первый стек!",
|
||||||
"stackName": "Имя стека",
|
"stackName": "Имя стека",
|
||||||
@ -24,9 +24,9 @@
|
|||||||
"saveStackDraft": "Сохранить",
|
"saveStackDraft": "Сохранить",
|
||||||
"notAvailableShort": "Н/Д",
|
"notAvailableShort": "Н/Д",
|
||||||
"deleteStackMsg": "Вы уверены что хотите удалить этот стек?",
|
"deleteStackMsg": "Вы уверены что хотите удалить этот стек?",
|
||||||
"stackNotManagedByDockgeMsg": "Данный стек не обслуживается Dockge.",
|
"stackNotManagedByDockgeMsg": "Данный стек не управляется Dockge.",
|
||||||
"primaryHostname": "Имя хоста",
|
"primaryHostname": "Имя хоста",
|
||||||
"general": "Главное",
|
"general": "Основные",
|
||||||
"container": "Контейнер | Контейнеры",
|
"container": "Контейнер | Контейнеры",
|
||||||
"scanFolder": "Сканировать папку стеков",
|
"scanFolder": "Сканировать папку стеков",
|
||||||
"dockerImage": "Образ",
|
"dockerImage": "Образ",
|
||||||
@ -43,12 +43,12 @@
|
|||||||
"dependsOn": "Зависимость контейнера | Зависимости контейнера",
|
"dependsOn": "Зависимость контейнера | Зависимости контейнера",
|
||||||
"addListItem": "Добавить {0}",
|
"addListItem": "Добавить {0}",
|
||||||
"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": "Auto Get",
|
"autoGet": "Авто",
|
||||||
"add": "Добавить",
|
"add": "Добавить",
|
||||||
"Edit": "Изменить",
|
"Edit": "Изменить",
|
||||||
"applyToYAML": "Применить к YAML",
|
"applyToYAML": "Применить к YAML",
|
||||||
@ -62,16 +62,16 @@
|
|||||||
"New Password": "Новый пароль",
|
"New Password": "Новый пароль",
|
||||||
"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",
|
||||||
"Show update if available": "Показать обновление, если оно доступно",
|
"Show update if available": "Показать обновление, если оно доступно",
|
||||||
"Also check beta release": "Также проверьте бета-версию",
|
"Also check beta release": "Получать бета-версии",
|
||||||
"Remember me": "Запомнить меня",
|
"Remember me": "Запомнить меня",
|
||||||
"Login": "Логин",
|
"Login": "Логин",
|
||||||
"Username": "Имя пользователя",
|
"Username": "Имя пользователя",
|
||||||
@ -80,15 +80,37 @@
|
|||||||
"Logout": "Выйти",
|
"Logout": "Выйти",
|
||||||
"Lowercase only": "Только нижний регистр",
|
"Lowercase only": "Только нижний регистр",
|
||||||
"Convert to Compose": "Преобразовать в Compose",
|
"Convert to Compose": "Преобразовать в Compose",
|
||||||
"Docker Run": "Запустить Docker",
|
"Docker Run": "Docker Run",
|
||||||
"active": "активный",
|
"active": "акт.",
|
||||||
"exited": "завершенный",
|
"exited": "ост.",
|
||||||
"inactive": "неактинвый",
|
"inactive": "неакт.",
|
||||||
"Appearance": "Внешний вид",
|
"Appearance": "Внешний вид",
|
||||||
"Security": "Безопасность",
|
"Security": "Безопасность",
|
||||||
"About": "О продукте",
|
"About": "О продукте",
|
||||||
"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-адрес | URL-адреса",
|
||||||
|
"extra": "Дополнительно",
|
||||||
|
"reverseProxyMsg2": "Проверьте, как настроить его для WebSocket",
|
||||||
|
"connecting...": "Подключение к сокет-серверу…",
|
||||||
|
"newUpdate": "Доступно обновление",
|
||||||
|
"currentEndpoint": "Текущий",
|
||||||
|
"agentOnline": "В сети",
|
||||||
|
"agentOffline": "Не в сети",
|
||||||
|
"connecting": "Подключение",
|
||||||
|
"connect": "Подключить",
|
||||||
|
"addAgent": "Добавить Агента",
|
||||||
|
"agentAddedSuccessfully": "Агент успешно добавлен.",
|
||||||
|
"removeAgent": "Удалить агента",
|
||||||
|
"removeAgentMsg": "Вы уверены, что хотите удалить этого агента?",
|
||||||
|
"dockgeAgent": "Агент Dockge | Агенты Dockge",
|
||||||
|
"dockgeURL": "URL-адрес Dockge (например: http://127.0.0.1:5001)",
|
||||||
|
"agentRemovedSuccessfully": "Агент успешно удален.",
|
||||||
|
"LongSyntaxNotSupported": "Длинный синтаксис здесь не поддерживается. Пожалуйста, используйте редактор YAML."
|
||||||
}
|
}
|
||||||
|
@ -90,5 +90,27 @@
|
|||||||
"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 & Deaktiviraj",
|
||||||
|
"connecting...": "Povezovanje s strežnikom…",
|
||||||
|
"reverseProxyMsg1": "Uporabljate obratni proxy?",
|
||||||
|
"extra": "Dodatno",
|
||||||
|
"reconnecting...": "Ponovna povezava …",
|
||||||
|
"newUpdate": "Nova posodobitev",
|
||||||
|
"reverseProxyMsg2": "Preverite, kako ga konfigurirati za WebSocket",
|
||||||
|
"Cannot connect to the socket server.": "Ni mogoče vzpostaviti povezave s strežnikom vtičnic.",
|
||||||
|
"url": "URL | URL-ji",
|
||||||
|
"currentEndpoint": "Trenutni",
|
||||||
|
"dockgeURL": "Dockge URL (npr. http://127.0.0.1:5001)",
|
||||||
|
"agentOnline": "Aktivno",
|
||||||
|
"agentOffline": "Neaktivno",
|
||||||
|
"connecting": "Povezujem",
|
||||||
|
"connect": "Poveži",
|
||||||
|
"addAgent": "Dodaj agenta",
|
||||||
|
"dockgeAgent": "Dockge agent | Dockge agenti",
|
||||||
|
"agentAddedSuccessfully": "Agent dodan uspešno.",
|
||||||
|
"agentRemovedSuccessfully": "Agent uspešno odstranjen.",
|
||||||
|
"removeAgent": "Odstrani agent",
|
||||||
|
"removeAgentMsg": "Ali ste prepričani, da želite odstraniti agenta?",
|
||||||
|
"LongSyntaxNotSupported": "Long syntax-a ni podprta tukaj. Prosim uporabite YAML urejevalnik."
|
||||||
}
|
}
|
||||||
|
@ -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": "Stop",
|
"stopStack": "Stoppa",
|
||||||
"restartStack": "Starta om",
|
"restartStack": "Starta om",
|
||||||
"updateStack": "Uppdatera",
|
"updateStack": "Uppdatera",
|
||||||
"startStack": "Starta",
|
"startStack": "Starta",
|
||||||
"downStack": "Stop & Ner",
|
"downStack": "Stoppa & Inaktivera",
|
||||||
"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": "Scanna Stackfolder",
|
"scanFolder": "Skanna Stackmapp",
|
||||||
"dockerImage": "Bild",
|
"dockerImage": "Avbild",
|
||||||
"restartPolicyUnlessStopped": "Om inte stoppas",
|
"restartPolicyUnlessStopped": "Om inte stoppad",
|
||||||
"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 senarion <stong>när du ska implementera tredjeparts autentisering</strong> framör Dockge som Cloudflare Access, Authelia eller andra autentiseringsmekanismer.",
|
"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.",
|
||||||
"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ändaren",
|
"Current User": "Nuvarande användare",
|
||||||
"Change Password": "Byt lösenord",
|
"Change Password": "Ändra 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 betaversionen",
|
"Also check beta release": "Kontrollera även betaversioner",
|
||||||
"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 Run",
|
"Docker Run": "Docker kör",
|
||||||
"active": "aktiv",
|
"active": "aktiv",
|
||||||
"exited": "avslutad",
|
"exited": "avslutad",
|
||||||
"inactive": "inaktiv",
|
"inactive": "inaktiv",
|
||||||
@ -89,7 +89,44 @@
|
|||||||
"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…",
|
||||||
|
"newUpdate": "Ny uppdatering",
|
||||||
|
"currentEndpoint": "Nuvarande",
|
||||||
|
"dockgeURL": "Dockge URL (ex. http://127.0.0.1:5001)",
|
||||||
|
"agentOnline": "On-line",
|
||||||
|
"agentOffline": "Off-line",
|
||||||
|
"connecting": "Ansluter",
|
||||||
|
"connect": "Ansluten",
|
||||||
|
"addAgent": "Lägg till agent",
|
||||||
|
"agentRemovedSuccessfully": "Agent borttagen.",
|
||||||
|
"removeAgent": "Ta bort agent",
|
||||||
|
"removeAgentMsg": "Är du säker att du vill ta bort denna agent?",
|
||||||
|
"dockgeAgent": "Dockge agenter | Dockge agenter",
|
||||||
|
"agentAddedSuccessfully": "Agent tillagd.",
|
||||||
|
"LongSyntaxNotSupported": "Lång syntax stöds inte här. Använd YAML-redigeraren.",
|
||||||
|
"Lost connection to the socket server. Reconnecting...": "Tappade anslutning till socket-server. Återansluter...",
|
||||||
|
"Saved": "Sparad",
|
||||||
|
"Deployed": "Uppsatt",
|
||||||
|
"Deleted": "Borttagen",
|
||||||
|
"Updated": "Uppdaterad",
|
||||||
|
"Started": "Startad",
|
||||||
|
"Stopped": "Stoppad",
|
||||||
|
"Restarted": "Omstartad",
|
||||||
|
"Downed": "Nedstängd",
|
||||||
|
"Switch to sh": "Byt till sh",
|
||||||
|
"terminal": "Terminal",
|
||||||
|
"CurrentHostname": "(Ej angedd: Följ nuvarande värdnamn)",
|
||||||
|
"New Container Name...": "Nytt kontainernamn...",
|
||||||
|
"Network name...": "Nätverksnamn...",
|
||||||
|
"Select a network...": "Välj ett nätverk...",
|
||||||
|
"NoNetworksAvailable": "Inga nätverk tillgängliga. Du måste lägga till interna nätverk eller aktivera externa nätverk på högra sidan först."
|
||||||
}
|
}
|
||||||
|
@ -1,17 +1,17 @@
|
|||||||
{
|
{
|
||||||
"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": "ปิดใช้งาน Auth",
|
"signedInDispDisabled": "ปิดใช้งาน Auth",
|
||||||
"home": "หน้าหลักe",
|
"home": "หน้าหลัก",
|
||||||
"console": "คอนโซล",
|
"console": "คอนโซล",
|
||||||
"registry": "Registry",
|
"registry": "Registry",
|
||||||
"compose": "Compose",
|
"compose": "Compose",
|
||||||
"addFirstStackMsg": "Compose stack แรกของคุณ",
|
"addFirstStackMsg": "Compose stack แรกของคุณ!",
|
||||||
"stackName": "ชื่อ Stack",
|
"stackName": "ชื่อ Stack",
|
||||||
"deployStack": "ปรับใช้",
|
"deployStack": "ปรับใช้",
|
||||||
"deleteStack": "ลบ",
|
"deleteStack": "ลบ",
|
||||||
@ -19,7 +19,7 @@
|
|||||||
"restartStack": "เริ่มใหม่",
|
"restartStack": "เริ่มใหม่",
|
||||||
"updateStack": "อัปเดต",
|
"updateStack": "อัปเดต",
|
||||||
"startStack": "เริ่มต้น",
|
"startStack": "เริ่มต้น",
|
||||||
"downStack": "หยุดและปิด",
|
"downStack": "หยุดการทำงาน",
|
||||||
"editStack": "แก้ไข",
|
"editStack": "แก้ไข",
|
||||||
"discardStack": "ยกเลิก",
|
"discardStack": "ยกเลิก",
|
||||||
"saveStackDraft": "บันทึก",
|
"saveStackDraft": "บันทึก",
|
||||||
@ -91,5 +91,26 @@
|
|||||||
"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...": "กำลังเชื่อมต่อใหม่…",
|
||||||
|
"newUpdate": "อัปเดตใหม่",
|
||||||
|
"dockgeAgent": "เอเย่นต์ Dockge | เอเย่นต์ Dockge",
|
||||||
|
"currentEndpoint": "ปัจุบัน",
|
||||||
|
"agentOnline": "ออนไลน์",
|
||||||
|
"agentOffline": "ออฟไลน์",
|
||||||
|
"connecting": "กำลังเชื่อมต่อ",
|
||||||
|
"connect": "เชื่อมต่อ",
|
||||||
|
"addAgent": "เพิ่มเอเย่นต์",
|
||||||
|
"agentAddedSuccessfully": "เพิ่มเอเย่นต์สำเร็จ",
|
||||||
|
"agentRemovedSuccessfully": "ลบเอเย่นต์สำเร็จ",
|
||||||
|
"removeAgent": "ลบเอเย่นต์",
|
||||||
|
"removeAgentMsg": "คุณแน่ใจหรือไม่ที่จะลบเอเย่นต์นี้?",
|
||||||
|
"dockgeURL": "ลิ้งก์ Dockge (เช่น http://127.0.0.1:5001)",
|
||||||
|
"LongSyntaxNotSupported": "Syntax แบบยาสไม่รองรับที่นี่ กรุณาใช้ตัวแก้ไข YAML"
|
||||||
}
|
}
|
@ -7,22 +7,22 @@
|
|||||||
"Create": "Oluştur",
|
"Create": "Oluştur",
|
||||||
"signedInDisp": "{0} olarak oturum açıldı",
|
"signedInDisp": "{0} olarak oturum açıldı",
|
||||||
"signedInDispDisabled": "Yetkilendirme Devre Dışı.",
|
"signedInDispDisabled": "Yetkilendirme Devre Dışı.",
|
||||||
"home": "Anasayfa",
|
"home": "Ana Sayfa",
|
||||||
"console": "Konsol",
|
"console": "Konsol",
|
||||||
"registry": "Kayıt Defteri",
|
"registry": "Kayıt Defteri",
|
||||||
"compose": "Compose",
|
"compose": "Oluştur",
|
||||||
"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ğıt",
|
||||||
"deleteStack": "Sil",
|
"deleteStack": "Sil",
|
||||||
"stopStack": "Dudur",
|
"stopStack": "Durdur",
|
||||||
"restartStack": "Yeniden Başlat",
|
"restartStack": "Yeniden Başlat",
|
||||||
"updateStack": "Güncelle",
|
"updateStack": "Güncelle",
|
||||||
"startStack": "Başlat",
|
"startStack": "Başlat",
|
||||||
"editStack": "Düzenle",
|
"editStack": "Düzenle",
|
||||||
"discardStack": "Çıkar",
|
"discardStack": "İptal Et",
|
||||||
"saveStackDraft": "Kaydet",
|
"saveStackDraft": "Kaydet",
|
||||||
"notAvailableShort": "N/A",
|
"notAvailableShort": "YOK",
|
||||||
"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ı",
|
||||||
@ -45,19 +45,19 @@
|
|||||||
"deleteContainer": "Sil",
|
"deleteContainer": "Sil",
|
||||||
"addContainer": "Konteyner Ekle",
|
"addContainer": "Konteyner Ekle",
|
||||||
"addNetwork": "Ağ Ekle",
|
"addNetwork": "Ağ Ekle",
|
||||||
"disableauth.message1": "<strong>Kimlik doğrulamayı devre dışı</strong> bırakmak istediğinizden emin misiniz?",
|
"disableauth.message1": "<strong>Kimlik doğrulamayı devre dışı bırakmak</strong> istediğinizden emin misiniz?",
|
||||||
"disableauth.message2": "Cloudflare Access, Authelia veya diğer kimlik doğrulama mekanizmaları gibi Uptime Kuma'nın önünde <strong>üçüncü taraf kimlik doğrulaması uygulamak</strong> istediğiniz senaryolar için tasarlanmıştır.",
|
"disableauth.message2": "Cloudflare Access, Authelia veya diğer kimlik doğrulama mekanizmaları Dockge önünde <strong>üçüncü taraf kimlik doğrulaması uygulamak</strong> istediğiniz senaryolar için tasarlanmıştır.",
|
||||||
"passwordNotMatchMsg": "Tekrarlanan parola eşleşmiyor.",
|
"passwordNotMatchMsg": "Tekrarlanan parola eşleşmiyor.",
|
||||||
"autoGet": "Otomatik Al",
|
"autoGet": "Otomatik Al",
|
||||||
"add": "Ekle",
|
"add": "Ekle",
|
||||||
"Edit": "Düzenle",
|
"Edit": "Düzenle",
|
||||||
"applyToYAML": "YAML'ye uygulayın",
|
"applyToYAML": "YAML dosyasına uygula",
|
||||||
"createExternalNetwork": "Oluştur",
|
"createExternalNetwork": "Oluştur",
|
||||||
"addInternalNetwork": "Ekle",
|
"addInternalNetwork": "Ekle",
|
||||||
"Save": "Kaydet",
|
"Save": "Kaydet",
|
||||||
"Language": "Dil",
|
"Language": "Dil",
|
||||||
"Current User": "Mevcut Kullanıcı",
|
"Current User": "Mevcut Kullanıcı",
|
||||||
"Change Password": "Mevcut Parola",
|
"Change Password": "Parolayı Değiştir",
|
||||||
"Current Password": "Mevcut Parola",
|
"Current Password": "Mevcut Parola",
|
||||||
"New Password": "Yeni Parola",
|
"New Password": "Yeni Parola",
|
||||||
"Repeat New Password": "Yeni Parolayı Tekrarla",
|
"Repeat New Password": "Yeni Parolayı Tekrarla",
|
||||||
@ -66,31 +66,31 @@
|
|||||||
"Please use this option carefully!": "Lütfen bu seçeneği dikkatli kullanın!",
|
"Please use this option carefully!": "Lütfen bu seçeneği dikkatli kullanın!",
|
||||||
"Enable Auth": "Kimlik Doğrulamayı Etkinleştir",
|
"Enable Auth": "Kimlik Doğrulamayı Etkinleştir",
|
||||||
"Disable Auth": "Kimlik Doğrulamayı Devre Dışı Bırak",
|
"Disable Auth": "Kimlik Doğrulamayı Devre Dışı Bırak",
|
||||||
"I understand, please disable": "Anlıyorum, lütfen devre dışı bırakın",
|
"I understand, please disable": "Anlıyorum, lütfen devre dışı bırak",
|
||||||
"Leave": "Ayrıl",
|
"Leave": "Ayrıl",
|
||||||
"Frontend Version": "Frontend Versiyon",
|
"Frontend Version": "Ön Uç Sürümü",
|
||||||
"Check Update On GitHub": "GitHub'da Güncellemeyi Kontrol Edin",
|
"Check Update On GitHub": "GitHub'da Güncellemeyi Kontrol Edin",
|
||||||
"Show update if available": "Varsa güncellemeyi göster",
|
"Show update if available": "Varsa güncellemeyi göster",
|
||||||
"Also check beta release": "Ayrıca beta sürümünü kontrol edin",
|
"Also check beta release": "Ayrıca beta sürümünü kontrol edin",
|
||||||
"Remember me": "Beni Hatırla",
|
"Remember me": "Beni hatırla",
|
||||||
"Login": "Oturum Aç",
|
"Login": "Oturum Aç",
|
||||||
"Username": "Kullanıcı Adı",
|
"Username": "Kullanıcı Adı",
|
||||||
"Password": "Parola",
|
"Password": "Parola",
|
||||||
"Settings": "Ayarlar",
|
"Settings": "Ayarlar",
|
||||||
"Logout": "Oturumu Kapat",
|
"Logout": "Oturumu Kapat",
|
||||||
"Lowercase only": "Yalnızca küçük harf",
|
"Lowercase only": "Yalnızca küçük harf",
|
||||||
"Convert to Compose": "Compose'a Dönüştür",
|
"Convert to Compose": "Compose dosyasına dönüştür",
|
||||||
"Docker Run": "Docker Run",
|
"Docker Run": "Docker Run",
|
||||||
"active": "aktif",
|
"active": "etkin",
|
||||||
"exited": "çıkış yaptı",
|
"exited": "çıktı",
|
||||||
"inactive": "aktif değil",
|
"inactive": "devre dışı",
|
||||||
"Appearance": "Görünüm",
|
"Appearance": "Görünüm",
|
||||||
"Security": "Güvenlik",
|
"Security": "Güvenlik",
|
||||||
"About": "Hakkında",
|
"About": "Hakkında",
|
||||||
"Allowed commands:": "İzin verilen komutlar:",
|
"Allowed commands:": "İzin verilen komutlar:",
|
||||||
"Internal Networks": "İç Ağlar",
|
"Internal Networks": "Dahili Ağlar",
|
||||||
"External Networks": "Dış Ağlar",
|
"External Networks": "Harici Ağlar",
|
||||||
"No External Networks": "Dış Ağ Yok",
|
"No External Networks": "Harici Ağ Yok",
|
||||||
"extra": "Ekstra",
|
"extra": "Ekstra",
|
||||||
"reverseProxyMsg1": "Ters Proxy mi kullanıyorsunuz?",
|
"reverseProxyMsg1": "Ters Proxy mi kullanıyorsunuz?",
|
||||||
"reverseProxyMsg2": "WebSocket için nasıl yapılandırma yapılacağını kontrol edin",
|
"reverseProxyMsg2": "WebSocket için nasıl yapılandırma yapılacağını kontrol edin",
|
||||||
@ -98,5 +98,35 @@
|
|||||||
"connecting...": "Soket sunucusuna bağlanıyor…",
|
"connecting...": "Soket sunucusuna bağlanıyor…",
|
||||||
"url": "URL | URL’ler",
|
"url": "URL | URL’ler",
|
||||||
"Cannot connect to the socket server.": "Soket sunucusuna bağlanılamıyor.",
|
"Cannot connect to the socket server.": "Soket sunucusuna bağlanılamıyor.",
|
||||||
"downStack": "Durdur & Kapat"
|
"downStack": "Durdur ve Devre Dışı Bırak",
|
||||||
|
"newUpdate": "Yeni Güncelleme",
|
||||||
|
"dockgeAgent": "Dockge Aracısı | Dockge Aracıları",
|
||||||
|
"currentEndpoint": "Varsayılan",
|
||||||
|
"dockgeURL": "Dockge URL (ör. http://127.0.0.1:5001)",
|
||||||
|
"agentOnline": "Çevrimiçi",
|
||||||
|
"agentOffline": "Çevrimdışı",
|
||||||
|
"connecting": "Bağlanıyor",
|
||||||
|
"connect": "Bağlan",
|
||||||
|
"addAgent": "Aracı Ekle",
|
||||||
|
"agentAddedSuccessfully": "Aracı başarıyla eklendi.",
|
||||||
|
"agentRemovedSuccessfully": "Aracı başarıyla kaldırıldı.",
|
||||||
|
"removeAgent": "Aracıyı Kaldır",
|
||||||
|
"removeAgentMsg": "Bu aracıyı kaldırmak istediğinize emin misiniz?",
|
||||||
|
"LongSyntaxNotSupported": "Uzun syntax burada desteklenmiyor. Lütfen YAML editörünü kullanın.",
|
||||||
|
"Lost connection to the socket server. Reconnecting...": "Soket sunucusuna bağlantı kesildi. Yeniden bağlanılıyor...",
|
||||||
|
"NoNetworksAvailable": "Kullanılabilir ağ yok. Önce dahili ağları eklemeniz veya sağ tarafta harici ağları etkinleştirmeniz gerekir.",
|
||||||
|
"Saved": "Kayıtlı",
|
||||||
|
"Deployed": "Deploy Edildi",
|
||||||
|
"Deleted": "Silindi",
|
||||||
|
"Updated": "Güncellendi",
|
||||||
|
"Started": "Başladı",
|
||||||
|
"Stopped": "Durdu",
|
||||||
|
"Restarted": "Yeniden başlatıldı",
|
||||||
|
"Downed": "Düştü",
|
||||||
|
"Switch to sh": "sh'ye çevir",
|
||||||
|
"terminal": "Terminal",
|
||||||
|
"CurrentHostname": "(Ayarlanmamış: Mevcut hostname'i takip et)",
|
||||||
|
"New Container Name...": "Yeni Konteyner Adı...",
|
||||||
|
"Network name...": "Ağ adı...",
|
||||||
|
"Select a network...": "Bir ağ seçin..."
|
||||||
}
|
}
|
||||||
|
@ -80,7 +80,7 @@
|
|||||||
"Logout": "Вийти",
|
"Logout": "Вийти",
|
||||||
"Lowercase only": "Тільки нижній регістр",
|
"Lowercase only": "Тільки нижній регістр",
|
||||||
"Convert to Compose": "Конвертувати в Compose",
|
"Convert to Compose": "Конвертувати в Compose",
|
||||||
"Docker Run": "Запустити Docker",
|
"Docker Run": "Docker Run",
|
||||||
"active": "активно",
|
"active": "активно",
|
||||||
"exited": "завершено",
|
"exited": "завершено",
|
||||||
"inactive": "неактивно",
|
"inactive": "неактивно",
|
||||||
@ -92,11 +92,41 @@
|
|||||||
"External Networks": "Зовнішні мережі",
|
"External Networks": "Зовнішні мережі",
|
||||||
"No External Networks": "Немає зовнішніх мереж",
|
"No External Networks": "Немає зовнішніх мереж",
|
||||||
"downStack": "Зупинити і вимкнути",
|
"downStack": "Зупинити і вимкнути",
|
||||||
"reverseProxyMsg1": "Використовувати зворотній проксі?",
|
"reverseProxyMsg1": "Використовуєте зворотній проксі?",
|
||||||
"Cannot connect to the socket server.": "Не вдається підключитися до сервера сокетів.",
|
"Cannot connect to the socket server.": "Не вдається підключитися до сервера сокетів.",
|
||||||
"reconnecting...": "Повторне підключення…",
|
"reconnecting...": "Повторне підключення…",
|
||||||
"connecting...": "Підключення до сервера сокетів…",
|
"connecting...": "Підключення до сервера сокетів…",
|
||||||
"url": "URL-адреса | URL-адреси",
|
"url": "URL-адреса | URL-адреси",
|
||||||
"reverseProxyMsg2": "Перевірте, як налаштувати його для WebSocket",
|
"reverseProxyMsg2": "Перевірте, як налаштувати його для WebSocket",
|
||||||
"extra": "Додатково"
|
"extra": "Додатково",
|
||||||
|
"newUpdate": "Оновлення",
|
||||||
|
"currentEndpoint": "Поточний",
|
||||||
|
"agentOnline": "Онлайн",
|
||||||
|
"agentOffline": "Офлайн",
|
||||||
|
"connecting": "Підключення",
|
||||||
|
"connect": "Підключитися",
|
||||||
|
"addAgent": "Додати агент",
|
||||||
|
"removeAgent": "Видалити агент",
|
||||||
|
"dockgeAgent": "Dockge-агент | Dockge-агенти",
|
||||||
|
"dockgeURL": "Dockge URL (напр. http://127.0.0.1:5001)",
|
||||||
|
"agentRemovedSuccessfully": "Агент успішно видалено.",
|
||||||
|
"agentAddedSuccessfully": "Агент успішно додано.",
|
||||||
|
"removeAgentMsg": "Ви впевнені, що хочете видалити цей агент?",
|
||||||
|
"LongSyntaxNotSupported": "Довгий синтаксис тут не підтримується. Будь ласка, використовуйте редактор YAML.",
|
||||||
|
"Saved": "Збережено",
|
||||||
|
"Deployed": "Розгорнуто",
|
||||||
|
"Deleted": "Видалено",
|
||||||
|
"Updated": "Оновлено",
|
||||||
|
"Started": "Запущено",
|
||||||
|
"Stopped": "Зупинено",
|
||||||
|
"Downed": "Вимкнено",
|
||||||
|
"Switch to sh": "Перемкнути на sh",
|
||||||
|
"terminal": "Термінал",
|
||||||
|
"New Container Name...": "Нова назва контейнера...",
|
||||||
|
"Network name...": "Назва мережі...",
|
||||||
|
"Select a network...": "Вибрати мережу...",
|
||||||
|
"Lost connection to the socket server. Reconnecting...": "Втрачено зв'язок з сервером сокетів. Повторне підключення...",
|
||||||
|
"Restarted": "Перезапущено",
|
||||||
|
"CurrentHostname": "(Не встановлено: використовувати поточну назву хосту)",
|
||||||
|
"NoNetworksAvailable": "Немає доступних мереж. Спочатку потрібно додати внутрішні мережі або увімкнути зовнішні мережі в правій частині."
|
||||||
}
|
}
|
||||||
|
@ -97,6 +97,20 @@
|
|||||||
"connecting...": "ساکٹ سرور سے منسلک ہو رہا ہے…",
|
"connecting...": "ساکٹ سرور سے منسلک ہو رہا ہے…",
|
||||||
"url": "یو آر ایل | یو آر ایل",
|
"url": "یو آر ایل | یو آر ایل",
|
||||||
"extra": "اضافی",
|
"extra": "اضافی",
|
||||||
"downStack": "اسٹاپ اینڈ ڈاؤن",
|
"downStack": "روکیں اور غیر فعال",
|
||||||
"reverseProxyMsg2": "اسے WebSocket کے لیے ترتیب دینے کا طریقہ چیک کریں"
|
"reverseProxyMsg2": "اسے WebSocket کے لیے ترتیب دینے کا طریقہ چیک کریں",
|
||||||
|
"newUpdate": "نئی تازہ کاری",
|
||||||
|
"dockgeAgent": "ڈاکج ایجنٹ | ڈاکج ایجنٹس",
|
||||||
|
"currentEndpoint": "کرنٹ",
|
||||||
|
"dockgeURL": "Dockge URL (جیسے http://127.0.0.1:5001)",
|
||||||
|
"agentOnline": "آن لائن",
|
||||||
|
"agentOffline": "آف لائن",
|
||||||
|
"connecting": "جڑ رہا ہے",
|
||||||
|
"connect": "جڑیں",
|
||||||
|
"addAgent": "ایجنٹ شامل کریں",
|
||||||
|
"agentAddedSuccessfully": "ایجنٹ کامیابی کے ساتھ شامل ہو گیا۔",
|
||||||
|
"agentRemovedSuccessfully": "ایجنٹ کو کامیابی سے ہٹا دیا گیا۔",
|
||||||
|
"removeAgent": "ایجنٹ کو ہٹا دیں",
|
||||||
|
"removeAgentMsg": "کیا آپ واقعی اس ایجنٹ کو ہٹانا چاہتے ہیں؟",
|
||||||
|
"LongSyntaxNotSupported": "لمبا نحو یہاں تعاون یافتہ نہیں ہے۔ براہ کرم YAML ایڈیٹر استعمال کریں۔"
|
||||||
}
|
}
|
||||||
|
115
frontend/src/lang/vi.json
Normal file
115
frontend/src/lang/vi.json
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
{
|
||||||
|
"authIncorrectCreds": "Sai tên người dùng hoặc mật khẩu.",
|
||||||
|
"PasswordsDoNotMatch": "Mật khẩu không khớp.",
|
||||||
|
"Repeat Password": "Lặp Lại Mật Khẩu",
|
||||||
|
"Create": "Tạo",
|
||||||
|
"signedInDisp": "Đã đăng nhập với tư cách {0}",
|
||||||
|
"home": "Trang chủ",
|
||||||
|
"console": "Console",
|
||||||
|
"compose": "Compose",
|
||||||
|
"registry": "Registry",
|
||||||
|
"stackName": "Tên Stack",
|
||||||
|
"deployStack": "Triển khai",
|
||||||
|
"deleteStack": "Xoá",
|
||||||
|
"stopStack": "Dừng",
|
||||||
|
"restartStack": "Khởi động lại",
|
||||||
|
"signedInDispDisabled": "Đã Tắt Xác Thực Đăng Nhập.",
|
||||||
|
"startStack": "Bắt đầu",
|
||||||
|
"downStack": "Dừng & Ngưng hoạt động",
|
||||||
|
"editStack": "Chỉnh sửa",
|
||||||
|
"saveStackDraft": "Lưu",
|
||||||
|
"notAvailableShort": "N/A",
|
||||||
|
"deleteStackMsg": "Bạn có chắc chắn muốn xoá stack này?",
|
||||||
|
"primaryHostname": "Tên Host Chính",
|
||||||
|
"scanFolder": "Quét Thư Mục Stack",
|
||||||
|
"restartPolicyAlways": "Luôn Luôn",
|
||||||
|
"restartPolicyOnFailure": "Khi Có Lỗi",
|
||||||
|
"restartPolicyNo": "Không",
|
||||||
|
"environmentVariable": "Biến Môi Trường | Các Biến Môi Trường",
|
||||||
|
"restartPolicy": "Chính Sách Khởi Động Lại",
|
||||||
|
"containerName": "Tên Container",
|
||||||
|
"port": "Cổng | Cổng",
|
||||||
|
"addListItem": "Thêm {0}",
|
||||||
|
"deleteContainer": "Xoá",
|
||||||
|
"addContainer": "Thêm Container",
|
||||||
|
"addNetwork": "Thêm Mạng",
|
||||||
|
"passwordNotMatchMsg": "Mật khẩu nhập lại không khớp.",
|
||||||
|
"autoGet": "Tự Động Lấy",
|
||||||
|
"add": "Thêm",
|
||||||
|
"Edit": "Chỉnh sửa",
|
||||||
|
"applyToYAML": "Áp dụng cho YAML",
|
||||||
|
"createExternalNetwork": "Tạo",
|
||||||
|
"addInternalNetwork": "Thêm",
|
||||||
|
"Save": "Lưu",
|
||||||
|
"Language": "Ngôn ngữ",
|
||||||
|
"Current User": "Người Dùng Hiện Tại",
|
||||||
|
"Change Password": "Đổi Mật Khẩu",
|
||||||
|
"Current Password": "Mật Khẩu Hiện Tại",
|
||||||
|
"New Password": "Mật Khẩu Mới",
|
||||||
|
"Repeat New Password": "Nhập Lại Mật Khẩu Mới",
|
||||||
|
"Update Password": "Cập Nhật Mật Khẩu",
|
||||||
|
"Advanced": "Nâng cao",
|
||||||
|
"Please use this option carefully!": "Vui lòng sử dụng tuỳ chọn này cẩn thận!",
|
||||||
|
"Enable Auth": "Kích Hoạt Xác Thực Đăng Nhập",
|
||||||
|
"Disable Auth": "Vô Hiệu Xác Thực Đăng Nhập",
|
||||||
|
"I understand, please disable": "Tôi hiểu, vui lòng vô hiệu",
|
||||||
|
"Leave": "Rời",
|
||||||
|
"Frontend Version": "Phiên Bản Giao Diện Người Dùng",
|
||||||
|
"Check Update On GitHub": "Kiểm Tra Cập Nhật Trên Github",
|
||||||
|
"Also check beta release": "Kiểm tra cả bản phát hành beta",
|
||||||
|
"Remember me": "Ghi nhớ tôi",
|
||||||
|
"Login": "Đăng nhập",
|
||||||
|
"Username": "Tên người dùng",
|
||||||
|
"Password": "Mật khẩu",
|
||||||
|
"Settings": "Cài đặt",
|
||||||
|
"Logout": "Đăng xuất",
|
||||||
|
"Lowercase only": "Chỉ viết thường",
|
||||||
|
"Convert to Compose": "Chuyển đổi sang Compose",
|
||||||
|
"Docker Run": "Chạy Docker",
|
||||||
|
"active": "hoạt động",
|
||||||
|
"exited": "đã thoát",
|
||||||
|
"inactive": "không hoạt động",
|
||||||
|
"Security": "Bảo Mật",
|
||||||
|
"Appearance": "Giao Diện",
|
||||||
|
"About": "Về",
|
||||||
|
"Allowed commands:": "Các lệnh được cho phép:",
|
||||||
|
"Internal Networks": "Mạng Nội Bộ",
|
||||||
|
"External Networks": "Mạng Ngoại Vi",
|
||||||
|
"No External Networks": "Không Có Mạng Ngoại Vi",
|
||||||
|
"reverseProxyMsg1": "Đang sử dụng Reverse Proxy?",
|
||||||
|
"reverseProxyMsg2": "Xem cách để cấu hình nó cho WebSocket",
|
||||||
|
"Cannot connect to the socket server.": "Không thể kết nối tới máy chủ socket.",
|
||||||
|
"reconnecting...": "Đang kết nối lại…",
|
||||||
|
"connecting...": "Đang kết nối tới máy chủ socket…",
|
||||||
|
"url": "URL",
|
||||||
|
"extra": "Bổ sung",
|
||||||
|
"newUpdate": "Cập Nhật Mới",
|
||||||
|
"dockgeAgent": "Dockge Agent",
|
||||||
|
"currentEndpoint": "Đang sử dụng",
|
||||||
|
"dockgeURL": "URL của Dockge (v.d. http://127.0.0.1:5001)",
|
||||||
|
"agentOnline": "Trực tuyến",
|
||||||
|
"agentOffline": "Ngoại tuyến",
|
||||||
|
"connecting": "Đang kết nối",
|
||||||
|
"connect": "Kết nối",
|
||||||
|
"addAgent": "Thêm Agent",
|
||||||
|
"agentAddedSuccessfully": "Agent đã được thêm thành công.",
|
||||||
|
"agentRemovedSuccessfully": "Agent đã được xoá thành công.",
|
||||||
|
"removeAgent": "Xoá Agent",
|
||||||
|
"removeAgentMsg": "Bạn có chắc chắn muốn xoá agent này?",
|
||||||
|
"languageName": "Tiếng Việt",
|
||||||
|
"Create your admin account": "Tạo tài khoản admin của bạn",
|
||||||
|
"addFirstStackMsg": "Tạo stack đầu tiên của bạn!",
|
||||||
|
"volume": "Volume | Volume",
|
||||||
|
"updateStack": "Cập nhật",
|
||||||
|
"network": "Mạng | Mạng",
|
||||||
|
"discardStack": "Huỷ",
|
||||||
|
"stackNotManagedByDockgeMsg": "Stack này không được quản lý bởi Dockge.",
|
||||||
|
"dependsOn": "Container Phụ Thuộc | Các Container Phụ Thuộc",
|
||||||
|
"general": "Tổng Quan",
|
||||||
|
"disableauth.message1": "Bạn có chắc chắn muốn <strong>tắt xác thực đăng nhập</strong>?",
|
||||||
|
"container": "Container",
|
||||||
|
"disableauth.message2": "Nó được thiết kế trong hoàn cảnh <strong>mà bạn dự định triển khai xác thực đăng nhập bên thứ ba</strong> trước Dockge như là Cloudflare Access, Authelia hay các phương thức xác minh đăng nhập khác.",
|
||||||
|
"dockerImage": "Image",
|
||||||
|
"Show update if available": "Hiển thị cập nhật nếu có",
|
||||||
|
"restartPolicyUnlessStopped": "Trừ Khi Dừng Lại"
|
||||||
|
}
|
@ -98,5 +98,35 @@
|
|||||||
"Cannot connect to the socket server.": "无法连接到socket服务器。",
|
"Cannot connect to the socket server.": "无法连接到socket服务器。",
|
||||||
"url": "网址 | 网址",
|
"url": "网址 | 网址",
|
||||||
"extra": "额外",
|
"extra": "额外",
|
||||||
"downStack": "停止并删除"
|
"downStack": "停止并置于非活动状态",
|
||||||
|
"newUpdate": "新版本",
|
||||||
|
"dockgeURL": "Dockge地址 (例如 http://127.0.0.1:5001)",
|
||||||
|
"agentOnline": "在线",
|
||||||
|
"agentOffline": "离线",
|
||||||
|
"connecting": "连接中",
|
||||||
|
"connect": "连接",
|
||||||
|
"dockgeAgent": "Dockge代理",
|
||||||
|
"currentEndpoint": "当前",
|
||||||
|
"addAgent": "添加代理",
|
||||||
|
"agentRemovedSuccessfully": "代理移除成功。",
|
||||||
|
"removeAgent": "移除代理",
|
||||||
|
"removeAgentMsg": "您确定要移除此代理?",
|
||||||
|
"agentAddedSuccessfully": "代理添加成功。",
|
||||||
|
"LongSyntaxNotSupported": "此处不支持Long syntax,请使用YAML编辑器。",
|
||||||
|
"Lost connection to the socket server. Reconnecting...": "已断开socket服务器连接,重新连接中...",
|
||||||
|
"Saved": "已保存",
|
||||||
|
"Deployed": "已部署",
|
||||||
|
"Deleted": "已删除",
|
||||||
|
"Updated": "已更新",
|
||||||
|
"Started": "已启动",
|
||||||
|
"Stopped": "已停止",
|
||||||
|
"Restarted": "已重启",
|
||||||
|
"Switch to sh": "切换至sh",
|
||||||
|
"terminal": "终端",
|
||||||
|
"CurrentHostname": "未设置:沿用当前主机名",
|
||||||
|
"New Container Name...": "新的容器名称...",
|
||||||
|
"Network name...": "网络名称...",
|
||||||
|
"Select a network...": "选择网络...",
|
||||||
|
"NoNetworksAvailable": "网络不可用.你需要在正确的方向先添加内部网络或者启用外部网络.",
|
||||||
|
"Downed": "已宕机"
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"languageName": "繁體中文(台灣)",
|
"languageName": "繁體中文 (台灣)",
|
||||||
"Create your admin account": "建立您的管理員帳號",
|
"Create your admin account": "建立您的管理員帳號",
|
||||||
"authIncorrectCreds": "使用者名稱或密碼錯誤。",
|
"authIncorrectCreds": "使用者名稱或密碼錯誤。",
|
||||||
"PasswordsDoNotMatch": "兩次輸入的密碼不一致。",
|
"PasswordsDoNotMatch": "兩次輸入的密碼不一致。",
|
||||||
@ -10,7 +10,7 @@
|
|||||||
"home": "首頁",
|
"home": "首頁",
|
||||||
"console": "主控台",
|
"console": "主控台",
|
||||||
"registry": "映像倉庫",
|
"registry": "映像倉庫",
|
||||||
"compose": "Compose",
|
"compose": "撰寫",
|
||||||
"addFirstStackMsg": "組合您的第一個堆疊!",
|
"addFirstStackMsg": "組合您的第一個堆疊!",
|
||||||
"stackName": "堆疊名稱",
|
"stackName": "堆疊名稱",
|
||||||
"deployStack": "部署",
|
"deployStack": "部署",
|
||||||
@ -20,7 +20,7 @@
|
|||||||
"updateStack": "更新",
|
"updateStack": "更新",
|
||||||
"startStack": "啟動",
|
"startStack": "啟動",
|
||||||
"editStack": "編輯",
|
"editStack": "編輯",
|
||||||
"discardStack": "捨棄",
|
"discardStack": "丟棄",
|
||||||
"saveStackDraft": "儲存",
|
"saveStackDraft": "儲存",
|
||||||
"notAvailableShort": "不可用",
|
"notAvailableShort": "不可用",
|
||||||
"deleteStackMsg": "您確定要刪除這個堆疊嗎?",
|
"deleteStackMsg": "您確定要刪除這個堆疊嗎?",
|
||||||
@ -46,12 +46,12 @@
|
|||||||
"addContainer": "新增容器",
|
"addContainer": "新增容器",
|
||||||
"addNetwork": "新增網路",
|
"addNetwork": "新增網路",
|
||||||
"disableauth.message1": "您確定要<strong>停用身份驗證</strong>嗎?",
|
"disableauth.message1": "您確定要<strong>停用身份驗證</strong>嗎?",
|
||||||
"disableauth.message2": "該選項設計用於某些場景,<strong>例如在 Dockge 之上接入第三方認證</strong>,如 Cloudflare Access、Authelia 或其他認證機制。如果您不清楚這個選項的作用,請不要停用驗證!",
|
"disableauth.message2": "該選項設計用於某些場景,<strong>例如在 Dockge 之介接接第三方身份驗證</strong>,例如 Cloudflare Access、Authelia 或其他身份驗證機制。",
|
||||||
"passwordNotMatchMsg": "兩次輸入的密碼不一致。",
|
"passwordNotMatchMsg": "兩次輸入的密碼不一致。",
|
||||||
"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": "使用者名稱",
|
||||||
@ -91,12 +91,42 @@
|
|||||||
"Internal Networks": "內部網路",
|
"Internal Networks": "內部網路",
|
||||||
"External Networks": "外部網路",
|
"External Networks": "外部網路",
|
||||||
"No External Networks": "無外部網路",
|
"No External Networks": "無外部網路",
|
||||||
"downStack": "停止",
|
"downStack": "停止及未啟動化",
|
||||||
"reverseProxyMsg1": "在使用反向代理吗?",
|
"reverseProxyMsg1": "在使用反向代理嗎?",
|
||||||
"reverseProxyMsg2": "點擊這裡了解如何為 WebSocket 配置反向代理",
|
"reverseProxyMsg2": "點擊這裡了解如何為 WebSocket 配置反向代理",
|
||||||
"Cannot connect to the socket server.": "無法連接到 Socket 伺服器。",
|
"Cannot connect to the socket server.": "無法連接到 Socket 伺服器。",
|
||||||
"reconnecting...": "重新連線中…",
|
"reconnecting...": "重新連線中…",
|
||||||
"connecting...": "連線至 Socket 伺服器中…",
|
"connecting...": "連線至 Socket 伺服器中…",
|
||||||
"url": "網址 | 網址",
|
"url": "網址 | 網址",
|
||||||
"extra": "額外"
|
"extra": "額外",
|
||||||
|
"newUpdate": "新版本",
|
||||||
|
"currentEndpoint": "目前",
|
||||||
|
"dockgeURL": "Dockge URL(例如:http://127.0.0.1:5001)",
|
||||||
|
"agentOnline": "線上",
|
||||||
|
"connecting": "正在連線",
|
||||||
|
"agentOffline": "離線",
|
||||||
|
"Lost connection to the socket server. Reconnecting...": "與伺服器斷線。正在重新連線...",
|
||||||
|
"dockgeAgent": "Dockge代理 | Dockge代理",
|
||||||
|
"Saved": "已儲存",
|
||||||
|
"Switch to sh": "切換到 sh",
|
||||||
|
"NoNetworksAvailable": "沒有可以使用的網路。您需要先在右側新增內部網路或啟用外部網路。",
|
||||||
|
"LongSyntaxNotSupported": "這裡不支援長語法。請使用 YAML 編輯器。",
|
||||||
|
"connect": "連接",
|
||||||
|
"addAgent": "新增代理",
|
||||||
|
"agentAddedSuccessfully": "代理新增成功。",
|
||||||
|
"agentRemovedSuccessfully": "代理刪除成功。",
|
||||||
|
"Deployed": "已佈署",
|
||||||
|
"Deleted": "已刪除",
|
||||||
|
"Updated": "已更新",
|
||||||
|
"Started": "開始",
|
||||||
|
"Stopped": "已停止",
|
||||||
|
"Restarted": "重新啟動",
|
||||||
|
"Downed": "斷線",
|
||||||
|
"terminal": "終端",
|
||||||
|
"CurrentHostname": "(取消設定:依據目前主機名稱)",
|
||||||
|
"New Container Name...": "新容器名稱...",
|
||||||
|
"Network name...": "網路名稱...",
|
||||||
|
"Select a network...": "選擇網路...",
|
||||||
|
"removeAgent": "刪除代理",
|
||||||
|
"removeAgentMsg": "您確定要刪除這個代理嗎?"
|
||||||
}
|
}
|
||||||
|
@ -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-info me-3">
|
<a v-if="hasNewVersion" target="_blank" href="https://github.com/louislam/dockge/releases" class="btn btn-warning me-3">
|
||||||
<font-awesome-icon icon="arrow-alt-circle-up" /> {{ $t("New Update") }}
|
<font-awesome-icon icon="arrow-alt-circle-up" /> {{ $t("newUpdate") }}
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<ul class="nav nav-pills">
|
<ul class="nav nav-pills">
|
||||||
@ -98,6 +98,7 @@
|
|||||||
<script>
|
<script>
|
||||||
import Login from "../components/Login.vue";
|
import Login from "../components/Login.vue";
|
||||||
import { compareVersions } from "compare-versions";
|
import { compareVersions } from "compare-versions";
|
||||||
|
import { ALL_ENDPOINTS } from "../../../common/util-common";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
|
||||||
@ -145,7 +146,7 @@ export default {
|
|||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
scanFolder() {
|
scanFolder() {
|
||||||
this.$root.getSocket().emit("requestStackList", (res) => {
|
this.$root.emitAgent(ALL_ENDPOINTS, "requestStackList", (res) => {
|
||||||
this.$root.toastRes(res);
|
this.$root.toastRes(res);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
// Dayjs init inside this, so it has to be the first import
|
// Dayjs init inside this, so it has to be the first import
|
||||||
import "../../backend/util-common";
|
import "../../common/util-common";
|
||||||
|
|
||||||
import { createApp, defineComponent, h } from "vue";
|
import { createApp, defineComponent, h } from "vue";
|
||||||
import App from "./App.vue";
|
import App from "./App.vue";
|
||||||
@ -10,12 +10,12 @@ import { i18n } from "./i18n";
|
|||||||
// Dependencies
|
// Dependencies
|
||||||
import "bootstrap";
|
import "bootstrap";
|
||||||
import Toast, { POSITION, useToast } from "vue-toastification";
|
import Toast, { POSITION, useToast } from "vue-toastification";
|
||||||
import "xterm/lib/xterm.js";
|
import "@xterm/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/css/xterm.css";
|
import "@xterm/xterm/css/xterm.css";
|
||||||
import "./styles/main.scss";
|
import "./styles/main.scss";
|
||||||
|
|
||||||
// Minxins
|
// Minxins
|
||||||
|
@ -2,7 +2,8 @@ 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";
|
import { Terminal } from "@xterm/xterm";
|
||||||
|
import { AgentSocket } from "../../../common/agent-socket";
|
||||||
|
|
||||||
let socket : Socket;
|
let socket : Socket;
|
||||||
|
|
||||||
@ -28,16 +29,51 @@ export default defineComponent({
|
|||||||
loggedIn: false,
|
loggedIn: false,
|
||||||
allowLoginDialog: false,
|
allowLoginDialog: false,
|
||||||
username: null,
|
username: null,
|
||||||
stackList: {},
|
|
||||||
composeTemplate: "",
|
composeTemplate: "",
|
||||||
|
|
||||||
|
stackList: {},
|
||||||
|
|
||||||
|
// All stack list from all agents
|
||||||
|
allAgentStackList: {} as Record<string, object>,
|
||||||
|
|
||||||
|
// online / offline / connecting
|
||||||
|
agentStatusList: {
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
// Agent List
|
||||||
|
agentList: {
|
||||||
|
|
||||||
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
|
||||||
|
agentCount() {
|
||||||
|
return Object.keys(this.agentList).length;
|
||||||
|
},
|
||||||
|
|
||||||
|
completeStackList() {
|
||||||
|
let list : Record<string, object> = {};
|
||||||
|
|
||||||
|
for (let stackName in this.stackList) {
|
||||||
|
list[stackName + "_"] = this.stackList[stackName];
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let endpoint in this.allAgentStackList) {
|
||||||
|
let instance = this.allAgentStackList[endpoint];
|
||||||
|
for (let stackName in instance.stackList) {
|
||||||
|
list[stackName + "_" + endpoint] = instance.stackList[stackName];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
},
|
||||||
|
|
||||||
usernameFirstChar() {
|
usernameFirstChar() {
|
||||||
if (typeof this.username == "string" && this.username.length >= 1) {
|
if (typeof this.username == "string" && this.username.length >= 1) {
|
||||||
return this.username.charAt(0).toUpperCase();
|
return this.username.charAt(0).toUpperCase();
|
||||||
} else {
|
} else {
|
||||||
return "🐻";
|
return "🐬";
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -65,6 +101,15 @@ export default defineComponent({
|
|||||||
|
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
|
|
||||||
|
"socketIO.connected"() {
|
||||||
|
if (this.socketIO.connected) {
|
||||||
|
this.agentStatusList[""] = "online";
|
||||||
|
} else {
|
||||||
|
this.agentStatusList[""] = "offline";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
remember() {
|
remember() {
|
||||||
localStorage.remember = (this.remember) ? "1" : "0";
|
localStorage.remember = (this.remember) ? "1" : "0";
|
||||||
},
|
},
|
||||||
@ -84,6 +129,15 @@ export default defineComponent({
|
|||||||
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
|
||||||
|
endpointDisplayFunction(endpoint : string) {
|
||||||
|
if (endpoint) {
|
||||||
|
return endpoint;
|
||||||
|
} else {
|
||||||
|
return this.$t("currentEndpoint");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize connection to socket server
|
* Initialize connection to socket server
|
||||||
* @param bypass Should the check for if we
|
* @param bypass Should the check for if we
|
||||||
@ -108,8 +162,12 @@ export default defineComponent({
|
|||||||
this.socketIO.connecting = true;
|
this.socketIO.connecting = true;
|
||||||
}, 1500);
|
}, 1500);
|
||||||
|
|
||||||
socket = io(url, {
|
socket = io(url);
|
||||||
transports: [ "websocket", "polling" ]
|
|
||||||
|
// Handling events from agents
|
||||||
|
let agentSocket = new AgentSocket();
|
||||||
|
socket.on("agent", (eventName : unknown, ...args : unknown[]) => {
|
||||||
|
agentSocket.call(eventName, ...args);
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on("connect", () => {
|
socket.on("connect", () => {
|
||||||
@ -145,7 +203,7 @@ export default defineComponent({
|
|||||||
|
|
||||||
socket.on("disconnect", () => {
|
socket.on("disconnect", () => {
|
||||||
console.log("disconnect");
|
console.log("disconnect");
|
||||||
this.socketIO.connectionErrorMsg = "Lost connection to the socket server. Reconnecting...";
|
this.socketIO.connectionErrorMsg = `${this.$t("Lost connection to the socket server. Reconnecting...")}`;
|
||||||
this.socketIO.connected = false;
|
this.socketIO.connected = false;
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -177,7 +235,7 @@ export default defineComponent({
|
|||||||
this.$router.push("/setup");
|
this.$router.push("/setup");
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on("terminalWrite", (terminalName, data) => {
|
agentSocket.on("terminalWrite", (terminalName, data) => {
|
||||||
const terminal = terminalMap.get(terminalName);
|
const terminal = terminalMap.get(terminalName);
|
||||||
if (!terminal) {
|
if (!terminal) {
|
||||||
//console.error("Terminal not found: " + terminalName);
|
//console.error("Terminal not found: " + terminalName);
|
||||||
@ -186,9 +244,18 @@ export default defineComponent({
|
|||||||
terminal.write(data);
|
terminal.write(data);
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on("stackList", (res) => {
|
agentSocket.on("stackList", (res) => {
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
this.stackList = res.stackList;
|
if (!res.endpoint) {
|
||||||
|
this.stackList = res.stackList;
|
||||||
|
} else {
|
||||||
|
if (!this.allAgentStackList[res.endpoint]) {
|
||||||
|
this.allAgentStackList[res.endpoint] = {
|
||||||
|
stackList: {},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
this.allAgentStackList[res.endpoint].stackList = res.stackList;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -202,6 +269,24 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
socket.on("agentStatus", (res) => {
|
||||||
|
this.agentStatusList[res.endpoint] = res.status;
|
||||||
|
|
||||||
|
if (res.msg) {
|
||||||
|
this.toastError(res.msg);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on("agentList", (res) => {
|
||||||
|
if (res.ok) {
|
||||||
|
this.agentList = res.agentList;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on("refresh", () => {
|
||||||
|
location.reload();
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -216,6 +301,10 @@ export default defineComponent({
|
|||||||
return socket;
|
return socket;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
emitAgent(endpoint : string, eventName : string, ...args : unknown[]) {
|
||||||
|
this.getSocket().emit("agent", endpoint, eventName, ...args);
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get payload of JWT cookie
|
* Get payload of JWT cookie
|
||||||
* @returns {(object | undefined)} JWT payload
|
* @returns {(object | undefined)} JWT payload
|
||||||
@ -306,9 +395,9 @@ export default defineComponent({
|
|||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
bindTerminal(terminalName : string, terminal : Terminal) {
|
bindTerminal(endpoint : string, terminalName : string, terminal : Terminal) {
|
||||||
// Load terminal, get terminal screen
|
// Load terminal, get terminal screen
|
||||||
socket.emit("terminalJoin", terminalName, (res) => {
|
this.emitAgent(endpoint, "terminalJoin", terminalName, (res) => {
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
terminal.write(res.buffer);
|
terminal.write(res.buffer);
|
||||||
terminalMap.set(terminalName, terminal);
|
terminalMap.set(terminalName, terminal);
|
||||||
|
@ -1,8 +1,13 @@
|
|||||||
<template>
|
<template>
|
||||||
<transition name="slide-fade" appear>
|
<transition name="slide-fade" appear>
|
||||||
<div>
|
<div>
|
||||||
<h1 v-if="isAdd" class="mb-3">Compose</h1>
|
<h1 v-if="isAdd" class="mb-3">{{$t("compose")}}</h1>
|
||||||
<h1 v-else class="mb-3"><Uptime :stack="globalStack" :pill="true" /> {{ stack.name }}</h1>
|
<h1 v-else class="mb-3">
|
||||||
|
<Uptime :stack="globalStack" :pill="true" /> {{ stack.name }}
|
||||||
|
<span v-if="$root.agentCount > 1" class="agent-name">
|
||||||
|
({{ endpointDisplay }})
|
||||||
|
</span>
|
||||||
|
</h1>
|
||||||
|
|
||||||
<div v-if="stack.isManagedByDockge" class="mb-3">
|
<div v-if="stack.isManagedByDockge" class="mb-3">
|
||||||
<div class="btn-group me-2" role="group">
|
<div class="btn-group me-2" role="group">
|
||||||
@ -70,6 +75,7 @@
|
|||||||
ref="progressTerminal"
|
ref="progressTerminal"
|
||||||
class="mb-3 terminal"
|
class="mb-3 terminal"
|
||||||
:name="terminalName"
|
:name="terminalName"
|
||||||
|
:endpoint="endpoint"
|
||||||
:rows="progressTerminalRows"
|
:rows="progressTerminalRows"
|
||||||
@has-data="showProgressTerminal = true; submitted = true;"
|
@has-data="showProgressTerminal = true; submitted = true;"
|
||||||
></Terminal>
|
></Terminal>
|
||||||
@ -87,6 +93,16 @@
|
|||||||
<input id="name" v-model="stack.name" type="text" class="form-control" required @blur="stackNameToLowercase">
|
<input id="name" v-model="stack.name" type="text" class="form-control" required @blur="stackNameToLowercase">
|
||||||
<div class="form-text">{{ $t("Lowercase only") }}</div>
|
<div class="form-text">{{ $t("Lowercase only") }}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Endpoint -->
|
||||||
|
<div class="mt-3">
|
||||||
|
<label for="name" class="form-label">{{ $t("dockgeAgent") }}</label>
|
||||||
|
<select v-model="stack.endpoint" class="form-select">
|
||||||
|
<option v-for="(agent, endpoint) in $root.agentList" :key="endpoint" :value="endpoint" :disabled="$root.agentStatusList[endpoint] != 'online'">
|
||||||
|
({{ $root.agentStatusList[endpoint] }}) {{ (endpoint) ? endpoint : $t("currentEndpoint") }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -96,7 +112,7 @@
|
|||||||
<div v-if="isEditMode" class="input-group mb-3">
|
<div v-if="isEditMode" class="input-group mb-3">
|
||||||
<input
|
<input
|
||||||
v-model="newContainerName"
|
v-model="newContainerName"
|
||||||
placeholder="New Container Name..."
|
:placeholder="$t(`New Container Name...`)"
|
||||||
class="form-control"
|
class="form-control"
|
||||||
@keyup.enter="addContainer"
|
@keyup.enter="addContainer"
|
||||||
/>
|
/>
|
||||||
@ -134,14 +150,15 @@
|
|||||||
|
|
||||||
<!-- Combined Terminal Output -->
|
<!-- Combined Terminal Output -->
|
||||||
<div v-show="!isEditMode">
|
<div v-show="!isEditMode">
|
||||||
<h4 class="mb-3">Terminal</h4>
|
<h4 class="mb-3">{{$t("terminal")}}</h4>
|
||||||
<Terminal
|
<Terminal
|
||||||
ref="combinedTerminal"
|
ref="combinedTerminal"
|
||||||
class="mb-3 terminal"
|
class="mb-3 terminal"
|
||||||
:name="combinedTerminalName"
|
:name="combinedTerminalName"
|
||||||
|
:endpoint="endpoint"
|
||||||
:rows="combinedTerminalRows"
|
:rows="combinedTerminalRows"
|
||||||
:cols="combinedTerminalCols"
|
:cols="combinedTerminalCols"
|
||||||
style="height: 350px;"
|
style="height: 315px;"
|
||||||
></Terminal>
|
></Terminal>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -231,16 +248,17 @@ import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
|
|||||||
import {
|
import {
|
||||||
COMBINED_TERMINAL_COLS,
|
COMBINED_TERMINAL_COLS,
|
||||||
COMBINED_TERMINAL_ROWS,
|
COMBINED_TERMINAL_ROWS,
|
||||||
copyYAMLComments,
|
copyYAMLComments, envsubstYAML,
|
||||||
getCombinedTerminalName,
|
getCombinedTerminalName,
|
||||||
getComposeTerminalName,
|
getComposeTerminalName,
|
||||||
PROGRESS_TERMINAL_ROWS,
|
PROGRESS_TERMINAL_ROWS,
|
||||||
RUNNING
|
RUNNING
|
||||||
} from "../../../backend/util-common";
|
} from "../../../common/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 = `
|
||||||
services:
|
services:
|
||||||
nginx:
|
nginx:
|
||||||
image: nginx:latest
|
image: nginx:latest
|
||||||
@ -277,6 +295,7 @@ export default {
|
|||||||
return {
|
return {
|
||||||
editorFocus: false,
|
editorFocus: false,
|
||||||
jsonConfig: {},
|
jsonConfig: {},
|
||||||
|
envsubstJSONConfig: {},
|
||||||
yamlError: "",
|
yamlError: "",
|
||||||
processing: true,
|
processing: true,
|
||||||
showProgressTerminal: false,
|
showProgressTerminal: false,
|
||||||
@ -296,13 +315,17 @@ export default {
|
|||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
|
||||||
|
endpointDisplay() {
|
||||||
|
return this.$root.endpointDisplayFunction(this.endpoint);
|
||||||
|
},
|
||||||
|
|
||||||
urls() {
|
urls() {
|
||||||
if (!this.jsonConfig["x-dockge"] || !this.jsonConfig["x-dockge"].urls || !Array.isArray(this.jsonConfig["x-dockge"].urls)) {
|
if (!this.envsubstJSONConfig["x-dockge"] || !this.envsubstJSONConfig["x-dockge"].urls || !Array.isArray(this.envsubstJSONConfig["x-dockge"].urls)) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
let urls = [];
|
let urls = [];
|
||||||
for (const url of this.jsonConfig["x-dockge"].urls) {
|
for (const url of this.envsubstJSONConfig["x-dockge"].urls) {
|
||||||
let display;
|
let display;
|
||||||
try {
|
try {
|
||||||
let obj = new URL(url);
|
let obj = new URL(url);
|
||||||
@ -332,7 +355,7 @@ export default {
|
|||||||
* @return {*}
|
* @return {*}
|
||||||
*/
|
*/
|
||||||
globalStack() {
|
globalStack() {
|
||||||
return this.$root.stackList[this.stack.name];
|
return this.$root.completeStackList[this.stack.name + "_" + this.endpoint];
|
||||||
},
|
},
|
||||||
|
|
||||||
status() {
|
status() {
|
||||||
@ -347,20 +370,31 @@ export default {
|
|||||||
if (!this.stack.name) {
|
if (!this.stack.name) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
return getComposeTerminalName(this.stack.name);
|
return getComposeTerminalName(this.endpoint, this.stack.name);
|
||||||
},
|
},
|
||||||
|
|
||||||
combinedTerminalName() {
|
combinedTerminalName() {
|
||||||
if (!this.stack.name) {
|
if (!this.stack.name) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
return getCombinedTerminalName(this.stack.name);
|
return getCombinedTerminalName(this.endpoint, this.stack.name);
|
||||||
},
|
},
|
||||||
|
|
||||||
networks() {
|
networks() {
|
||||||
return this.jsonConfig.networks;
|
return this.jsonConfig.networks;
|
||||||
}
|
},
|
||||||
|
|
||||||
|
endpoint() {
|
||||||
|
return this.stack.endpoint || this.$route.params.endpoint || "";
|
||||||
|
},
|
||||||
|
|
||||||
|
url() {
|
||||||
|
if (this.stack.endpoint) {
|
||||||
|
return `/compose/${this.stack.name}/${this.stack.endpoint}`;
|
||||||
|
} else {
|
||||||
|
return `/compose/${this.stack.name}`;
|
||||||
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
"stack.composeYAML": {
|
"stack.composeYAML": {
|
||||||
@ -372,6 +406,17 @@ 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) {
|
||||||
@ -392,9 +437,7 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
$route(to, from) {
|
$route(to, from) {
|
||||||
// Leave Combined Terminal
|
|
||||||
console.debug("leaveCombinedTerminal", from.params.stackName);
|
|
||||||
this.$root.getSocket().emit("leaveCombinedTerminal", this.stack.name, () => {});
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
@ -424,6 +467,7 @@ export default {
|
|||||||
composeYAML,
|
composeYAML,
|
||||||
composeENV,
|
composeENV,
|
||||||
isManagedByDockge: true,
|
isManagedByDockge: true,
|
||||||
|
endpoint: "",
|
||||||
};
|
};
|
||||||
|
|
||||||
this.yamlCodeChange();
|
this.yamlCodeChange();
|
||||||
@ -436,11 +480,9 @@ export default {
|
|||||||
this.requestServiceStatus();
|
this.requestServiceStatus();
|
||||||
},
|
},
|
||||||
unmounted() {
|
unmounted() {
|
||||||
this.stopServiceStatusTimeout = true;
|
|
||||||
clearTimeout(serviceStatusTimeout);
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
|
||||||
startServiceStatusTimeout() {
|
startServiceStatusTimeout() {
|
||||||
clearTimeout(serviceStatusTimeout);
|
clearTimeout(serviceStatusTimeout);
|
||||||
serviceStatusTimeout = setTimeout(async () => {
|
serviceStatusTimeout = setTimeout(async () => {
|
||||||
@ -449,7 +491,7 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
requestServiceStatus() {
|
requestServiceStatus() {
|
||||||
this.$root.getSocket().emit("serviceStatusList", this.stack.name, (res) => {
|
this.$root.emitAgent(this.endpoint, "serviceStatusList", this.stack.name, (res) => {
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
this.serviceStatusList = res.serviceStatusList;
|
this.serviceStatusList = res.serviceStatusList;
|
||||||
}
|
}
|
||||||
@ -462,22 +504,34 @@ export default {
|
|||||||
exitConfirm(next) {
|
exitConfirm(next) {
|
||||||
if (this.isEditMode) {
|
if (this.isEditMode) {
|
||||||
if (confirm("You are currently editing a stack. Are you sure you want to leave?")) {
|
if (confirm("You are currently editing a stack. Are you sure you want to leave?")) {
|
||||||
|
this.exitAction();
|
||||||
next();
|
next();
|
||||||
} else {
|
} else {
|
||||||
next(false);
|
next(false);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
this.exitAction();
|
||||||
next();
|
next();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
exitAction() {
|
||||||
|
console.log("exitAction");
|
||||||
|
this.stopServiceStatusTimeout = true;
|
||||||
|
clearTimeout(serviceStatusTimeout);
|
||||||
|
|
||||||
|
// Leave Combined Terminal
|
||||||
|
console.debug("leaveCombinedTerminal", this.endpoint, this.stack.name);
|
||||||
|
this.$root.emitAgent(this.endpoint, "leaveCombinedTerminal", this.stack.name, () => {});
|
||||||
|
},
|
||||||
|
|
||||||
bindTerminal() {
|
bindTerminal() {
|
||||||
this.$refs.progressTerminal?.bind(this.terminalName);
|
this.$refs.progressTerminal?.bind(this.endpoint, this.terminalName);
|
||||||
},
|
},
|
||||||
|
|
||||||
loadStack() {
|
loadStack() {
|
||||||
this.processing = true;
|
this.processing = true;
|
||||||
this.$root.getSocket().emit("getStack", this.stack.name, (res) => {
|
this.$root.emitAgent(this.endpoint, "getStack", this.stack.name, (res) => {
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
this.stack = res.stack;
|
this.stack = res.stack;
|
||||||
this.yamlCodeChange();
|
this.yamlCodeChange();
|
||||||
@ -519,15 +573,15 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.bindTerminal(this.terminalName);
|
this.bindTerminal();
|
||||||
|
|
||||||
this.$root.getSocket().emit("deployStack", this.stack.name, this.stack.composeYAML, this.stack.composeENV, this.isAdd, (res) => {
|
this.$root.emitAgent(this.stack.endpoint, "deployStack", this.stack.name, this.stack.composeYAML, this.stack.composeENV, this.isAdd, (res) => {
|
||||||
this.processing = false;
|
this.processing = false;
|
||||||
this.$root.toastRes(res);
|
this.$root.toastRes(res);
|
||||||
|
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
this.isEditMode = false;
|
this.isEditMode = false;
|
||||||
this.$router.push("/compose/" + this.stack.name);
|
this.$router.push(this.url);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@ -535,13 +589,13 @@ 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.emitAgent(this.stack.endpoint, "saveStack", this.stack.name, this.stack.composeYAML, this.stack.composeENV, this.isAdd, (res) => {
|
||||||
this.processing = false;
|
this.processing = false;
|
||||||
this.$root.toastRes(res);
|
this.$root.toastRes(res);
|
||||||
|
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
this.isEditMode = false;
|
this.isEditMode = false;
|
||||||
this.$router.push("/compose/" + this.stack.name);
|
this.$router.push(this.url);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@ -549,7 +603,7 @@ export default {
|
|||||||
startStack() {
|
startStack() {
|
||||||
this.processing = true;
|
this.processing = true;
|
||||||
|
|
||||||
this.$root.getSocket().emit("startStack", this.stack.name, (res) => {
|
this.$root.emitAgent(this.endpoint, "startStack", this.stack.name, (res) => {
|
||||||
this.processing = false;
|
this.processing = false;
|
||||||
this.$root.toastRes(res);
|
this.$root.toastRes(res);
|
||||||
});
|
});
|
||||||
@ -558,7 +612,7 @@ export default {
|
|||||||
stopStack() {
|
stopStack() {
|
||||||
this.processing = true;
|
this.processing = true;
|
||||||
|
|
||||||
this.$root.getSocket().emit("stopStack", this.stack.name, (res) => {
|
this.$root.emitAgent(this.endpoint, "stopStack", this.stack.name, (res) => {
|
||||||
this.processing = false;
|
this.processing = false;
|
||||||
this.$root.toastRes(res);
|
this.$root.toastRes(res);
|
||||||
});
|
});
|
||||||
@ -567,7 +621,7 @@ export default {
|
|||||||
downStack() {
|
downStack() {
|
||||||
this.processing = true;
|
this.processing = true;
|
||||||
|
|
||||||
this.$root.getSocket().emit("downStack", this.stack.name, (res) => {
|
this.$root.emitAgent(this.endpoint, "downStack", this.stack.name, (res) => {
|
||||||
this.processing = false;
|
this.processing = false;
|
||||||
this.$root.toastRes(res);
|
this.$root.toastRes(res);
|
||||||
});
|
});
|
||||||
@ -576,7 +630,7 @@ export default {
|
|||||||
restartStack() {
|
restartStack() {
|
||||||
this.processing = true;
|
this.processing = true;
|
||||||
|
|
||||||
this.$root.getSocket().emit("restartStack", this.stack.name, (res) => {
|
this.$root.emitAgent(this.endpoint, "restartStack", this.stack.name, (res) => {
|
||||||
this.processing = false;
|
this.processing = false;
|
||||||
this.$root.toastRes(res);
|
this.$root.toastRes(res);
|
||||||
});
|
});
|
||||||
@ -585,14 +639,14 @@ export default {
|
|||||||
updateStack() {
|
updateStack() {
|
||||||
this.processing = true;
|
this.processing = true;
|
||||||
|
|
||||||
this.$root.getSocket().emit("updateStack", this.stack.name, (res) => {
|
this.$root.emitAgent(this.endpoint, "updateStack", this.stack.name, (res) => {
|
||||||
this.processing = false;
|
this.processing = false;
|
||||||
this.$root.toastRes(res);
|
this.$root.toastRes(res);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
deleteDialog() {
|
deleteDialog() {
|
||||||
this.$root.getSocket().emit("deleteStack", this.stack.name, (res) => {
|
this.$root.emitAgent(this.endpoint, "deleteStack", this.stack.name, (res) => {
|
||||||
this.$root.toastRes(res);
|
this.$root.toastRes(res);
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
this.$router.push("/");
|
this.$router.push("/");
|
||||||
@ -622,7 +676,7 @@ export default {
|
|||||||
greedy: true
|
greedy: true
|
||||||
},
|
},
|
||||||
"keyword": {
|
"keyword": {
|
||||||
pattern: /^[^ :=]*(?=[:=])/m,
|
pattern: /^\w*(?=[:=])/m,
|
||||||
greedy: true
|
greedy: true
|
||||||
},
|
},
|
||||||
"value": {
|
"value": {
|
||||||
@ -645,28 +699,41 @@ export default {
|
|||||||
return highlight(code, languages.docker_env);
|
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 doc = parseDocument(this.stack.composeYAML);
|
let { config, doc } = this.yamlToJSON(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) {
|
||||||
@ -724,6 +791,8 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
|
@import "../styles/vars.scss";
|
||||||
|
|
||||||
.terminal {
|
.terminal {
|
||||||
height: 200px;
|
height: 200px;
|
||||||
}
|
}
|
||||||
@ -735,4 +804,9 @@ export default {
|
|||||||
background-color: #2c2f38 !important;
|
background-color: #2c2f38 !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.agent-name {
|
||||||
|
font-size: 13px;
|
||||||
|
color: $dark-font-color3;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -1,39 +1,48 @@
|
|||||||
<template>
|
<template>
|
||||||
<transition name="slide-fade" appear>
|
<transition name="slide-fade" appear>
|
||||||
<div>
|
<div v-if="!processing">
|
||||||
<h1 class="mb-3">Console</h1>
|
<h1 class="mb-3">Console</h1>
|
||||||
|
|
||||||
<div>
|
<Terminal v-if="enableConsole" class="terminal" :rows="20" mode="mainTerminal" name="console" :endpoint="endpoint"></Terminal>
|
||||||
<p>
|
|
||||||
{{ $t("Allowed commands:") }}
|
|
||||||
<template v-for="(command, index) in allowedCommandList" :key="command">
|
|
||||||
<code>{{ command }}</code>
|
|
||||||
|
|
||||||
<!-- No comma at the end -->
|
<div v-else class="alert alert-warning shadow-box" role="alert">
|
||||||
<span v-if="index !== allowedCommandList.length - 1">, </span>
|
<h4 class="alert-heading">Console is not enabled</h4>
|
||||||
</template>
|
<p>
|
||||||
|
Console is a powerful tool that allows you to execute any commands such as <code>docker</code>, <code>rm</code> within the Dockge's container in this Web UI.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
It might be dangerous since this Dockge container is connecting to the host's Docker daemon. Also Dockge could be possibly taken down by commands like <code>rm -rf</code>.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
If you understand the risk, you can enable it by setting <code>DOCKGE_ENABLE_CONSOLE=true</code> in the environment variables.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Terminal class="terminal" :rows="20" mode="mainTerminal" name="console"></Terminal>
|
|
||||||
</div>
|
</div>
|
||||||
</transition>
|
</transition>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
import { allowedCommandList } from "../../../backend/util-common";
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
allowedCommandList,
|
processing: true,
|
||||||
|
enableConsole: false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
computed: {
|
||||||
|
endpoint() {
|
||||||
|
return this.$route.params.endpoint || "";
|
||||||
|
},
|
||||||
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
|
this.$root.emitAgent(this.endpoint, "checkMainTerminal", (res) => {
|
||||||
|
this.enableConsole = res.ok;
|
||||||
|
this.processing = false;
|
||||||
|
});
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
|
||||||
|
@ -1,19 +1,19 @@
|
|||||||
<template>
|
<template>
|
||||||
<transition name="slide-fade" appear>
|
<transition name="slide-fade" appear>
|
||||||
<div>
|
<div>
|
||||||
<h1 class="mb-3">Terminal - {{ serviceName }} ({{ stackName }})</h1>
|
<h1 class="mb-3">{{$t("terminal")}} - {{ serviceName }} ({{ stackName }})</h1>
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<router-link :to="sh" class="btn btn-normal me-2">Switch to sh</router-link>
|
<router-link :to="sh" class="btn btn-normal me-2">{{ $t("Switch to sh") }}</router-link>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Terminal class="terminal" :rows="20" mode="interactive" :name="terminalName" :stack-name="stackName" :service-name="serviceName" :shell="shell"></Terminal>
|
<Terminal class="terminal" :rows="20" mode="interactive" :name="terminalName" :stack-name="stackName" :service-name="serviceName" :shell="shell" :endpoint="endpoint"></Terminal>
|
||||||
</div>
|
</div>
|
||||||
</transition>
|
</transition>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { getContainerExecTerminalName } from "../../../backend/util-common";
|
import { getContainerExecTerminalName } from "../../../common/util-common";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
@ -27,6 +27,9 @@ export default {
|
|||||||
stackName() {
|
stackName() {
|
||||||
return this.$route.params.stackName;
|
return this.$route.params.stackName;
|
||||||
},
|
},
|
||||||
|
endpoint() {
|
||||||
|
return this.$route.params.endpoint || "";
|
||||||
|
},
|
||||||
shell() {
|
shell() {
|
||||||
return this.$route.params.type;
|
return this.$route.params.type;
|
||||||
},
|
},
|
||||||
@ -34,10 +37,12 @@ export default {
|
|||||||
return this.$route.params.serviceName;
|
return this.$route.params.serviceName;
|
||||||
},
|
},
|
||||||
terminalName() {
|
terminalName() {
|
||||||
return getContainerExecTerminalName(this.stackName, this.serviceName, 0);
|
return getContainerExecTerminalName(this.endpoint, this.stackName, this.serviceName, 0);
|
||||||
},
|
},
|
||||||
sh() {
|
sh() {
|
||||||
return {
|
let endpoint = this.$route.params.endpoint;
|
||||||
|
|
||||||
|
let data = {
|
||||||
name: "containerTerminal",
|
name: "containerTerminal",
|
||||||
params: {
|
params: {
|
||||||
stackName: this.stackName,
|
stackName: this.stackName,
|
||||||
@ -45,6 +50,13 @@ export default {
|
|||||||
type: "sh",
|
type: "sh",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (endpoint) {
|
||||||
|
data.name = "containerTerminalEndpoint";
|
||||||
|
data.params.endpoint = endpoint;
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
|
@ -5,36 +5,97 @@
|
|||||||
{{ $t("home") }}
|
{{ $t("home") }}
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<div class="shadow-box big-padding text-center mb-4">
|
<div class="row first-row">
|
||||||
<div class="row">
|
<!-- Left -->
|
||||||
<div class="col">
|
<div class="col-md-7">
|
||||||
<h3>{{ $t("active") }}</h3>
|
<!-- Stats -->
|
||||||
<span class="num active">{{ activeNum }}</span>
|
<div class="shadow-box big-padding text-center mb-4">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<h3>{{ $t("active") }}</h3>
|
||||||
|
<span class="num active">{{ activeNum }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="col">
|
||||||
|
<h3>{{ $t("exited") }}</h3>
|
||||||
|
<span class="num exited">{{ exitedNum }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="col">
|
||||||
|
<h3>{{ $t("inactive") }}</h3>
|
||||||
|
<span class="num inactive">{{ inactiveNum }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col">
|
|
||||||
<h3>{{ $t("exited") }}</h3>
|
<!-- Docker Run -->
|
||||||
<span class="num exited">{{ exitedNum }}</span>
|
<h2 class="mb-3">{{ $t("Docker Run") }}</h2>
|
||||||
|
<div class="mb-3">
|
||||||
|
<textarea id="name" v-model="dockerRunCommand" type="text" class="form-control docker-run" required placeholder="docker run ..."></textarea>
|
||||||
</div>
|
</div>
|
||||||
<div class="col">
|
|
||||||
<h3>{{ $t("inactive") }}</h3>
|
<button class="btn-normal btn mb-4" @click="convertDockerRun">{{ $t("Convert to Compose") }}</button>
|
||||||
<span class="num inactive">{{ inactiveNum }}</span>
|
</div>
|
||||||
|
<!-- Right -->
|
||||||
|
<div class="col-md-5">
|
||||||
|
<!-- Agent List -->
|
||||||
|
<div class="shadow-box big-padding">
|
||||||
|
<h4 class="mb-3">{{ $tc("dockgeAgent", 2) }} <span class="badge bg-warning" style="font-size: 12px;">beta</span></h4>
|
||||||
|
|
||||||
|
<div v-for="(agent, endpoint) in $root.agentList" :key="endpoint" class="mb-3 agent">
|
||||||
|
<!-- Agent Status -->
|
||||||
|
<template v-if="$root.agentStatusList[endpoint]">
|
||||||
|
<span v-if="$root.agentStatusList[endpoint] === 'online'" class="badge bg-primary me-2">{{ $t("agentOnline") }}</span>
|
||||||
|
<span v-else-if="$root.agentStatusList[endpoint] === 'offline'" class="badge bg-danger me-2">{{ $t("agentOffline") }}</span>
|
||||||
|
<span v-else class="badge bg-secondary me-2">{{ $t($root.agentStatusList[endpoint]) }}</span>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- Agent Display Name -->
|
||||||
|
<span v-if="endpoint === ''">{{ $t("currentEndpoint") }}</span>
|
||||||
|
<a v-else :href="agent.url" target="_blank">{{ endpoint }}</a>
|
||||||
|
|
||||||
|
<!-- Remove Button -->
|
||||||
|
<font-awesome-icon v-if="endpoint !== ''" class="ms-2 remove-agent" icon="trash" @click="showRemoveAgentDialog[agent.url] = !showRemoveAgentDialog[agent.url]" />
|
||||||
|
|
||||||
|
<!-- Remoe Agent Dialog -->
|
||||||
|
<BModal v-model="showRemoveAgentDialog[agent.url]" :okTitle="$t('removeAgent')" okVariant="danger" @ok="removeAgent(agent.url)">
|
||||||
|
<p>{{ agent.url }}</p>
|
||||||
|
{{ $t("removeAgentMsg") }}
|
||||||
|
</BModal>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button v-if="!showAgentForm" class="btn btn-normal" @click="showAgentForm = !showAgentForm">{{ $t("addAgent") }}</button>
|
||||||
|
|
||||||
|
<!-- Add Agent Form -->
|
||||||
|
<form v-if="showAgentForm" @submit.prevent="addAgent">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="url" class="form-label">{{ $t("dockgeURL") }}</label>
|
||||||
|
<input id="url" v-model="agent.url" type="url" class="form-control" required placeholder="http://">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="username" class="form-label">{{ $t("Username") }}</label>
|
||||||
|
<input id="username" v-model="agent.username" type="text" class="form-control" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="password" class="form-label">{{ $t("Password") }}</label>
|
||||||
|
<input id="password" v-model="agent.password" type="password" class="form-control" required autocomplete="new-password">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="submit" class="btn btn-primary" :disabled="connectingAgent">
|
||||||
|
<template v-if="connectingAgent">{{ $t("connecting") }}</template>
|
||||||
|
<template v-else>{{ $t("connect") }}</template>
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h2 class="mb-3">{{ $t("Docker Run") }}</h2>
|
|
||||||
<div class="mb-3">
|
|
||||||
<textarea id="name" v-model="dockerRunCommand" type="text" class="form-control docker-run" required placeholder="docker run ..."></textarea>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button class="btn-normal btn" @click="convertDockerRun">{{ $t("Convert to Compose") }}</button>
|
|
||||||
</div>
|
</div>
|
||||||
</transition>
|
</transition>
|
||||||
<router-view ref="child" />
|
<router-view ref="child" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { statusNameShort } from "../../../backend/util-common";
|
import { statusNameShort } from "../../../common/util-common";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
@ -58,6 +119,14 @@ export default {
|
|||||||
importantHeartBeatListLength: 0,
|
importantHeartBeatListLength: 0,
|
||||||
displayedRecords: [],
|
displayedRecords: [],
|
||||||
dockerRunCommand: "",
|
dockerRunCommand: "",
|
||||||
|
showAgentForm: false,
|
||||||
|
showRemoveAgentDialog: {},
|
||||||
|
connectingAgent: false,
|
||||||
|
agent: {
|
||||||
|
url: "http://",
|
||||||
|
username: "",
|
||||||
|
password: "",
|
||||||
|
}
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -98,11 +167,43 @@ export default {
|
|||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
|
|
||||||
|
addAgent() {
|
||||||
|
this.connectingAgent = true;
|
||||||
|
this.$root.getSocket().emit("addAgent", this.agent, (res) => {
|
||||||
|
this.$root.toastRes(res);
|
||||||
|
|
||||||
|
if (res.ok) {
|
||||||
|
this.showAgentForm = false;
|
||||||
|
this.agent = {
|
||||||
|
url: "http://",
|
||||||
|
username: "",
|
||||||
|
password: "",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
this.connectingAgent = false;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
removeAgent(url) {
|
||||||
|
this.$root.getSocket().emit("removeAgent", url, (res) => {
|
||||||
|
if (res.ok) {
|
||||||
|
this.$root.toastRes(res);
|
||||||
|
|
||||||
|
let urlObj = new URL(url);
|
||||||
|
let endpoint = urlObj.host;
|
||||||
|
|
||||||
|
// Remove the stack list and status list of the removed agent
|
||||||
|
delete this.$root.allAgentStackList[endpoint];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
getStatusNum(statusName) {
|
getStatusNum(statusName) {
|
||||||
let num = 0;
|
let num = 0;
|
||||||
|
|
||||||
for (let stackName in this.$root.stackList) {
|
for (let stackName in this.$root.completeStackList) {
|
||||||
const stack = this.$root.stackList[stackName];
|
const stack = this.$root.completeStackList[stackName];
|
||||||
if (statusNameShort(stack.status) === statusName) {
|
if (statusNameShort(stack.status) === statusName) {
|
||||||
num += 1;
|
num += 1;
|
||||||
}
|
}
|
||||||
@ -230,4 +331,20 @@ table {
|
|||||||
font-family: 'JetBrains Mono', monospace;
|
font-family: 'JetBrains Mono', monospace;
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.first-row .shadow-box {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.remove-agent {
|
||||||
|
cursor: pointer;
|
||||||
|
color: rgba(255, 255, 255, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.agent {
|
||||||
|
a {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
@ -35,22 +35,33 @@ const routes = [
|
|||||||
component: Compose,
|
component: Compose,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/compose/:stackName",
|
path: "/compose/:stackName/:endpoint",
|
||||||
name: "compose",
|
component: Compose,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/compose/:stackName",
|
||||||
component: Compose,
|
component: Compose,
|
||||||
props: true,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/terminal/:stackName/:serviceName/:type",
|
path: "/terminal/:stackName/:serviceName/:type",
|
||||||
component: ContainerTerminal,
|
component: ContainerTerminal,
|
||||||
name: "containerTerminal",
|
name: "containerTerminal",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "/terminal/:stackName/:serviceName/:type/:endpoint",
|
||||||
|
component: ContainerTerminal,
|
||||||
|
name: "containerTerminalEndpoint",
|
||||||
|
},
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/console",
|
path: "/console",
|
||||||
component: Console,
|
component: Console,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "/console/:endpoint",
|
||||||
|
component: Console,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: "/settings",
|
path: "/settings",
|
||||||
component: Settings,
|
component: Settings,
|
||||||
|
@ -36,7 +36,7 @@ textarea.form-control {
|
|||||||
}
|
}
|
||||||
|
|
||||||
::placeholder {
|
::placeholder {
|
||||||
color: $dark-font-color3 !important;
|
color: $dark-font-color3;
|
||||||
}
|
}
|
||||||
|
|
||||||
.incident a,
|
.incident a,
|
||||||
@ -422,9 +422,8 @@ optgroup {
|
|||||||
// Floating Label
|
// Floating Label
|
||||||
.form-floating > .form-control:focus ~ label::after, .form-floating > .form-control:not(:placeholder-shown) ~ label::after, .form-floating > .form-control-plaintext ~ label::after, .form-floating > .form-select ~ label::after {
|
.form-floating > .form-control:focus ~ label::after, .form-floating > .form-control:not(:placeholder-shown) ~ label::after, .form-floating > .form-control-plaintext ~ label::after, .form-floating > .form-select ~ label::after {
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-floating > label {
|
.form-floating > label {
|
||||||
.dark & {
|
.dark & {
|
||||||
color: $dark-font-color3 !important;
|
color: $dark-font-color3 !important;
|
||||||
@ -657,13 +656,6 @@ $shadow-box-padding: 20px;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.main-terminal {
|
|
||||||
.xterm-viewport {
|
|
||||||
border-radius: 10px;
|
|
||||||
background-color: $dark-bg !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
code {
|
code {
|
||||||
padding: .2em .4em;
|
padding: .2em .4em;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
10520
package-lock.json
generated
Normal file
10520
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
73
package.json
73
package.json
@ -1,22 +1,25 @@
|
|||||||
{
|
{
|
||||||
"name": "dockge",
|
"name": "dockge",
|
||||||
"version": "1.3.0",
|
"version": "1.4.2",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 18.0.0 && <= 18.17.1"
|
"node": ">= 22.14.0"
|
||||||
},
|
},
|
||||||
"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 && npm run dev:backend \" \"npm 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 && npm run build:frontend && npm run build:docker",
|
||||||
|
"release-beta": "tsx ./extra/test-docker.ts && tsx extra/update-version.ts && npm run build:frontend && npm run build:docker-beta",
|
||||||
"build:frontend": "vite build --config ./frontend/vite.config.ts",
|
"build:frontend": "vite build --config ./frontend/vite.config.ts",
|
||||||
"build:docker-base": "docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/dockge:base -f ./docker/Base.Dockerfile . --push",
|
"build:docker-base": "docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/dockge:base -f ./docker/Base.Dockerfile . --push",
|
||||||
"build:docker": "node ./extra/env2arg.js docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/dockge:latest -t louislam/dockge:1 -t louislam/dockge:$VERSION --target release -f ./docker/Dockerfile . --push",
|
"build:docker": "node ./extra/env2arg.js docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/dockge:latest -t louislam/dockge:1 -t louislam/dockge:$VERSION -t louislam/dockge:beta -t louislam/dockge:nightly --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-beta": "node ./extra/env2arg.js docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/dockge:beta -t louislam/dockge:$VERSION --target release -f ./docker/Dockerfile . --push",
|
||||||
|
"build:docker-nightly": "npm run build:frontend && docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/dockge:nightly --target nightly -f ./docker/Dockerfile . --push",
|
||||||
"build:healthcheck": "docker buildx build -f docker/BuildHealthCheck.Dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/dockge:build-healthcheck . --push",
|
"build:healthcheck": "docker buildx build -f docker/BuildHealthCheck.Dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/dockge:build-healthcheck . --push",
|
||||||
"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",
|
||||||
@ -24,69 +27,75 @@
|
|||||||
"reset-password": "tsx ./extra/reset-password.ts"
|
"reset-password": "tsx ./extra/reset-password.ts"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@homebridge/node-pty-prebuilt-multiarch": "~0.11.11",
|
"@homebridge/node-pty-prebuilt-multiarch": "0.11.14",
|
||||||
|
"@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.10",
|
||||||
"command-exists": "~1.2.9",
|
"command-exists": "~1.2.9",
|
||||||
"compare-versions": "~6.1.0",
|
"compare-versions": "~6.1.1",
|
||||||
"composerize": "~1.4.1",
|
"composerize": "~1.7.1",
|
||||||
"croner": "~7.0.5",
|
"croner": "~8.1.2",
|
||||||
"dayjs": "~1.11.10",
|
"dayjs": "~1.11.13",
|
||||||
"dotenv": "~16.3.1",
|
"dotenv": "~16.3.2",
|
||||||
"express": "~4.18.2",
|
"express": "~4.21.2",
|
||||||
"express-static-gzip": "~2.1.7",
|
"express-static-gzip": "~2.1.8",
|
||||||
"http-graceful-shutdown": "~3.1.13",
|
"http-graceful-shutdown": "~3.1.14",
|
||||||
"jsonwebtoken": "~9.0.2",
|
"jsonwebtoken": "~9.0.2",
|
||||||
"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.3",
|
"mysql2": "~3.12.0",
|
||||||
"promisify-child-process": "~4.1.2",
|
"promisify-child-process": "~4.1.2",
|
||||||
"redbean-node": "~0.3.3",
|
"redbean-node": "~0.3.3",
|
||||||
"socket.io": "~4.7.2",
|
"semver": "^7.7.1",
|
||||||
"socket.io-client": "~4.7.2",
|
"socket.io": "~4.8.1",
|
||||||
"timezones-list": "~3.0.2",
|
"socket.io-client": "~4.8.1",
|
||||||
|
"timezones-list": "~3.0.3",
|
||||||
"ts-command-line-args": "~2.5.1",
|
"ts-command-line-args": "~2.5.1",
|
||||||
"tsx": "~3.14.0",
|
"tsx": "~4.19.3",
|
||||||
"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.17",
|
"@fontsource/jetbrains-mono": "^5.2.5",
|
||||||
"@fortawesome/fontawesome-svg-core": "6.4.2",
|
"@fortawesome/fontawesome-svg-core": "6.4.2",
|
||||||
"@fortawesome/free-regular-svg-icons": "6.4.2",
|
"@fortawesome/free-regular-svg-icons": "6.4.2",
|
||||||
"@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.9",
|
"@types/bootstrap": "~5.2.10",
|
||||||
"@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.9",
|
||||||
|
"@types/semver": "^7.7.0",
|
||||||
"@typescript-eslint/eslint-plugin": "~6.8.0",
|
"@typescript-eslint/eslint-plugin": "~6.8.0",
|
||||||
"@typescript-eslint/parser": "~6.8.0",
|
"@typescript-eslint/parser": "~6.8.0",
|
||||||
"@vitejs/plugin-vue": "~4.5.0",
|
"@vitejs/plugin-vue": "~5.2.3",
|
||||||
|
"@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",
|
||||||
"eslint-plugin-vue": "~9.17.0",
|
"eslint-plugin-vue": "~9.32.0",
|
||||||
"prismjs": "~1.29.0",
|
"prismjs": "~1.30.0",
|
||||||
"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.0",
|
"vite": "~5.4.15",
|
||||||
"vite-plugin-compression": "~0.5.1",
|
"vite-plugin-compression": "~0.5.1",
|
||||||
"vue": "~3.3.8",
|
"vue": "~3.5.13",
|
||||||
"vue-eslint-parser": "~9.3.2",
|
"vue-eslint-parser": "~9.3.2",
|
||||||
"vue-i18n": "~9.5.0",
|
"vue-i18n": "~10.0.6",
|
||||||
"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.2",
|
||||||
"vue-router": "~4.2.5",
|
"vue-router": "~4.5.0",
|
||||||
"vue-toastification": "2.0.0-rc.5",
|
"vue-toastification": "2.0.0-rc.5",
|
||||||
"xterm": "5.4.0-beta.37",
|
"wait-on": "^7.2.0",
|
||||||
"xterm-addon-web-links": "~0.9.0"
|
"xterm-addon-web-links": "~0.9.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
5007
pnpm-lock.yaml
generated
5007
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@ -7,6 +7,7 @@
|
|||||||
"skipLibCheck": true
|
"skipLibCheck": true
|
||||||
},
|
},
|
||||||
"include": [
|
"include": [
|
||||||
"backend/**/*"
|
"backend/**/*",
|
||||||
|
"common/**/*"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user