forked from extern/dockge
Compare commits
99 Commits
docs
...
update-dep
Author | SHA1 | Date | |
---|---|---|---|
1ce74d1801 | |||
007eac7b58 | |||
b945ddea55 | |||
bd58de535e | |||
8c6bcef987 | |||
bec5460395 | |||
4e899dcf21 | |||
8296c7b18f | |||
607c908f2d | |||
bd5dd3c3ad | |||
6eca6dc59f | |||
54fb2c1ef4 | |||
562abb485d | |||
86bed768ea | |||
b79db2375f | |||
b586cca711 | |||
793a9de50d | |||
0df3fee3f4 | |||
05b79ba50e | |||
a3c4082800 | |||
027d9e9b59 | |||
80876a463d | |||
beefe41264 | |||
e4f9b9c9fe | |||
a85813ab95 | |||
47debeddc9 | |||
9ce68f67fa | |||
c3b9db8549 | |||
39279cc7df | |||
f4eeb38d18 | |||
49187577ca | |||
7ffb36ec54 | |||
cd596b2d37 | |||
1711e4f47c | |||
2c0fb7f7e0 | |||
ef7f26b142 | |||
80b907577f | |||
e54211de1b | |||
fc42639cb5 | |||
fde2ef4869 | |||
d554adb0c7 | |||
c1e788d22a | |||
972179b4a8 | |||
d5e1ce51ed | |||
9155cf14a5 | |||
17eb6583df | |||
b717fc6655 | |||
da50860211 | |||
340ea0abe9 | |||
d53cb7ca71 | |||
e29eed4602 | |||
8e9f2209c2 | |||
a8d95d06b9 | |||
e2c81bd3e0 | |||
bf2d4c95f9 | |||
b4aa1b83a2 | |||
157a74aafc | |||
b7a2bab808 | |||
71446b9eb9 | |||
06dbc3fa28 | |||
a0fca4df4d | |||
694923cd42 | |||
bfaa8fd795 | |||
d5721dd8ca | |||
5c35b09e93 | |||
9a5d403219 | |||
9ca65ec94d | |||
5b02b63c95 | |||
88d33aace8 | |||
b5f6919bab | |||
0546f6a24e | |||
7385d216a3 | |||
631bc60cb2 | |||
d23e2d8aa1 | |||
457f038108 | |||
f862bbc7cd | |||
3d56846cd6 | |||
cff929c69d | |||
766e751522 | |||
45ab36db98 | |||
47435d41cd | |||
ee8f39699a | |||
204c776b0d | |||
724b5d6d7e | |||
766ecb070d | |||
d1d3a54377 | |||
d17a63fcab | |||
5454b44a1c | |||
9e8bccbf2f | |||
866fa380dd | |||
49b28d0e36 | |||
1e7dd0504b | |||
05191b14de | |||
81cacbdddd | |||
0279d431e0 | |||
0e768abfb4 | |||
62e952f1e6 | |||
f044c2e328 | |||
dc8787d204 |
5
.github/DISCUSSION_TEMPLATE/ask-for-help.yml
vendored
5
.github/DISCUSSION_TEMPLATE/ask-for-help.yml
vendored
@ -1,5 +1,4 @@
|
|||||||
title: "❓ Ask for help"
|
labels: [help]
|
||||||
labels:
|
|
||||||
body:
|
body:
|
||||||
- type: checkboxes
|
- type: checkboxes
|
||||||
id: no-duplicate-issues
|
id: no-duplicate-issues
|
||||||
@ -69,4 +68,4 @@ body:
|
|||||||
description: "If running with Node.js? which version are you running?"
|
description: "If running with Node.js? which version are you running?"
|
||||||
placeholder: "Ex. 14.18.0"
|
placeholder: "Ex. 14.18.0"
|
||||||
validations:
|
validations:
|
||||||
required: false
|
required: false
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
title: 🚀 Feature Request
|
|
||||||
labels: [feature-request]
|
labels: [feature-request]
|
||||||
body:
|
body:
|
||||||
- type: checkboxes
|
- type: checkboxes
|
||||||
@ -52,4 +51,4 @@ body:
|
|||||||
attributes:
|
attributes:
|
||||||
label: "📝 Additional Context"
|
label: "📝 Additional Context"
|
||||||
description: "Add any other context or screenshots about the feature request here."
|
description: "Add any other context or screenshots about the feature request here."
|
||||||
placeholder: "..."
|
placeholder: "..."
|
||||||
|
8
.github/ISSUE_TEMPLATE/ask-for-help.yaml
vendored
8
.github/ISSUE_TEMPLATE/ask-for-help.yaml
vendored
@ -1,14 +1,14 @@
|
|||||||
name: "❓ Ask for help"
|
name: "⚠️ Ask for help (Please go to the \"Discussions\" tab to submit a Help Request)"
|
||||||
description: "Please go to the Discussions tab to submit a Help Request"
|
description: "⚠️ Please go to the \"Discussions\" tab to submit a Help Request"
|
||||||
body:
|
body:
|
||||||
- type: markdown
|
- type: markdown
|
||||||
attributes:
|
attributes:
|
||||||
value: |
|
value: |
|
||||||
Please go to https://github.com/louislam/dockge/discussions/new?category=ask-for-help
|
⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ Please go to https://github.com/louislam/dockge/discussions/new?category=ask-for-help
|
||||||
- type: checkboxes
|
- type: checkboxes
|
||||||
id: no-duplicate-issues
|
id: no-duplicate-issues
|
||||||
attributes:
|
attributes:
|
||||||
label: "Issues are for bug reports only"
|
label: "Issues are for bug reports only, please go to the \"Discussions\" tab to submit a Feature Request"
|
||||||
options:
|
options:
|
||||||
- label: "I understand"
|
- label: "I understand"
|
||||||
required: true
|
required: true
|
||||||
|
8
.github/ISSUE_TEMPLATE/feature_request.yaml
vendored
8
.github/ISSUE_TEMPLATE/feature_request.yaml
vendored
@ -1,14 +1,14 @@
|
|||||||
name: 🚀 Feature Request
|
name: 🚀 Feature Request (Please go to the "Discussions" tab to submit a Feature Request)
|
||||||
description: "Please go to the Discussions tab to submit a Feature Request"
|
description: "⚠️ Please go to the \"Discussions\" tab to submit a Feature Request"
|
||||||
body:
|
body:
|
||||||
- type: markdown
|
- type: markdown
|
||||||
attributes:
|
attributes:
|
||||||
value: |
|
value: |
|
||||||
Please go to https://github.com/louislam/dockge/discussions/new?category=feature-request
|
⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ Please go to https://github.com/louislam/dockge/discussions/new?category=ask-for-help
|
||||||
- type: checkboxes
|
- type: checkboxes
|
||||||
id: no-duplicate-issues
|
id: no-duplicate-issues
|
||||||
attributes:
|
attributes:
|
||||||
label: "Issues are for bug reports only"
|
label: "Issues are for bug reports only, please go to the \"Discussions\" tab to submit a Feature Request"
|
||||||
options:
|
options:
|
||||||
- label: "I understand"
|
- label: "I understand"
|
||||||
required: true
|
required: true
|
||||||
|
7
.github/workflows/ci.yml
vendored
7
.github/workflows/ci.yml
vendored
@ -14,8 +14,8 @@ jobs:
|
|||||||
ci:
|
ci:
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
os: [ubuntu-latest, windows-latest, macos-latest]
|
os: [ubuntu-latest, windows-latest, macos-latest, ARM64]
|
||||||
node: [20.x] # Can be changed
|
node: [18.17.1] # Can be changed
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Code
|
- name: Checkout Code
|
||||||
@ -56,5 +56,8 @@ jobs:
|
|||||||
|
|
||||||
- name: Check Typescript
|
- name: Check Typescript
|
||||||
run: pnpm run check-ts
|
run: pnpm run check-ts
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
run: pnpm run build:frontend
|
||||||
# more things can be add later like tests etc..
|
# more things can be add later like tests etc..
|
||||||
|
|
||||||
|
29
.github/workflows/close-incorrect-issue.yml
vendored
29
.github/workflows/close-incorrect-issue.yml
vendored
@ -16,10 +16,27 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: Use Node.js ${{ matrix.node-version }}
|
- uses: pnpm/action-setup@v2
|
||||||
uses: actions/setup-node@v3
|
name: Install pnpm
|
||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node-version }}
|
version: 8
|
||||||
cache: 'npm'
|
run_install: false
|
||||||
- run: npm ci
|
|
||||||
- run: node extra/close-incorrect-issue.js ${{ secrets.GITHUB_TOKEN }} ${{ github.event.issue.number }} ${{ github.event.issue.user.login }}
|
- 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
|
||||||
|
run: node extra/close-incorrect-issue.js ${{ secrets.GITHUB_TOKEN }} ${{ github.event.issue.number }} ${{ github.event.issue.user.login }}
|
||||||
|
16
.github/workflows/prevent-file-change.yml
vendored
Normal file
16
.github/workflows/prevent-file-change.yml
vendored
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
name: Prevent File Change
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
check-file-changes:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Prevent file change
|
||||||
|
uses: xalvarez/prevent-file-change-action@v1
|
||||||
|
with:
|
||||||
|
githubToken: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
# Regex, /src/lang/*.json is not allowed to be changed, except for /src/lang/en.json
|
||||||
|
pattern: '^(?!frontend/src/lang/en\.json$)frontend/src/lang/.*\.json$'
|
||||||
|
trustedAuthors: UptimeKumaBot
|
@ -7,13 +7,15 @@ Here are some references:
|
|||||||
### ✅ Usually accepted:
|
### ✅ Usually accepted:
|
||||||
- Bug fix
|
- Bug fix
|
||||||
- Security fix
|
- Security fix
|
||||||
- Translation
|
- Adding new language files (see [these instructions](https://github.com/louislam/dockge/blob/master/frontend/src/lang/README.md))
|
||||||
|
- Adding new language keys: `$t("...")`
|
||||||
|
|
||||||
### ⚠️ Discussion required:
|
### ⚠️ Discussion required:
|
||||||
- Large pull requests
|
- Large pull requests
|
||||||
- New features
|
- New features
|
||||||
|
|
||||||
### ❌ Won't be merged:
|
### ❌ Won't be merged:
|
||||||
|
- A dedicated PR for translating existing languages (see [these instructions](https://github.com/louislam/dockge/blob/master/frontend/src/lang/README.md))
|
||||||
- Do not pass the auto-test
|
- Do not pass the auto-test
|
||||||
- Any breaking changes
|
- Any breaking changes
|
||||||
- Duplicated pull requests
|
- Duplicated pull requests
|
||||||
|
86
README.md
86
README.md
@ -34,12 +34,18 @@ View Video: https://youtu.be/AWAlOQeNpgU?t=48
|
|||||||
## 🔧 How to Install
|
## 🔧 How to Install
|
||||||
|
|
||||||
Requirements:
|
Requirements:
|
||||||
- [Docker CE](https://docs.docker.com/engine/install/) 20+ is recommended / Podman
|
- [Docker](https://docs.docker.com/engine/install/) 20+ / Podman
|
||||||
- (Docker only) [Docker Compose Plugin](https://docs.docker.com/compose/install/linux/)
|
|
||||||
- (Podman only) podman-docker (Debian: `apt install podman-docker`)
|
- (Podman only) podman-docker (Debian: `apt install podman-docker`)
|
||||||
- OS:
|
- OS:
|
||||||
- As long as you can run Docker CE / Podman, it should be fine, but:
|
- Major Linux distros that can run Docker/Podman such as:
|
||||||
- Debian/Raspbian Buster or lower is not supported, please upgrade to Bullseye or higher
|
- ✅ Ubuntu
|
||||||
|
- ✅ Debian (Bullseye or newer)
|
||||||
|
- ✅ Raspbian (Bullseye or newer)
|
||||||
|
- ✅ CentOS
|
||||||
|
- ✅ Fedora
|
||||||
|
- ✅ ArchLinux
|
||||||
|
- ❌ Debian/Raspbian Buster or lower is not supported
|
||||||
|
- ❌ Windows (Will be supported later)
|
||||||
- Arch: armv7, arm64, amd64 (a.k.a x86_64)
|
- Arch: armv7, arm64, amd64 (a.k.a x86_64)
|
||||||
|
|
||||||
### Basic
|
### Basic
|
||||||
@ -48,14 +54,14 @@ Requirements:
|
|||||||
- Default Port: 5001
|
- Default Port: 5001
|
||||||
|
|
||||||
```
|
```
|
||||||
# Create a directory that stores your stacks and stores dockge's compose.yaml
|
# Create directories that store your stacks and stores Dockge's stack
|
||||||
mkdir -p /opt/stacks /opt/dockge
|
mkdir -p /opt/stacks /opt/dockge
|
||||||
cd /opt/dockge
|
cd /opt/dockge
|
||||||
|
|
||||||
# Download the compose.yaml
|
# Download the compose.yaml
|
||||||
curl https://raw.githubusercontent.com/louislam/dockge/master/compose.yaml --output compose.yaml
|
curl https://raw.githubusercontent.com/louislam/dockge/master/compose.yaml --output compose.yaml
|
||||||
|
|
||||||
# Start the Server
|
# Start the server
|
||||||
docker compose up -d
|
docker compose up -d
|
||||||
|
|
||||||
# If you are using docker-compose V1 or Podman
|
# If you are using docker-compose V1 or Podman
|
||||||
@ -66,40 +72,24 @@ Dockge is now running on http://localhost:5001
|
|||||||
|
|
||||||
### Advanced
|
### Advanced
|
||||||
|
|
||||||
If you want to store your stacks in another directory, you can change the `DOCKGE_STACKS_DIR` environment variable and volumes.
|
If you want to store your stacks in another directory, you can generate your compose.yaml file by using the following URL with custom query strings.
|
||||||
|
|
||||||
```yaml
|
|
||||||
version: "3.8"
|
|
||||||
services:
|
|
||||||
dockge:
|
|
||||||
image: louislam/dockge:1
|
|
||||||
restart: unless-stopped
|
|
||||||
ports:
|
|
||||||
# Host Port:Container Port
|
|
||||||
- 5001:5001
|
|
||||||
volumes:
|
|
||||||
- /var/run/docker.sock:/var/run/docker.sock
|
|
||||||
- ./data:/app/data
|
|
||||||
|
|
||||||
# If you want to use private registries, you need to share the auth file with Dockge:
|
|
||||||
# - /root/.docker/:/root/.docker
|
|
||||||
|
|
||||||
# Your stacks directory in the host (The paths inside container must be the same as the host)
|
|
||||||
# ⚠️⚠️ If you did it wrong, your data could end up be written into a wrong path.
|
|
||||||
# ✔️✔️✔️✔️ CORRECT EXAMPLE: - /my-stacks:/my-stacks (Both paths match)
|
|
||||||
# ❌❌❌❌ WRONG EXAMPLE: - /docker:/my-stacks (Both paths do not match)
|
|
||||||
- /opt/stacks:/opt/stacks
|
|
||||||
environment:
|
|
||||||
# Tell Dockge where is your stacks directory
|
|
||||||
- DOCKGE_STACKS_DIR=/opt/stacks
|
|
||||||
```
|
```
|
||||||
|
# Download your compose.yaml
|
||||||
|
curl "https://dockge.kuma.pet/compose.yaml?port=5001&stacksPath=/opt/stacks" --output compose.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
- port=`5001`
|
||||||
|
- stacksPath=`/opt/stacks`
|
||||||
|
|
||||||
|
Interactive compose.yaml generator is available on:
|
||||||
|
https://dockge.kuma.pet
|
||||||
|
|
||||||
## How to Update
|
## How to Update
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd /opt/dockge
|
cd /opt/dockge
|
||||||
docker compose pull
|
docker compose pull && docker compose up -d
|
||||||
docker compose up -d
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Screenshots
|
## Screenshots
|
||||||
@ -122,7 +112,7 @@ docker compose up -d
|
|||||||
If you love this project, please consider giving it a ⭐.
|
If you love this project, please consider giving it a ⭐.
|
||||||
|
|
||||||
|
|
||||||
## 🗣️
|
## 🗣️ Community and Contribution
|
||||||
|
|
||||||
### Bug Report
|
### Bug Report
|
||||||
https://github.com/louislam/dockge/issues
|
https://github.com/louislam/dockge/issues
|
||||||
@ -130,15 +120,18 @@ https://github.com/louislam/dockge/issues
|
|||||||
### Ask for Help / Discussions
|
### Ask for Help / Discussions
|
||||||
https://github.com/louislam/dockge/discussions
|
https://github.com/louislam/dockge/discussions
|
||||||
|
|
||||||
## Translation
|
### Translation
|
||||||
|
|
||||||
If you want to translate Dockge into your language, please read [Translation Guide](https://github.com/louislam/dockge/blob/master/frontend/src/lang/README.md)
|
If you want to translate Dockge into your language, please read [Translation Guide](https://github.com/louislam/dockge/blob/master/frontend/src/lang/README.md)
|
||||||
|
|
||||||
|
### Create a Pull Request
|
||||||
|
|
||||||
|
Be sure to read the [guide](https://github.com/louislam/dockge/blob/master/CONTRIBUTING.md), as we don't accept all types of pull requests and don't want to waste your time.
|
||||||
|
|
||||||
## FAQ
|
## FAQ
|
||||||
|
|
||||||
#### "Dockge"?
|
#### "Dockge"?
|
||||||
|
|
||||||
"Dockge" is a coinage word which is created by myself. I 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`.
|
||||||
|
|
||||||
@ -155,17 +148,18 @@ 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
|
||||||
|
|
||||||
## More Ideas?
|
#### Is Dockge a Portainer replacement?
|
||||||
|
|
||||||
- Stats
|
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.
|
||||||
- File manager
|
|
||||||
- App store for yaml templates
|
|
||||||
- Get app icons
|
|
||||||
- Switch Docker context
|
|
||||||
- Support Dockerfile and build
|
|
||||||
- Support Docker swarm
|
|
||||||
|
|
||||||
|
If you want to manage your container with docker-compose only, the answer may be yes.
|
||||||
|
|
||||||
# Others
|
If you still need to manage something like docker networks, single containers, the answer may be no.
|
||||||
|
|
||||||
|
#### Can I install both Dockge and Portainer?
|
||||||
|
|
||||||
|
Yes, you can.
|
||||||
|
|
||||||
|
## Others
|
||||||
|
|
||||||
Dockge is built on top of [Compose V2](https://docs.docker.com/compose/migrate/). `compose.yaml` also known as `docker-compose.yml`.
|
Dockge is built on top of [Compose V2](https://docs.docker.com/compose/migrate/). `compose.yaml` also known as `docker-compose.yml`.
|
||||||
|
@ -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;
|
||||||
|
@ -1,3 +0,0 @@
|
|||||||
export class Docker {
|
|
||||||
|
|
||||||
}
|
|
@ -29,7 +29,10 @@ 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 childProcess from "child_process";
|
import childProcessAsync from "promisify-child-process";
|
||||||
|
import { Terminal } from "./terminal";
|
||||||
|
|
||||||
|
import "dotenv/config";
|
||||||
|
|
||||||
export class DockgeServer {
|
export class DockgeServer {
|
||||||
app : Express;
|
app : Express;
|
||||||
@ -149,9 +152,6 @@ export class DockgeServer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create all the necessary directories
|
|
||||||
this.initDataDir();
|
|
||||||
|
|
||||||
// Create express
|
// Create express
|
||||||
this.app = express();
|
this.app = express();
|
||||||
|
|
||||||
@ -230,6 +230,11 @@ export class DockgeServer {
|
|||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (isDev) {
|
||||||
|
setInterval(() => {
|
||||||
|
log.debug("terminal", "Terminal count: " + Terminal.getTerminalCount());
|
||||||
|
}, 5000);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async afterLogin(socket : DockgeSocket, user : User) {
|
async afterLogin(socket : DockgeSocket, user : User) {
|
||||||
@ -249,6 +254,9 @@ export class DockgeServer {
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
async serve() {
|
async serve() {
|
||||||
|
// Create all the necessary directories
|
||||||
|
this.initDataDir();
|
||||||
|
|
||||||
// Connect to database
|
// Connect to database
|
||||||
try {
|
try {
|
||||||
await Database.init(this);
|
await Database.init(this);
|
||||||
@ -285,21 +293,22 @@ export class DockgeServer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Listen
|
// Listen
|
||||||
this.httpServer.listen(5001, this.config.hostname, () => {
|
this.httpServer.listen(this.config.port, this.config.hostname, () => {
|
||||||
if (this.config.hostname) {
|
if (this.config.hostname) {
|
||||||
log.info( "server", `Listening on ${this.config.hostname}:${this.config.port}`);
|
log.info( "server", `Listening on ${this.config.hostname}:${this.config.port}`);
|
||||||
} else {
|
} else {
|
||||||
log.info("server", `Listening on ${this.config.port}`);
|
log.info("server", `Listening on ${this.config.port}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run every 5 seconds
|
// Run every 10 seconds
|
||||||
Cron("*/2 * * * * *", {
|
Cron("*/10 * * * * *", {
|
||||||
protect: true, // Enabled over-run protection.
|
protect: true, // Enabled over-run protection.
|
||||||
}, () => {
|
}, () => {
|
||||||
log.debug("server", "Cron job running");
|
//log.debug("server", "Cron job running");
|
||||||
this.sendStackList(true);
|
this.sendStackList(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
checkVersion.startInterval();
|
||||||
});
|
});
|
||||||
|
|
||||||
gracefulShutdown(this.httpServer, {
|
gracefulShutdown(this.httpServer, {
|
||||||
@ -477,7 +486,7 @@ export class DockgeServer {
|
|||||||
return jwtSecretBean;
|
return jwtSecretBean;
|
||||||
}
|
}
|
||||||
|
|
||||||
sendStackList(useCache = false) {
|
async sendStackList(useCache = false) {
|
||||||
let roomList = this.io.sockets.adapter.rooms.keys();
|
let roomList = this.io.sockets.adapter.rooms.keys();
|
||||||
let map : Map<string, object> | undefined;
|
let map : Map<string, object> | undefined;
|
||||||
|
|
||||||
@ -488,7 +497,7 @@ export class DockgeServer {
|
|||||||
// Get the list only if there is a room
|
// Get the list only if there is a room
|
||||||
if (!map) {
|
if (!map) {
|
||||||
map = new Map();
|
map = new Map();
|
||||||
let stackList = Stack.getStackList(this, useCache);
|
let stackList = await Stack.getStackList(this, useCache);
|
||||||
|
|
||||||
for (let [ stackName, stack ] of stackList) {
|
for (let [ stackName, stack ] of stackList) {
|
||||||
map.set(stackName, stack.toSimpleJSON());
|
map.set(stackName, stack.toSimpleJSON());
|
||||||
@ -504,8 +513,8 @@ export class DockgeServer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sendStackStatusList() {
|
async sendStackStatusList() {
|
||||||
let statusList = Stack.getStatusList();
|
let statusList = await Stack.getStatusList();
|
||||||
|
|
||||||
let roomList = this.io.sockets.adapter.rooms.keys();
|
let roomList = this.io.sockets.adapter.rooms.keys();
|
||||||
|
|
||||||
@ -523,8 +532,15 @@ export class DockgeServer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getDockerNetworkList() : string[] {
|
async getDockerNetworkList() : Promise<string[]> {
|
||||||
let res = childProcess.spawnSync("docker", [ "network", "ls", "--format", "{{.Name}}" ]);
|
let res = await childProcessAsync.spawn("docker", [ "network", "ls", "--format", "{{.Name}}" ], {
|
||||||
|
encoding: "utf-8",
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!res.stdout) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
let list = res.stdout.toString().split("\n");
|
let list = res.stdout.toString().split("\n");
|
||||||
|
|
||||||
// Remove empty string item
|
// Remove empty string item
|
||||||
|
@ -9,10 +9,10 @@ import composerize from "composerize";
|
|||||||
export class DockerSocketHandler extends SocketHandler {
|
export class DockerSocketHandler extends SocketHandler {
|
||||||
create(socket : DockgeSocket, server : DockgeServer) {
|
create(socket : DockgeSocket, server : DockgeServer) {
|
||||||
|
|
||||||
socket.on("deployStack", async (name : unknown, composeYAML : unknown, isAdd : unknown, callback) => {
|
socket.on("deployStack", async (name : unknown, composeYAML : unknown, composeENV : unknown, isAdd : unknown, callback) => {
|
||||||
try {
|
try {
|
||||||
checkLogin(socket);
|
checkLogin(socket);
|
||||||
const stack = this.saveStack(socket, server, name, composeYAML, isAdd);
|
const stack = await this.saveStack(socket, server, name, composeYAML, composeENV, isAdd);
|
||||||
await stack.deploy(socket);
|
await stack.deploy(socket);
|
||||||
server.sendStackList();
|
server.sendStackList();
|
||||||
callback({
|
callback({
|
||||||
@ -25,10 +25,10 @@ export class DockerSocketHandler extends SocketHandler {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on("saveStack", async (name : unknown, composeYAML : unknown, isAdd : unknown, callback) => {
|
socket.on("saveStack", async (name : unknown, composeYAML : unknown, composeENV : unknown, isAdd : unknown, callback) => {
|
||||||
try {
|
try {
|
||||||
checkLogin(socket);
|
checkLogin(socket);
|
||||||
this.saveStack(socket, server, name, composeYAML, isAdd);
|
this.saveStack(socket, server, name, composeYAML, composeENV, isAdd);
|
||||||
callback({
|
callback({
|
||||||
ok: true,
|
ok: true,
|
||||||
"msg": "Saved"
|
"msg": "Saved"
|
||||||
@ -45,7 +45,7 @@ export class DockerSocketHandler extends SocketHandler {
|
|||||||
if (typeof(name) !== "string") {
|
if (typeof(name) !== "string") {
|
||||||
throw new ValidationError("Name must be a string");
|
throw new ValidationError("Name must be a string");
|
||||||
}
|
}
|
||||||
const stack = Stack.getStack(server, name);
|
const stack = await Stack.getStack(server, name);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await stack.delete(socket);
|
await stack.delete(socket);
|
||||||
@ -65,7 +65,7 @@ export class DockerSocketHandler extends SocketHandler {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on("getStack", (stackName : unknown, callback) => {
|
socket.on("getStack", async (stackName : unknown, callback) => {
|
||||||
try {
|
try {
|
||||||
checkLogin(socket);
|
checkLogin(socket);
|
||||||
|
|
||||||
@ -73,9 +73,11 @@ export class DockerSocketHandler extends SocketHandler {
|
|||||||
throw new ValidationError("Stack name must be a string");
|
throw new ValidationError("Stack name must be a string");
|
||||||
}
|
}
|
||||||
|
|
||||||
const stack = Stack.getStack(server, stackName);
|
const stack = await Stack.getStack(server, stackName);
|
||||||
|
|
||||||
stack.joinCombinedTerminal(socket);
|
if (stack.isManagedByDockge) {
|
||||||
|
stack.joinCombinedTerminal(socket);
|
||||||
|
}
|
||||||
|
|
||||||
callback({
|
callback({
|
||||||
ok: true,
|
ok: true,
|
||||||
@ -109,7 +111,7 @@ export class DockerSocketHandler extends SocketHandler {
|
|||||||
throw new ValidationError("Stack name must be a string");
|
throw new ValidationError("Stack name must be a string");
|
||||||
}
|
}
|
||||||
|
|
||||||
const stack = Stack.getStack(server, stackName);
|
const stack = await Stack.getStack(server, stackName);
|
||||||
await stack.start(socket);
|
await stack.start(socket);
|
||||||
callback({
|
callback({
|
||||||
ok: true,
|
ok: true,
|
||||||
@ -133,7 +135,7 @@ export class DockerSocketHandler extends SocketHandler {
|
|||||||
throw new ValidationError("Stack name must be a string");
|
throw new ValidationError("Stack name must be a string");
|
||||||
}
|
}
|
||||||
|
|
||||||
const stack = Stack.getStack(server, stackName);
|
const stack = await Stack.getStack(server, stackName);
|
||||||
await stack.stop(socket);
|
await stack.stop(socket);
|
||||||
callback({
|
callback({
|
||||||
ok: true,
|
ok: true,
|
||||||
@ -154,7 +156,7 @@ export class DockerSocketHandler extends SocketHandler {
|
|||||||
throw new ValidationError("Stack name must be a string");
|
throw new ValidationError("Stack name must be a string");
|
||||||
}
|
}
|
||||||
|
|
||||||
const stack = Stack.getStack(server, stackName);
|
const stack = await Stack.getStack(server, stackName);
|
||||||
await stack.restart(socket);
|
await stack.restart(socket);
|
||||||
callback({
|
callback({
|
||||||
ok: true,
|
ok: true,
|
||||||
@ -175,7 +177,7 @@ export class DockerSocketHandler extends SocketHandler {
|
|||||||
throw new ValidationError("Stack name must be a string");
|
throw new ValidationError("Stack name must be a string");
|
||||||
}
|
}
|
||||||
|
|
||||||
const stack = Stack.getStack(server, stackName);
|
const stack = await Stack.getStack(server, stackName);
|
||||||
await stack.update(socket);
|
await stack.update(socket);
|
||||||
callback({
|
callback({
|
||||||
ok: true,
|
ok: true,
|
||||||
@ -196,7 +198,7 @@ export class DockerSocketHandler extends SocketHandler {
|
|||||||
throw new ValidationError("Stack name must be a string");
|
throw new ValidationError("Stack name must be a string");
|
||||||
}
|
}
|
||||||
|
|
||||||
const stack = Stack.getStack(server, stackName);
|
const stack = await Stack.getStack(server, stackName);
|
||||||
await stack.down(socket);
|
await stack.down(socket);
|
||||||
callback({
|
callback({
|
||||||
ok: true,
|
ok: true,
|
||||||
@ -217,7 +219,7 @@ export class DockerSocketHandler extends SocketHandler {
|
|||||||
throw new ValidationError("Stack name must be a string");
|
throw new ValidationError("Stack name must be a string");
|
||||||
}
|
}
|
||||||
|
|
||||||
const stack = 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({
|
callback({
|
||||||
ok: true,
|
ok: true,
|
||||||
@ -232,7 +234,7 @@ export class DockerSocketHandler extends SocketHandler {
|
|||||||
socket.on("getDockerNetworkList", async (callback) => {
|
socket.on("getDockerNetworkList", async (callback) => {
|
||||||
try {
|
try {
|
||||||
checkLogin(socket);
|
checkLogin(socket);
|
||||||
const dockerNetworkList = server.getDockerNetworkList();
|
const dockerNetworkList = await server.getDockerNetworkList();
|
||||||
callback({
|
callback({
|
||||||
ok: true,
|
ok: true,
|
||||||
dockerNetworkList,
|
dockerNetworkList,
|
||||||
@ -262,7 +264,7 @@ export class DockerSocketHandler extends SocketHandler {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
saveStack(socket : DockgeSocket, server : DockgeServer, name : unknown, composeYAML : unknown, isAdd : unknown) : Stack {
|
async saveStack(socket : DockgeSocket, server : DockgeServer, name : unknown, composeYAML : unknown, composeENV : unknown, isAdd : unknown) : Promise<Stack> {
|
||||||
// Check types
|
// Check types
|
||||||
if (typeof(name) !== "string") {
|
if (typeof(name) !== "string") {
|
||||||
throw new ValidationError("Name must be a string");
|
throw new ValidationError("Name must be a string");
|
||||||
@ -270,12 +272,15 @@ export class DockerSocketHandler extends SocketHandler {
|
|||||||
if (typeof(composeYAML) !== "string") {
|
if (typeof(composeYAML) !== "string") {
|
||||||
throw new ValidationError("Compose YAML must be a string");
|
throw new ValidationError("Compose YAML must be a string");
|
||||||
}
|
}
|
||||||
|
if (typeof(composeENV) !== "string") {
|
||||||
|
throw new ValidationError("Compose ENV must be a string");
|
||||||
|
}
|
||||||
if (typeof(isAdd) !== "boolean") {
|
if (typeof(isAdd) !== "boolean") {
|
||||||
throw new ValidationError("isAdd must be a boolean");
|
throw new ValidationError("isAdd must be a boolean");
|
||||||
}
|
}
|
||||||
|
|
||||||
const stack = new Stack(server, name, composeYAML);
|
const stack = new Stack(server, name, composeYAML, composeENV, false);
|
||||||
stack.save(isAdd);
|
await stack.save(isAdd);
|
||||||
return stack;
|
return stack;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -101,7 +101,7 @@ export class TerminalSocketHandler extends SocketHandler {
|
|||||||
log.debug("interactiveTerminal", "Service name: " + serviceName);
|
log.debug("interactiveTerminal", "Service name: " + serviceName);
|
||||||
|
|
||||||
// Get stack
|
// Get stack
|
||||||
const stack = Stack.getStack(server, stackName);
|
const stack = await Stack.getStack(server, stackName);
|
||||||
stack.joinContainerTerminal(socket, serviceName, shell);
|
stack.joinContainerTerminal(socket, serviceName, shell);
|
||||||
|
|
||||||
callback({
|
callback({
|
||||||
@ -140,9 +140,26 @@ export class TerminalSocketHandler extends SocketHandler {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Close Terminal
|
// Leave Combined Terminal
|
||||||
socket.on("terminalClose", async (terminalName : unknown, callback : unknown) => {
|
socket.on("leaveCombinedTerminal", async (stackName : unknown, callback) => {
|
||||||
|
try {
|
||||||
|
checkLogin(socket);
|
||||||
|
|
||||||
|
log.debug("leaveCombinedTerminal", "Stack name: " + stackName);
|
||||||
|
|
||||||
|
if (typeof(stackName) !== "string") {
|
||||||
|
throw new ValidationError("Stack name must be a string.");
|
||||||
|
}
|
||||||
|
|
||||||
|
const stack = await Stack.getStack(server, stackName);
|
||||||
|
await stack.leaveCombinedTerminal(socket);
|
||||||
|
|
||||||
|
callback({
|
||||||
|
ok: true,
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
callbackError(e, callback);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO: Resize Terminal
|
// TODO: Resize Terminal
|
||||||
|
175
backend/stack.ts
175
backend/stack.ts
@ -1,8 +1,8 @@
|
|||||||
import { DockgeServer } from "./dockge-server";
|
import { DockgeServer } from "./dockge-server";
|
||||||
import fs from "fs";
|
import fs, { promises as fsAsync } from "fs";
|
||||||
import { log } from "./log";
|
import { log } from "./log";
|
||||||
import yaml from "yaml";
|
import yaml from "yaml";
|
||||||
import { DockgeSocket, ValidationError } from "./util-server";
|
import { DockgeSocket, fileExists, ValidationError } from "./util-server";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import {
|
import {
|
||||||
COMBINED_TERMINAL_COLS,
|
COMBINED_TERMINAL_COLS,
|
||||||
@ -16,13 +16,14 @@ import {
|
|||||||
UNKNOWN
|
UNKNOWN
|
||||||
} from "./util-common";
|
} from "./util-common";
|
||||||
import { InteractiveTerminal, Terminal } from "./terminal";
|
import { InteractiveTerminal, Terminal } from "./terminal";
|
||||||
import childProcess from "child_process";
|
import childProcessAsync from "promisify-child-process";
|
||||||
|
|
||||||
export class Stack {
|
export class Stack {
|
||||||
|
|
||||||
name: string;
|
name: string;
|
||||||
protected _status: number = UNKNOWN;
|
protected _status: number = UNKNOWN;
|
||||||
protected _composeYAML?: string;
|
protected _composeYAML?: string;
|
||||||
|
protected _composeENV?: string;
|
||||||
protected _configFilePath?: string;
|
protected _configFilePath?: string;
|
||||||
protected _composeFileName: string = "compose.yaml";
|
protected _composeFileName: string = "compose.yaml";
|
||||||
protected server: DockgeServer;
|
protected server: DockgeServer;
|
||||||
@ -31,10 +32,11 @@ export class Stack {
|
|||||||
|
|
||||||
protected static managedStackList: Map<string, Stack> = new Map();
|
protected static managedStackList: Map<string, Stack> = new Map();
|
||||||
|
|
||||||
constructor(server : DockgeServer, name : string, composeYAML? : string, skipFSOperations = false) {
|
constructor(server : DockgeServer, name : string, composeYAML? : string, composeENV? : string, skipFSOperations = false) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.server = server;
|
this.server = server;
|
||||||
this._composeYAML = composeYAML;
|
this._composeYAML = composeYAML;
|
||||||
|
this._composeENV = composeENV;
|
||||||
|
|
||||||
if (!skipFSOperations) {
|
if (!skipFSOperations) {
|
||||||
// Check if compose file name is different from compose.yaml
|
// Check if compose file name is different from compose.yaml
|
||||||
@ -53,6 +55,7 @@ export class Stack {
|
|||||||
return {
|
return {
|
||||||
...obj,
|
...obj,
|
||||||
composeYAML: this.composeYAML,
|
composeYAML: this.composeYAML,
|
||||||
|
composeENV: this.composeENV,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -69,11 +72,15 @@ export class Stack {
|
|||||||
/**
|
/**
|
||||||
* Get the status of the stack from `docker compose ps --format json`
|
* Get the status of the stack from `docker compose ps --format json`
|
||||||
*/
|
*/
|
||||||
ps() : object {
|
async ps() : Promise<object> {
|
||||||
let res = childProcess.execSync("docker compose ps --format json", {
|
let res = await childProcessAsync.spawn("docker", [ "compose", "ps", "--format", "json" ], {
|
||||||
cwd: this.path
|
cwd: this.path,
|
||||||
|
encoding: "utf-8",
|
||||||
});
|
});
|
||||||
return JSON.parse(res.toString());
|
if (!res.stdout) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
return JSON.parse(res.stdout.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
get isManagedByDockge() : boolean {
|
get isManagedByDockge() : boolean {
|
||||||
@ -92,6 +99,15 @@ export class Stack {
|
|||||||
|
|
||||||
// Check YAML format
|
// Check YAML format
|
||||||
yaml.parse(this.composeYAML);
|
yaml.parse(this.composeYAML);
|
||||||
|
|
||||||
|
let lines = this.composeENV.split("\n");
|
||||||
|
|
||||||
|
// Check if the .env is able to pass docker-compose
|
||||||
|
// Prevent "setenv: The parameter is incorrect"
|
||||||
|
// It only happens when there is one line and it doesn't contain "="
|
||||||
|
if (lines.length === 1 && !lines[0].includes("=") && lines[0].length > 0) {
|
||||||
|
throw new ValidationError("Invalid .env format");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get composeYAML() : string {
|
get composeYAML() : string {
|
||||||
@ -105,6 +121,17 @@ export class Stack {
|
|||||||
return this._composeYAML;
|
return this._composeYAML;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get composeENV() : string {
|
||||||
|
if (this._composeENV === undefined) {
|
||||||
|
try {
|
||||||
|
this._composeENV = fs.readFileSync(path.join(this.path, ".env"), "utf-8");
|
||||||
|
} catch (e) {
|
||||||
|
this._composeENV = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this._composeENV;
|
||||||
|
}
|
||||||
|
|
||||||
get path() : string {
|
get path() : string {
|
||||||
return path.join(this.server.stacksDir, this.name);
|
return path.join(this.server.stacksDir, this.name);
|
||||||
}
|
}
|
||||||
@ -128,27 +155,35 @@ export class Stack {
|
|||||||
* Save the stack to the disk
|
* Save the stack to the disk
|
||||||
* @param isAdd
|
* @param isAdd
|
||||||
*/
|
*/
|
||||||
save(isAdd : boolean) {
|
async save(isAdd : boolean) {
|
||||||
this.validate();
|
this.validate();
|
||||||
|
|
||||||
let dir = this.path;
|
let dir = this.path;
|
||||||
|
|
||||||
// Check if the name is used if isAdd
|
// Check if the name is used if isAdd
|
||||||
if (isAdd) {
|
if (isAdd) {
|
||||||
if (fs.existsSync(dir)) {
|
if (await fileExists(dir)) {
|
||||||
throw new ValidationError("Stack name already exists");
|
throw new ValidationError("Stack name already exists");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create the stack folder
|
// Create the stack folder
|
||||||
fs.mkdirSync(dir);
|
await fsAsync.mkdir(dir);
|
||||||
} else {
|
} else {
|
||||||
if (!fs.existsSync(dir)) {
|
if (!await fileExists(dir)) {
|
||||||
throw new ValidationError("Stack not found");
|
throw new ValidationError("Stack not found");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write or overwrite the compose.yaml
|
// Write or overwrite the compose.yaml
|
||||||
fs.writeFileSync(path.join(dir, this._composeFileName), this.composeYAML);
|
await fsAsync.writeFile(path.join(dir, this._composeFileName), this.composeYAML);
|
||||||
|
|
||||||
|
const envPath = path.join(dir, ".env");
|
||||||
|
|
||||||
|
// Write or overwrite the .env
|
||||||
|
// If .env is not existing and the composeENV is empty, we don't need to write it
|
||||||
|
if (await fileExists(envPath) || this.composeENV.trim() !== "") {
|
||||||
|
await fsAsync.writeFile(envPath, this.composeENV);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async deploy(socket? : DockgeSocket) : Promise<number> {
|
async deploy(socket? : DockgeSocket) : Promise<number> {
|
||||||
@ -168,7 +203,7 @@ export class Stack {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Remove the stack folder
|
// Remove the stack folder
|
||||||
fs.rmSync(this.path, {
|
await fsAsync.rm(this.path, {
|
||||||
recursive: true,
|
recursive: true,
|
||||||
force: true
|
force: true
|
||||||
});
|
});
|
||||||
@ -176,8 +211,8 @@ export class Stack {
|
|||||||
return exitCode;
|
return exitCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
updateStatus() {
|
async updateStatus() {
|
||||||
let statusList = Stack.getStatusList();
|
let statusList = await Stack.getStatusList();
|
||||||
let status = statusList.get(this.name);
|
let status = statusList.get(this.name);
|
||||||
|
|
||||||
if (status) {
|
if (status) {
|
||||||
@ -187,26 +222,27 @@ export class Stack {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static getStackList(server : DockgeServer, useCacheForManaged = false) : 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>;
|
||||||
|
|
||||||
|
// Use cached stack list?
|
||||||
if (useCacheForManaged && this.managedStackList.size > 0) {
|
if (useCacheForManaged && this.managedStackList.size > 0) {
|
||||||
stackList = this.managedStackList;
|
stackList = this.managedStackList;
|
||||||
} else {
|
} else {
|
||||||
stackList = new Map<string, Stack>();
|
stackList = new Map<string, Stack>();
|
||||||
|
|
||||||
// Scan the stacks directory, and get the stack list
|
// Scan the stacks directory, and get the stack list
|
||||||
let filenameList = fs.readdirSync(stacksDir);
|
let filenameList = await fsAsync.readdir(stacksDir);
|
||||||
|
|
||||||
for (let filename of filenameList) {
|
for (let filename of filenameList) {
|
||||||
try {
|
try {
|
||||||
// Check if it is a directory
|
// Check if it is a directory
|
||||||
let stat = fs.statSync(path.join(stacksDir, filename));
|
let stat = await fsAsync.stat(path.join(stacksDir, filename));
|
||||||
if (!stat.isDirectory()) {
|
if (!stat.isDirectory()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let stack = 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);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -220,22 +256,26 @@ export class Stack {
|
|||||||
this.managedStackList = new Map(stackList);
|
this.managedStackList = new Map(stackList);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Also get the list from `docker compose ls --all --format json`
|
// Get status from docker compose ls
|
||||||
let res = childProcess.execSync("docker compose ls --all --format json");
|
let res = await childProcessAsync.spawn("docker", [ "compose", "ls", "--all", "--format", "json" ], {
|
||||||
let composeList = JSON.parse(res.toString());
|
encoding: "utf-8",
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!res.stdout) {
|
||||||
|
return stackList;
|
||||||
|
}
|
||||||
|
|
||||||
|
let composeList = JSON.parse(res.stdout.toString());
|
||||||
|
|
||||||
for (let composeStack of composeList) {
|
for (let composeStack of composeList) {
|
||||||
|
|
||||||
// Skip the dockge stack
|
|
||||||
// TODO: Could be self managed?
|
|
||||||
if (composeStack.Name === "dockge") {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let stack = stackList.get(composeStack.Name);
|
let stack = stackList.get(composeStack.Name);
|
||||||
|
|
||||||
// This stack probably is not managed by Dockge, but we still want to show it
|
// This stack probably is not managed by Dockge, but we still want to show it
|
||||||
if (!stack) {
|
if (!stack) {
|
||||||
|
// Skip the dockge stack if it is not managed by Dockge
|
||||||
|
if (composeStack.Name === "dockge") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
stack = new Stack(server, composeStack.Name);
|
stack = new Stack(server, composeStack.Name);
|
||||||
stackList.set(composeStack.Name, stack);
|
stackList.set(composeStack.Name, stack);
|
||||||
}
|
}
|
||||||
@ -251,11 +291,18 @@ export class Stack {
|
|||||||
* Get the status list, it will be used to update the status of the stacks
|
* Get the status list, it will be used to update the status of the stacks
|
||||||
* Not all status will be returned, only the stack that is deployed or created to `docker compose` will be returned
|
* Not all status will be returned, only the stack that is deployed or created to `docker compose` will be returned
|
||||||
*/
|
*/
|
||||||
static getStatusList() : Map<string, number> {
|
static async getStatusList() : Promise<Map<string, number>> {
|
||||||
let statusList = new Map<string, number>();
|
let statusList = new Map<string, number>();
|
||||||
|
|
||||||
let res = childProcess.execSync("docker compose ls --all --format json");
|
let res = await childProcessAsync.spawn("docker", [ "compose", "ls", "--all", "--format", "json" ], {
|
||||||
let composeList = JSON.parse(res.toString());
|
encoding: "utf-8",
|
||||||
|
});
|
||||||
|
|
||||||
|
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));
|
||||||
@ -283,13 +330,13 @@ export class Stack {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static getStack(server: DockgeServer, stackName: string, skipFSOperations = false) : Stack {
|
static async getStack(server: DockgeServer, stackName: string, skipFSOperations = false) : Promise<Stack> {
|
||||||
let dir = path.join(server.stacksDir, stackName);
|
let dir = path.join(server.stacksDir, stackName);
|
||||||
|
|
||||||
if (!skipFSOperations) {
|
if (!skipFSOperations) {
|
||||||
if (!fs.existsSync(dir) || !fs.statSync(dir).isDirectory()) {
|
if (!await fileExists(dir) || !(await fsAsync.stat(dir)).isDirectory()) {
|
||||||
// Maybe it is a stack managed by docker compose directly
|
// Maybe it is a stack managed by docker compose directly
|
||||||
let stackList = this.getStackList(server, true);
|
let stackList = await this.getStackList(server, true);
|
||||||
let stack = stackList.get(stackName);
|
let stack = stackList.get(stackName);
|
||||||
|
|
||||||
if (stack) {
|
if (stack) {
|
||||||
@ -300,7 +347,7 @@ export class Stack {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
log.debug("getStack", "Skip FS operations");
|
//log.debug("getStack", "Skip FS operations");
|
||||||
}
|
}
|
||||||
|
|
||||||
let stack : Stack;
|
let stack : Stack;
|
||||||
@ -308,7 +355,7 @@ export class Stack {
|
|||||||
if (!skipFSOperations) {
|
if (!skipFSOperations) {
|
||||||
stack = new Stack(server, stackName);
|
stack = new Stack(server, stackName);
|
||||||
} else {
|
} else {
|
||||||
stack = new Stack(server, stackName, undefined, true);
|
stack = new Stack(server, stackName, undefined, undefined, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
stack._status = UNKNOWN;
|
stack._status = UNKNOWN;
|
||||||
@ -360,7 +407,7 @@ export class Stack {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If the stack is not running, we don't need to restart it
|
// If the stack is not running, we don't need to restart it
|
||||||
this.updateStatus();
|
await this.updateStatus();
|
||||||
log.debug("update", "Status: " + this.status);
|
log.debug("update", "Status: " + this.status);
|
||||||
if (this.status !== RUNNING) {
|
if (this.status !== RUNNING) {
|
||||||
return exitCode;
|
return exitCode;
|
||||||
@ -376,12 +423,21 @@ export class Stack {
|
|||||||
async joinCombinedTerminal(socket: DockgeSocket) {
|
async joinCombinedTerminal(socket: DockgeSocket) {
|
||||||
const terminalName = getCombinedTerminalName(this.name);
|
const terminalName = getCombinedTerminalName(this.name);
|
||||||
const terminal = Terminal.getOrCreateTerminal(this.server, terminalName, "docker", [ "compose", "logs", "-f", "--tail", "100" ], this.path);
|
const terminal = Terminal.getOrCreateTerminal(this.server, terminalName, "docker", [ "compose", "logs", "-f", "--tail", "100" ], this.path);
|
||||||
|
terminal.enableKeepAlive = true;
|
||||||
terminal.rows = COMBINED_TERMINAL_ROWS;
|
terminal.rows = COMBINED_TERMINAL_ROWS;
|
||||||
terminal.cols = COMBINED_TERMINAL_COLS;
|
terminal.cols = COMBINED_TERMINAL_COLS;
|
||||||
terminal.join(socket);
|
terminal.join(socket);
|
||||||
terminal.start();
|
terminal.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async leaveCombinedTerminal(socket: DockgeSocket) {
|
||||||
|
const terminalName = getCombinedTerminalName(this.name);
|
||||||
|
const terminal = Terminal.getTerminal(terminalName);
|
||||||
|
if (terminal) {
|
||||||
|
terminal.leave(socket);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async joinContainerTerminal(socket: DockgeSocket, serviceName: string, shell : string = "sh", index: number = 0) {
|
async joinContainerTerminal(socket: DockgeSocket, serviceName: string, shell : string = "sh", index: number = 0) {
|
||||||
const terminalName = getContainerExecTerminalName(this.name, serviceName, index);
|
const terminalName = getContainerExecTerminalName(this.name, serviceName, index);
|
||||||
let terminal = Terminal.getTerminal(terminalName);
|
let terminal = Terminal.getTerminal(terminalName);
|
||||||
@ -399,24 +455,35 @@ export class Stack {
|
|||||||
async getServiceStatusList() {
|
async getServiceStatusList() {
|
||||||
let statusList = new Map<string, number>();
|
let statusList = new Map<string, number>();
|
||||||
|
|
||||||
let res = childProcess.spawnSync("docker", [ "compose", "ps", "--format", "json" ], {
|
try {
|
||||||
cwd: this.path,
|
let res = await childProcessAsync.spawn("docker", [ "compose", "ps", "--format", "json" ], {
|
||||||
});
|
cwd: this.path,
|
||||||
|
encoding: "utf-8",
|
||||||
|
});
|
||||||
|
|
||||||
let lines = res.stdout.toString().split("\n");
|
if (!res.stdout) {
|
||||||
|
return statusList;
|
||||||
for (let line of lines) {
|
|
||||||
try {
|
|
||||||
let obj = JSON.parse(line);
|
|
||||||
if (obj.Health === "") {
|
|
||||||
statusList.set(obj.Service, obj.State);
|
|
||||||
} else {
|
|
||||||
statusList.set(obj.Service, obj.Health);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let lines = res.stdout?.toString().split("\n");
|
||||||
|
|
||||||
|
for (let line of lines) {
|
||||||
|
try {
|
||||||
|
let obj = JSON.parse(line);
|
||||||
|
if (obj.Health === "") {
|
||||||
|
statusList.set(obj.Service, obj.State);
|
||||||
|
} else {
|
||||||
|
statusList.set(obj.Service, obj.Health);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return statusList;
|
||||||
|
} catch (e) {
|
||||||
|
log.error("getServiceStatusList", e);
|
||||||
|
return statusList;
|
||||||
}
|
}
|
||||||
|
|
||||||
return statusList;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,8 +5,6 @@ import { LimitQueue } from "./utils/limit-queue";
|
|||||||
import { DockgeSocket } from "./util-server";
|
import { DockgeSocket } from "./util-server";
|
||||||
import {
|
import {
|
||||||
allowedCommandList, allowedRawKeys,
|
allowedCommandList, allowedRawKeys,
|
||||||
getComposeTerminalName,
|
|
||||||
getCryptoRandomInt,
|
|
||||||
PROGRESS_TERMINAL_ROWS,
|
PROGRESS_TERMINAL_ROWS,
|
||||||
TERMINAL_COLS,
|
TERMINAL_COLS,
|
||||||
TERMINAL_ROWS
|
TERMINAL_ROWS
|
||||||
@ -34,6 +32,9 @@ export class Terminal {
|
|||||||
protected _rows : number = TERMINAL_ROWS;
|
protected _rows : number = TERMINAL_ROWS;
|
||||||
protected _cols : number = TERMINAL_COLS;
|
protected _cols : number = TERMINAL_COLS;
|
||||||
|
|
||||||
|
public enableKeepAlive : boolean = false;
|
||||||
|
protected keepAliveInterval? : NodeJS.Timeout;
|
||||||
|
|
||||||
constructor(server : DockgeServer, name : string, file : string, args : string | string[], cwd : string) {
|
constructor(server : DockgeServer, name : string, file : string, args : string | string[], cwd : string) {
|
||||||
this.server = server;
|
this.server = server;
|
||||||
this._name = name;
|
this._name = name;
|
||||||
@ -80,6 +81,25 @@ export class Terminal {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.enableKeepAlive) {
|
||||||
|
log.debug("Terminal", "Keep alive enabled for terminal " + this.name);
|
||||||
|
|
||||||
|
// Close if there is no clients
|
||||||
|
this.keepAliveInterval = setInterval(() => {
|
||||||
|
const clients = this.server.io.sockets.adapter.rooms.get(this.name);
|
||||||
|
const numClients = clients ? clients.size : 0;
|
||||||
|
|
||||||
|
if (numClients === 0) {
|
||||||
|
log.debug("Terminal", "Terminal " + this.name + " has no client, closing...");
|
||||||
|
this.close();
|
||||||
|
} else {
|
||||||
|
log.debug("Terminal", "Terminal " + this.name + " has " + numClients + " client(s)");
|
||||||
|
}
|
||||||
|
}, 60 * 1000);
|
||||||
|
} else {
|
||||||
|
log.debug("Terminal", "Keep alive disabled for terminal " + this.name);
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
this._ptyProcess = pty.spawn(this.file, this.args, {
|
this._ptyProcess = pty.spawn(this.file, this.args, {
|
||||||
name: this.name,
|
name: this.name,
|
||||||
@ -100,6 +120,8 @@ export class Terminal {
|
|||||||
this._ptyProcess.onExit(this.exit);
|
this._ptyProcess.onExit(this.exit);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof Error) {
|
if (error instanceof Error) {
|
||||||
|
clearInterval(this.keepAliveInterval);
|
||||||
|
|
||||||
log.error("Terminal", "Failed to start terminal: " + error.message);
|
log.error("Terminal", "Failed to start terminal: " + error.message);
|
||||||
const exitCode = Number(error.message.split(" ").pop());
|
const exitCode = Number(error.message.split(" ").pop());
|
||||||
this.exit({
|
this.exit({
|
||||||
@ -122,6 +144,8 @@ export class Terminal {
|
|||||||
Terminal.terminalMap.delete(this.name);
|
Terminal.terminalMap.delete(this.name);
|
||||||
log.debug("Terminal", "Terminal " + this.name + " exited with code " + res.exitCode);
|
log.debug("Terminal", "Terminal " + this.name + " exited with code " + res.exitCode);
|
||||||
|
|
||||||
|
clearInterval(this.keepAliveInterval);
|
||||||
|
|
||||||
if (this.callback) {
|
if (this.callback) {
|
||||||
this.callback(res.exitCode);
|
this.callback(res.exitCode);
|
||||||
}
|
}
|
||||||
@ -158,7 +182,9 @@ export class Terminal {
|
|||||||
}
|
}
|
||||||
|
|
||||||
close() {
|
close() {
|
||||||
this._ptyProcess?.kill();
|
clearInterval(this.keepAliveInterval);
|
||||||
|
// Send Ctrl+C to the terminal
|
||||||
|
this.ptyProcess?.write("\x03");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -179,20 +205,30 @@ export class Terminal {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static exec(server : DockgeServer, socket : DockgeSocket | undefined, terminalName : string, file : string, args : string | string[], cwd : string) : Promise<number> {
|
public static exec(server : DockgeServer, socket : DockgeSocket | undefined, terminalName : string, file : string, args : string | string[], cwd : string) : Promise<number> {
|
||||||
const terminal = new Terminal(server, terminalName, file, args, cwd);
|
return new Promise((resolve, reject) => {
|
||||||
terminal.rows = PROGRESS_TERMINAL_ROWS;
|
// check if terminal exists
|
||||||
|
if (Terminal.terminalMap.has(terminalName)) {
|
||||||
|
reject("Another operation is already running, please try again later.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (socket) {
|
let terminal = new Terminal(server, terminalName, file, args, cwd);
|
||||||
terminal.join(socket);
|
terminal.rows = PROGRESS_TERMINAL_ROWS;
|
||||||
}
|
|
||||||
|
if (socket) {
|
||||||
|
terminal.join(socket);
|
||||||
|
}
|
||||||
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
terminal.onExit((exitCode : number) => {
|
terminal.onExit((exitCode : number) => {
|
||||||
resolve(exitCode);
|
resolve(exitCode);
|
||||||
});
|
});
|
||||||
terminal.start();
|
terminal.start();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static getTerminalCount() {
|
||||||
|
return Terminal.terminalMap.size;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1,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);
|
||||||
@ -203,7 +207,7 @@ export function getContainerTerminalName(container : string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function getContainerExecTerminalName(stackName : string, container : string, index : number) {
|
export function getContainerExecTerminalName(stackName : string, container : string, index : number) {
|
||||||
return "container-exec-" + container + "-" + index;
|
return "container-exec-" + stackName + "-" + container + "-" + index;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function copyYAMLComments(doc : Document, src : Document) {
|
export function copyYAMLComments(doc : Document, src : Document) {
|
||||||
@ -340,3 +344,48 @@ 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) {
|
||||||
|
item.value = envsubst(item.value, env);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// @ts-ignore
|
||||||
|
} else if (pair.value && typeof(pair.value.value) === "string") {
|
||||||
|
// @ts-ignore
|
||||||
|
pair.value.value = envsubst(pair.value.value, env);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -5,6 +5,7 @@ import { log } from "./log";
|
|||||||
import { ERROR_TYPE_VALIDATION } from "./util-common";
|
import { ERROR_TYPE_VALIDATION } from "./util-common";
|
||||||
import { R } from "redbean-node";
|
import { R } from "redbean-node";
|
||||||
import { verifyPassword } from "./password-hash";
|
import { verifyPassword } from "./password-hash";
|
||||||
|
import fs from "fs";
|
||||||
|
|
||||||
export interface JWTDecoded {
|
export interface JWTDecoded {
|
||||||
username : string;
|
username : string;
|
||||||
@ -82,3 +83,9 @@ export async function doubleCheckPassword(socket : DockgeSocket, currentPassword
|
|||||||
|
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function fileExists(file : string) {
|
||||||
|
return fs.promises.access(file, fs.constants.F_OK)
|
||||||
|
.then(() => true)
|
||||||
|
.catch(() => false);
|
||||||
|
}
|
||||||
|
@ -13,10 +13,10 @@ services:
|
|||||||
# If you want to use private registries, you need to share the auth file with Dockge:
|
# If you want to use private registries, you need to share the auth file with Dockge:
|
||||||
# - /root/.docker/:/root/.docker
|
# - /root/.docker/:/root/.docker
|
||||||
|
|
||||||
# Your stacks directory in the host (The paths inside container must be the same as the host)
|
# Stacks Directory
|
||||||
# ⚠️⚠️ If you did it wrong, your data could end up be written into a wrong path.
|
# ⚠️ READ IT CAREFULLY. If you did it wrong, your data could end up writing into a WRONG PATH.
|
||||||
# ✔️✔️✔️✔️ CORRECT: - /my-stacks:/my-stacks (Both paths match)
|
# ⚠️ 1. FULL path only. No relative path (MUST)
|
||||||
# ❌❌❌❌ WRONG: - /docker:/my-stacks (Both paths do not match)
|
# ⚠️ 2. Left Stacks Path === Right Stacks Path (MUST)
|
||||||
- /opt/stacks:/opt/stacks
|
- /opt/stacks:/opt/stacks
|
||||||
environment:
|
environment:
|
||||||
# Tell Dockge where is your stacks directory
|
# Tell Dockge where is your stacks directory
|
||||||
|
@ -1,9 +1,7 @@
|
|||||||
FROM node:20-bookworm-slim
|
# Due to the bug of #145, Node.js's version cannot be changed, unless upstream is fixed.
|
||||||
|
FROM node:18.17.1-bookworm-slim
|
||||||
ENV PNPM_HOME="/pnpm"
|
ENV PNPM_HOME="/pnpm"
|
||||||
ENV PATH="$PNPM_HOME:$PATH"
|
ENV PATH="$PNPM_HOME:$PATH"
|
||||||
|
|
||||||
# COPY --from=docker:dind /usr/local/bin/docker /usr/local/bin/
|
|
||||||
|
|
||||||
RUN apt update && apt install --yes --no-install-recommends \
|
RUN apt update && apt install --yes --no-install-recommends \
|
||||||
curl \
|
curl \
|
||||||
ca-certificates \
|
ca-certificates \
|
||||||
@ -24,16 +22,3 @@ RUN apt update && apt install --yes --no-install-recommends \
|
|||||||
&& rm -rf /var/lib/apt/lists/* \
|
&& rm -rf /var/lib/apt/lists/* \
|
||||||
&& npm install pnpm -g \
|
&& npm install pnpm -g \
|
||||||
&& pnpm install -g tsx
|
&& pnpm install -g tsx
|
||||||
|
|
||||||
# ensures that /var/run/docker.sock exists
|
|
||||||
# changes the ownership of /var/run/docker.sock
|
|
||||||
RUN touch /var/run/docker.sock && chown node:node /var/run/docker.sock
|
|
||||||
|
|
||||||
# Full Base Image
|
|
||||||
# MariaDB, Chromium and fonts
|
|
||||||
#FROM base-slim AS base
|
|
||||||
#ENV DOCKGE_ENABLE_EMBEDDED_MARIADB=1
|
|
||||||
#RUN apt update && \
|
|
||||||
# apt --yes --no-install-recommends install mariadb-server && \
|
|
||||||
# rm -rf /var/lib/apt/lists/* && \
|
|
||||||
# apt --yes autoremove
|
|
||||||
|
10
docker/BuildHealthCheck.Dockerfile
Normal file
10
docker/BuildHealthCheck.Dockerfile
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
############################################
|
||||||
|
# Build in Golang
|
||||||
|
############################################
|
||||||
|
FROM golang:1.21.4-bookworm
|
||||||
|
WORKDIR /app
|
||||||
|
ARG TARGETPLATFORM
|
||||||
|
COPY ./extra/healthcheck.go ./extra/healthcheck.go
|
||||||
|
|
||||||
|
# Compile healthcheck.go
|
||||||
|
RUN go build -x -o ./extra/healthcheck ./extra/healthcheck.go
|
@ -1,3 +1,8 @@
|
|||||||
|
############################################
|
||||||
|
# Healthcheck Binary
|
||||||
|
############################################
|
||||||
|
FROM louislam/dockge:build-healthcheck AS build_healthcheck
|
||||||
|
|
||||||
############################################
|
############################################
|
||||||
# Build
|
# Build
|
||||||
############################################
|
############################################
|
||||||
@ -12,16 +17,17 @@ RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --prod --frozen-l
|
|||||||
############################################
|
############################################
|
||||||
FROM louislam/dockge:base AS release
|
FROM louislam/dockge:base AS release
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY --chown=node:node . .
|
COPY --chown=node:node --from=build_healthcheck /app/extra/healthcheck /app/extra/healthcheck
|
||||||
COPY --from=build /app/node_modules /app/node_modules
|
COPY --from=build /app/node_modules /app/node_modules
|
||||||
|
COPY --chown=node:node . .
|
||||||
RUN mkdir ./data
|
RUN mkdir ./data
|
||||||
|
|
||||||
VOLUME /app/data
|
VOLUME /app/data
|
||||||
EXPOSE 5001
|
EXPOSE 5001
|
||||||
|
HEALTHCHECK --interval=60s --timeout=30s --start-period=60s --retries=5 CMD extra/healthcheck
|
||||||
ENTRYPOINT ["/usr/bin/dumb-init", "--"]
|
ENTRYPOINT ["/usr/bin/dumb-init", "--"]
|
||||||
CMD ["tsx", "./backend/index.ts"]
|
CMD ["tsx", "./backend/index.ts"]
|
||||||
|
|
||||||
|
|
||||||
############################################
|
############################################
|
||||||
# Mark as Nightly
|
# Mark as Nightly
|
||||||
############################################
|
############################################
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
const github = require("@actions/github");
|
import github from "@actions/github";
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
try {
|
try {
|
||||||
|
74
extra/healthcheck.go
Normal file
74
extra/healthcheck.go
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
/*
|
||||||
|
* If changed, have to run `npm run build-docker-builder-go`.
|
||||||
|
* This script should be run after a period of time (180s), because the server may need some time to prepare.
|
||||||
|
*/
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// Is K8S + "dockge" as the container name
|
||||||
|
// See https://github.com/louislam/uptime-kuma/pull/2083
|
||||||
|
isK8s := strings.HasPrefix(os.Getenv("DOCKGE_PORT"), "tcp://")
|
||||||
|
|
||||||
|
// process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
|
||||||
|
http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{
|
||||||
|
InsecureSkipVerify: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
client := http.Client{
|
||||||
|
Timeout: 28 * time.Second,
|
||||||
|
}
|
||||||
|
|
||||||
|
sslKey := os.Getenv("DOCKGE_SSL_KEY")
|
||||||
|
sslCert := os.Getenv("DOCKGE_SSL_CERT")
|
||||||
|
|
||||||
|
hostname := os.Getenv("DOCKGE_HOST")
|
||||||
|
if len(hostname) == 0 {
|
||||||
|
hostname = "127.0.0.1"
|
||||||
|
}
|
||||||
|
|
||||||
|
port := ""
|
||||||
|
// DOCKGE_PORT is override by K8S unexpectedly,
|
||||||
|
if !isK8s {
|
||||||
|
port = os.Getenv("DOCKGE_PORT")
|
||||||
|
}
|
||||||
|
if len(port) == 0 {
|
||||||
|
port = "5001"
|
||||||
|
}
|
||||||
|
|
||||||
|
protocol := ""
|
||||||
|
if len(sslKey) != 0 && len(sslCert) != 0 {
|
||||||
|
protocol = "https"
|
||||||
|
} else {
|
||||||
|
protocol = "http"
|
||||||
|
}
|
||||||
|
|
||||||
|
url := protocol + "://" + hostname + ":" + port
|
||||||
|
|
||||||
|
log.Println("Checking " + url)
|
||||||
|
resp, err := client.Get(url)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
_, err = ioutil.ReadAll(resp.Body)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("Health Check OK [Res Code: %d]\n", resp.StatusCode)
|
||||||
|
|
||||||
|
}
|
84
extra/reset-password.ts
Normal file
84
extra/reset-password.ts
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
import { Database } from "../backend/database";
|
||||||
|
import { R } from "redbean-node";
|
||||||
|
import readline from "readline";
|
||||||
|
import { User } from "../backend/models/user";
|
||||||
|
import { DockgeServer } from "../backend/dockge-server";
|
||||||
|
import { log } from "../backend/log";
|
||||||
|
|
||||||
|
console.log("== Dockge Reset Password Tool ==");
|
||||||
|
|
||||||
|
const rl = readline.createInterface({
|
||||||
|
input: process.stdin,
|
||||||
|
output: process.stdout
|
||||||
|
});
|
||||||
|
|
||||||
|
export const main = async () => {
|
||||||
|
const server = new DockgeServer();
|
||||||
|
|
||||||
|
// Check if
|
||||||
|
|
||||||
|
console.log("Connecting the database");
|
||||||
|
try {
|
||||||
|
await Database.init(server);
|
||||||
|
} catch (e) {
|
||||||
|
if (e instanceof Error) {
|
||||||
|
log.error("server", "Failed to connect to your database: " + e.message);
|
||||||
|
}
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// No need to actually reset the password for testing, just make sure no connection problem. It is ok for now.
|
||||||
|
if (!process.env.TEST_BACKEND) {
|
||||||
|
const user = await R.findOne("user");
|
||||||
|
if (! user) {
|
||||||
|
throw new Error("user not found, have you installed?");
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("Found user: " + user.username);
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
let password = await question("New Password: ");
|
||||||
|
let confirmPassword = await question("Confirm New Password: ");
|
||||||
|
|
||||||
|
if (password === confirmPassword) {
|
||||||
|
await User.resetPassword(user.id, password);
|
||||||
|
|
||||||
|
// Reset all sessions by reset jwt secret
|
||||||
|
await server.initJWTSecret();
|
||||||
|
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
console.log("Passwords do not match, please try again.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log("Password reset successfully.");
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
if (e instanceof Error) {
|
||||||
|
console.error("Error: " + e.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await Database.close();
|
||||||
|
rl.close();
|
||||||
|
|
||||||
|
console.log("Finished.");
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ask question of user
|
||||||
|
* @param question Question to ask
|
||||||
|
* @returns Users response
|
||||||
|
*/
|
||||||
|
function question(question : string) : Promise<string> {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
rl.question(question, (answer) => {
|
||||||
|
resolve(answer);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!process.env.TEST_BACKEND) {
|
||||||
|
main();
|
||||||
|
}
|
1
frontend/components.d.ts
vendored
1
frontend/components.d.ts
vendored
@ -12,7 +12,6 @@ declare module 'vue' {
|
|||||||
ArrayInput: typeof import('./src/components/ArrayInput.vue')['default']
|
ArrayInput: typeof import('./src/components/ArrayInput.vue')['default']
|
||||||
ArraySelect: typeof import('./src/components/ArraySelect.vue')['default']
|
ArraySelect: typeof import('./src/components/ArraySelect.vue')['default']
|
||||||
BDropdown: typeof import('bootstrap-vue-next')['BDropdown']
|
BDropdown: typeof import('bootstrap-vue-next')['BDropdown']
|
||||||
BDropdownDivider: typeof import('bootstrap-vue-next')['BDropdownDivider']
|
|
||||||
BDropdownItem: typeof import('bootstrap-vue-next')['BDropdownItem']
|
BDropdownItem: typeof import('bootstrap-vue-next')['BDropdownItem']
|
||||||
BModal: typeof import('bootstrap-vue-next')['BModal']
|
BModal: typeof import('bootstrap-vue-next')['BModal']
|
||||||
Confirm: typeof import('./src/components/Confirm.vue')['default']
|
Confirm: typeof import('./src/components/Confirm.vue')['default']
|
||||||
|
@ -30,6 +30,10 @@ export default {
|
|||||||
displayName: {
|
displayName: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true,
|
required: true,
|
||||||
|
},
|
||||||
|
objectType: {
|
||||||
|
type: String,
|
||||||
|
default: "service",
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
@ -41,8 +45,7 @@ export default {
|
|||||||
array() {
|
array() {
|
||||||
// Create the array if not exists, it should be safe.
|
// Create the array if not exists, it should be safe.
|
||||||
if (!this.service[this.name]) {
|
if (!this.service[this.name]) {
|
||||||
// eslint-disable-next-line vue/no-side-effects-in-computed-properties
|
return [];
|
||||||
this.service[this.name] = [];
|
|
||||||
}
|
}
|
||||||
return this.service[this.name];
|
return this.service[this.name];
|
||||||
},
|
},
|
||||||
@ -56,8 +59,24 @@ export default {
|
|||||||
return this.service[this.name] !== undefined;
|
return this.service[this.name] !== undefined;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Not a good name, but it is used to get the object.
|
||||||
|
*/
|
||||||
service() {
|
service() {
|
||||||
return this.$parent.$parent.service;
|
if (this.objectType === "service") {
|
||||||
|
// Used in Container.vue
|
||||||
|
return this.$parent.$parent.service;
|
||||||
|
} else if (this.objectType === "x-dockge") {
|
||||||
|
|
||||||
|
if (!this.$parent.$parent.jsonConfig["x-dockge"]) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Used in Compose.vue
|
||||||
|
return this.$parent.$parent.jsonConfig["x-dockge"];
|
||||||
|
} else {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
valid() {
|
valid() {
|
||||||
@ -81,6 +100,19 @@ export default {
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
addField() {
|
addField() {
|
||||||
|
|
||||||
|
// Create the object if not exists.
|
||||||
|
if (this.objectType === "x-dockge") {
|
||||||
|
if (!this.$parent.$parent.jsonConfig["x-dockge"]) {
|
||||||
|
this.$parent.$parent.jsonConfig["x-dockge"] = {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the array if not exists.
|
||||||
|
if (!this.service[this.name]) {
|
||||||
|
this.service[this.name] = [];
|
||||||
|
}
|
||||||
|
|
||||||
this.array.push("");
|
this.array.push("");
|
||||||
},
|
},
|
||||||
remove(index) {
|
remove(index) {
|
||||||
|
@ -49,8 +49,7 @@ export default {
|
|||||||
array() {
|
array() {
|
||||||
// Create the array if not exists, it should be safe.
|
// Create the array if not exists, it should be safe.
|
||||||
if (!this.service[this.name]) {
|
if (!this.service[this.name]) {
|
||||||
// eslint-disable-next-line vue/no-side-effects-in-computed-properties
|
return [];
|
||||||
this.service[this.name] = [];
|
|
||||||
}
|
}
|
||||||
return this.service[this.name];
|
return this.service[this.name];
|
||||||
},
|
},
|
||||||
@ -89,6 +88,10 @@ export default {
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
addField() {
|
addField() {
|
||||||
|
// Create the array if not exists.
|
||||||
|
if (!this.service[this.name]) {
|
||||||
|
this.service[this.name] = [];
|
||||||
|
}
|
||||||
this.array.push("");
|
this.array.push("");
|
||||||
},
|
},
|
||||||
remove(index) {
|
remove(index) {
|
||||||
|
@ -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>
|
||||||
@ -213,16 +213,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;
|
||||||
|
@ -152,6 +152,14 @@ export default {
|
|||||||
});
|
});
|
||||||
|
|
||||||
result.sort((m1, m2) => {
|
result.sort((m1, m2) => {
|
||||||
|
|
||||||
|
// sort by managed by dockge
|
||||||
|
if (m1.isManagedByDockge && !m2.isManagedByDockge) {
|
||||||
|
return -1;
|
||||||
|
} else if (!m1.isManagedByDockge && m2.isManagedByDockge) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
if (m1.status !== m2.status) {
|
if (m1.status !== m2.status) {
|
||||||
if (m2.status === RUNNING) {
|
if (m2.status === RUNNING) {
|
||||||
return 1;
|
return 1;
|
||||||
|
@ -45,11 +45,12 @@ export default {
|
|||||||
<style scoped>
|
<style scoped>
|
||||||
.badge {
|
.badge {
|
||||||
min-width: 62px;
|
min-width: 62px;
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.fixed-width {
|
.fixed-width {
|
||||||
width: 62px;
|
width: 62px;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -9,12 +9,24 @@ const languageList = {
|
|||||||
"fr": "Français",
|
"fr": "Français",
|
||||||
"pl-PL": "Polski",
|
"pl-PL": "Polski",
|
||||||
"pt": "Português",
|
"pt": "Português",
|
||||||
|
"pt-BR": "Português-Brasil",
|
||||||
"sl": "Slovenščina",
|
"sl": "Slovenščina",
|
||||||
"tr": "Türkçe",
|
"tr": "Türkçe",
|
||||||
"zh-CN": "简体中文",
|
"zh-CN": "简体中文",
|
||||||
|
"zh-TW": "繁體中文(台灣)",
|
||||||
"ur": "Urdu",
|
"ur": "Urdu",
|
||||||
"ko-KR": "한국어",
|
"ko-KR": "한국어",
|
||||||
"ru": "Русский"
|
"ru": "Русский",
|
||||||
|
"cs-CZ": "Čeština",
|
||||||
|
"ar": "العربية",
|
||||||
|
"th": "ไทย",
|
||||||
|
"it-IT": "Italiano",
|
||||||
|
"sv-SE": "Svenska",
|
||||||
|
"uk-UA": "Українська",
|
||||||
|
"da": "Dansk",
|
||||||
|
"ja": "日本語",
|
||||||
|
"nl": "Nederlands",
|
||||||
|
"ro": "Română",
|
||||||
};
|
};
|
||||||
|
|
||||||
let messages = {
|
let messages = {
|
||||||
@ -27,7 +39,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
|
||||||
|
@ -2,13 +2,18 @@
|
|||||||
|
|
||||||
A simple guide on how to translate `Dockge` in your native language.
|
A simple guide on how to translate `Dockge` in your native language.
|
||||||
|
|
||||||
|
## How to Translate
|
||||||
|
|
||||||
|
(11-26-2023) Updated
|
||||||
|
|
||||||
|
1. Go to <https://weblate.kuma.pet>
|
||||||
|
2. Register an account on Weblate
|
||||||
|
3. Make sure your GitHub email is matched with Weblate's account, so that it could show you as a contributor on GitHub
|
||||||
|
4. Choose your language on Weblate and start translating.
|
||||||
|
|
||||||
## How to add a new language in the dropdown
|
## How to add a new language in the dropdown
|
||||||
|
|
||||||
(11-21-2023) Updated
|
1. Add your Language at <https://weblate.kuma.pet/projects/dockge/dockge/>.
|
||||||
|
2. Find the language code (You can find it at the end of the URL)
|
||||||
1. Add your Language at `frontend/src/lang/` by creating a new file with your language Code, format: `zh-TW.json` .
|
|
||||||
2. Copy the content from `en.json` and make translations from that.
|
|
||||||
3. Add your language at the end of `languageList` in `frontend/src/i18n.ts`, format: `"zh-TW": "繁體中文 (台灣)"`,
|
3. Add your language at the end of `languageList` in `frontend/src/i18n.ts`, format: `"zh-TW": "繁體中文 (台灣)"`,
|
||||||
4. Commit to new branch and make a new Pull Request for me to approve.
|
4. Commit to new branch and make a new Pull Request for me to approve.
|
||||||
|
|
||||||
*Note:* Currently we are only accepting one Pull Request per Language Translate.
|
|
||||||
|
102
frontend/src/lang/ar.json
Normal file
102
frontend/src/lang/ar.json
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
{
|
||||||
|
"languageName": "العربية",
|
||||||
|
"Create your admin account": "إنشاء حساب المشرف",
|
||||||
|
"authIncorrectCreds": "اسم المستخدم أو كلمة المرور غير صحيحة.",
|
||||||
|
"PasswordsDoNotMatch": "كلمة المرور غير مطابقة.",
|
||||||
|
"Repeat Password": "أعد كتابة كلمة السر",
|
||||||
|
"Create": "إنشاء",
|
||||||
|
"signedInDisp": "تم تسجيل الدخول باسم {0}",
|
||||||
|
"signedInDispDisabled": "تم تعطيل المصادقة.",
|
||||||
|
"home": "الرئيسية",
|
||||||
|
"console": "سطر الأوامر",
|
||||||
|
"registry": "السجل",
|
||||||
|
"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>؟",
|
||||||
|
"disableauth.message2": "إنه مصمم للحالات <strong>التي تنوي فيها مصادقة الطرف الثالث</strong> أمام Dockge مثل Cloudflare Access, Authelia أو أي من آليات المصادقة الأخرى.",
|
||||||
|
"passwordNotMatchMsg": "كلمة المرور المكررة غير متطابقة.",
|
||||||
|
"autoGet": "الجلب التلقائي",
|
||||||
|
"add": "إضافة",
|
||||||
|
"Edit": "تعديل",
|
||||||
|
"applyToYAML": "تطبيق على YAML",
|
||||||
|
"createExternalNetwork": "إنشاء",
|
||||||
|
"addInternalNetwork": "إضافة",
|
||||||
|
"Save": "حفظ",
|
||||||
|
"Language": "اللغة",
|
||||||
|
"Current User": "المستخدم الحالي",
|
||||||
|
"Change Password": "تعديل كلمة المرور",
|
||||||
|
"Current Password": "كلمة المرور الحالية",
|
||||||
|
"New Password": "كلمة مرور جديدة",
|
||||||
|
"Repeat New Password": "أعد تكرار كلمة المرور",
|
||||||
|
"Update Password": "تحديث كلمة المرور",
|
||||||
|
"Advanced": "متقدم",
|
||||||
|
"Please use this option carefully!": "من فضلك استخدم هذا الخيار بعناية!",
|
||||||
|
"Enable Auth": "تفعيل المصادقة",
|
||||||
|
"Disable Auth": "تعطيل المصادقة",
|
||||||
|
"I understand, please disable": "أتفهم, أرجو التعطيل",
|
||||||
|
"Leave": "مغادرة",
|
||||||
|
"Frontend Version": "لإصدار الواجهة الأمامية",
|
||||||
|
"Check Update On GitHub": "تحق من التحديث على GitHub",
|
||||||
|
"Show update if available": "اعرض التحديث إذا كان متاحًا",
|
||||||
|
"Also check beta release": "تحقق أيضًا من إصدار النسخة التجريبية",
|
||||||
|
"Remember me": "تذكرني",
|
||||||
|
"Login": "تسجيل الدخول",
|
||||||
|
"Username": "اسم المستخدم",
|
||||||
|
"Password": "كلمة المرور",
|
||||||
|
"Settings": "الاعدادات",
|
||||||
|
"Logout": "تسجيل الخروج",
|
||||||
|
"Lowercase only": "أحرف صغيرة فقط",
|
||||||
|
"Convert to Compose": "تحويل إلى كومبوز",
|
||||||
|
"Docker Run": "تشغيل Docker",
|
||||||
|
"active": "نشيط",
|
||||||
|
"exited": "تم الخروج",
|
||||||
|
"inactive": "غير نشيط",
|
||||||
|
"Appearance": "المظهر",
|
||||||
|
"Security": "الأمان",
|
||||||
|
"About": "حول",
|
||||||
|
"Allowed commands:": "الأوامر المسموح بها:",
|
||||||
|
"Internal Networks": "الشبكات الداخلية",
|
||||||
|
"External Networks": "الشبكات الخارجية",
|
||||||
|
"No External Networks": "لا توجد شبكات خارجية",
|
||||||
|
"reverseProxyMsg2": "تحقق كيف يتم إعداده لمقبس ويب",
|
||||||
|
"Cannot connect to the socket server.": "تعذر الاتصال بخادم المقبس.",
|
||||||
|
"reconnecting...": "إعادة الاتصال…",
|
||||||
|
"url": "رابط | روابط",
|
||||||
|
"extra": "إضافات",
|
||||||
|
"reverseProxyMsg1": "هل تستدخم خادم عكسي؟",
|
||||||
|
"connecting...": "جاري الاتصال بخادم المقبس…"
|
||||||
|
}
|
95
frontend/src/lang/cs-CZ.json
Normal file
95
frontend/src/lang/cs-CZ.json
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
{
|
||||||
|
"languageName": "Čeština",
|
||||||
|
"Create your admin account": "Vytvořit účet administrátora",
|
||||||
|
"authIncorrectCreds": "Nesprávné uživatelské jméno nebo heslo.",
|
||||||
|
"PasswordsDoNotMatch": "Hesla se neshodují.",
|
||||||
|
"Repeat Password": "Opakujte heslo",
|
||||||
|
"Create": "Vytvořit",
|
||||||
|
"signedInDisp": "Přihlášen jako {0}",
|
||||||
|
"signedInDispDisabled": "Ověření zakázáno.",
|
||||||
|
"home": "Domů",
|
||||||
|
"console": "Konzole",
|
||||||
|
"registry": "Registry",
|
||||||
|
"compose": "Compose",
|
||||||
|
"addFirstStackMsg": "Vytvořte svůj první stack!",
|
||||||
|
"stackName": "Název stacku",
|
||||||
|
"deployStack": "Nainstalovat",
|
||||||
|
"deleteStack": "Smazat",
|
||||||
|
"stopStack": "Zastavit",
|
||||||
|
"restartStack": "Restartovat",
|
||||||
|
"updateStack": "Aktualizovat",
|
||||||
|
"startStack": "Spustit",
|
||||||
|
"downStack": "Zastavit a vypnout",
|
||||||
|
"editStack": "Upravit",
|
||||||
|
"discardStack": "Zahodit",
|
||||||
|
"saveStackDraft": "Uložit",
|
||||||
|
"notAvailableShort": "N/A",
|
||||||
|
"deleteStackMsg": "Opravdu chcete smazat tento stack?",
|
||||||
|
"stackNotManagedByDockgeMsg": "Tento stack není spravován systémem Dockge.",
|
||||||
|
"primaryHostname": "Primární název hostitele",
|
||||||
|
"general": "Obecné",
|
||||||
|
"container": "Kontejner | Kontejnery",
|
||||||
|
"scanFolder": "Prohledat složku se stacky",
|
||||||
|
"dockerImage": "Obrázek",
|
||||||
|
"restartPolicyUnlessStopped": "Pokud není zastaveno",
|
||||||
|
"restartPolicyAlways": "Vždy",
|
||||||
|
"restartPolicyOnFailure": "Při selhání",
|
||||||
|
"restartPolicyNo": "Ne",
|
||||||
|
"environmentVariable": "Proměnná prostředí | Proměnné prostředí",
|
||||||
|
"restartPolicy": "Politika restartu",
|
||||||
|
"containerName": "Název kontejneru",
|
||||||
|
"port": "Port | Porty",
|
||||||
|
"volume": "Svazek | Svazky",
|
||||||
|
"network": "Síť | Sítě",
|
||||||
|
"dependsOn": "Závisí na kontejneru | Závislosti na kontejneru",
|
||||||
|
"addListItem": "Přidat {0}",
|
||||||
|
"deleteContainer": "Smazat",
|
||||||
|
"addContainer": "Přidat kontejner",
|
||||||
|
"addNetwork": "Přidat síť",
|
||||||
|
"disableauth.message1": "Opravdu chcete <strong>zakázat ověřování</strong>?",
|
||||||
|
"disableauth.message2": "Je navrženo pro scénáře, kde <strong>plánujete implementovat ověřování třetí strany</strong> před Dockge, například Cloudflare Access, Authelia nebo jiné ověřovací mechanismy.",
|
||||||
|
"passwordNotMatchMsg": "Hesla se neshodují.",
|
||||||
|
"autoGet": "Automaticky získat",
|
||||||
|
"add": "Přidat",
|
||||||
|
"Edit": "Upravit",
|
||||||
|
"applyToYAML": "Použít na YAML",
|
||||||
|
"createExternalNetwork": "Vytvořit",
|
||||||
|
"addInternalNetwork": "Přidat",
|
||||||
|
"Save": "Uložit",
|
||||||
|
"Language": "Jazyk",
|
||||||
|
"Current User": "Aktuální uživatel",
|
||||||
|
"Change Password": "Změnit heslo",
|
||||||
|
"Current Password": "Aktuální heslo",
|
||||||
|
"New Password": "Nové heslo",
|
||||||
|
"Repeat New Password": "Opakujte nové heslo",
|
||||||
|
"Update Password": "Aktualizovat heslo",
|
||||||
|
"Advanced": "Pokročilé",
|
||||||
|
"Please use this option carefully!": "Používejte tuto možnost opatrně!",
|
||||||
|
"Enable Auth": "Povolit ověřování",
|
||||||
|
"Disable Auth": "Zakázat ověřování",
|
||||||
|
"I understand, please disable": "Rozumím, prosím zakážte",
|
||||||
|
"Leave": "Opustit",
|
||||||
|
"Frontend Version": "Verze rozhraní",
|
||||||
|
"Check Update On GitHub": "Zkontrolovat aktualizaci na GitHubu",
|
||||||
|
"Show update if available": "Zobrazit aktualizaci, pokud je k dispozici",
|
||||||
|
"Also check beta release": "Zkontrolovat také beta verzi",
|
||||||
|
"Remember me": "Zapamatovat údaje",
|
||||||
|
"Login": "Přihlásit se",
|
||||||
|
"Username": "Uživatelské jméno",
|
||||||
|
"Password": "Heslo",
|
||||||
|
"Settings": "Nastavení",
|
||||||
|
"Logout": "Odhlásit se",
|
||||||
|
"Lowercase only": "Pouze malá písmena",
|
||||||
|
"Convert to Compose": "Převést na Compose",
|
||||||
|
"Docker Run": "Docker Run",
|
||||||
|
"active": "Aktivní",
|
||||||
|
"exited": "Ukončený",
|
||||||
|
"inactive": "Neaktivní",
|
||||||
|
"Appearance": "Vzhled",
|
||||||
|
"Security": "Zabezpečení",
|
||||||
|
"About": "O aplikaci",
|
||||||
|
"Allowed commands:": "Povolené příkazy:",
|
||||||
|
"Internal Networks": "Interní sítě",
|
||||||
|
"External Networks": "Externí sítě",
|
||||||
|
"No External Networks": "Žádné externí sítě"
|
||||||
|
}
|
102
frontend/src/lang/da.json
Normal file
102
frontend/src/lang/da.json
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
{
|
||||||
|
"languageName": "Dansk",
|
||||||
|
"authIncorrectCreds": "Forkert brugernavn eller adgangskode.",
|
||||||
|
"PasswordsDoNotMatch": "Adgangskode stemmer ikke overens.",
|
||||||
|
"Repeat Password": "Gentag adgangskode",
|
||||||
|
"Create": "Opret",
|
||||||
|
"signedInDisp": "Logget ind som {0}",
|
||||||
|
"signedInDispDisabled": "Auth Deaktiveret.",
|
||||||
|
"home": "Hjem",
|
||||||
|
"console": "Konsol",
|
||||||
|
"registry": "Registry",
|
||||||
|
"compose": "Compose",
|
||||||
|
"stackName": "Stack-navn",
|
||||||
|
"deployStack": "Udrulle",
|
||||||
|
"deleteStack": "Slet",
|
||||||
|
"stopStack": "Stop",
|
||||||
|
"restartStack": "Genstart",
|
||||||
|
"updateStack": "Opdatere",
|
||||||
|
"startStack": "Start",
|
||||||
|
"downStack": "Stop & Sluk",
|
||||||
|
"editStack": "Editere",
|
||||||
|
"discardStack": "Annuller",
|
||||||
|
"saveStackDraft": "Gem",
|
||||||
|
"notAvailableShort": "Ugyldig",
|
||||||
|
"stackNotManagedByDockgeMsg": "Denne stack administreres ikke af Dockge.",
|
||||||
|
"primaryHostname": "Primært værtsnavn",
|
||||||
|
"general": "Generelt",
|
||||||
|
"container": "Container | Containere",
|
||||||
|
"scanFolder": "Scan Stack-mappe",
|
||||||
|
"dockerImage": "Billede",
|
||||||
|
"restartPolicyUnlessStopped": "Medmindre stoppet",
|
||||||
|
"restartPolicyAlways": "Altid",
|
||||||
|
"restartPolicyOnFailure": "Ved fejl",
|
||||||
|
"restartPolicyNo": "Nej",
|
||||||
|
"restartPolicy": "Genstart politik",
|
||||||
|
"containerName": "Container navn",
|
||||||
|
"port": "Port | Porte",
|
||||||
|
"volume": "Volumen | Voluminer",
|
||||||
|
"network": "Netværk | Netværker",
|
||||||
|
"dependsOn": "Container Dependency | Container Dependencies",
|
||||||
|
"addListItem": "Tilføj {0}",
|
||||||
|
"deleteContainer": "Slet",
|
||||||
|
"addNetwork": "Tilføj Netværk",
|
||||||
|
"passwordNotMatchMsg": "Koden du gentog stemmer ikke overens.",
|
||||||
|
"autoGet": "Auto Get",
|
||||||
|
"add": "Tilføj",
|
||||||
|
"Edit": "Redigere",
|
||||||
|
"applyToYAML": "Anvend til YAML",
|
||||||
|
"createExternalNetwork": "Skabe",
|
||||||
|
"addInternalNetwork": "Tilføj",
|
||||||
|
"Save": "Gem",
|
||||||
|
"Language": "Sprog",
|
||||||
|
"Current User": "Nuværende bruger",
|
||||||
|
"Change Password": "Ændre adgangskode",
|
||||||
|
"Current Password": "Nuværende adgangskode",
|
||||||
|
"New Password": "Ny adgangskode",
|
||||||
|
"Repeat New Password": "Gentag ny adgangskode",
|
||||||
|
"Update Password": "Opdater adgangskode",
|
||||||
|
"Advanced": "Avanceret",
|
||||||
|
"Please use this option carefully!": "Brug venligst denne indstilling forsigtigt!",
|
||||||
|
"Enable Auth": "Aktiver Auth",
|
||||||
|
"Disable Auth": "Deaktiver Auth",
|
||||||
|
"I understand, please disable": "Jeg forstår, venligst deaktiver",
|
||||||
|
"Leave": "Forlad",
|
||||||
|
"Frontend Version": "Frontend Version",
|
||||||
|
"Check Update On GitHub": "Tjek opdatering på GitHub",
|
||||||
|
"Also check beta release": "Tjek også betaversionen",
|
||||||
|
"Remember me": "Husk mig",
|
||||||
|
"Login": "Login",
|
||||||
|
"Username": "Brugernavn",
|
||||||
|
"Password": "Adgangskode",
|
||||||
|
"Settings": "Indstillinger",
|
||||||
|
"Logout": "Log ud",
|
||||||
|
"Convert to Compose": "Konverter til Compose",
|
||||||
|
"active": "aktive",
|
||||||
|
"exited": "forladt",
|
||||||
|
"inactive": "inaktive",
|
||||||
|
"Appearance": "Udseende",
|
||||||
|
"Security": "Sikkerhed",
|
||||||
|
"Docker Run": "Docker Kør",
|
||||||
|
"About": "Om",
|
||||||
|
"Allowed commands:": "Tilladte kommandoer:",
|
||||||
|
"Internal Networks": "Interne netværk",
|
||||||
|
"External Networks": "Eksterne netværk",
|
||||||
|
"No External Networks": "Ingen eksterne netværk",
|
||||||
|
"reverseProxyMsg1": "Bruger du en Reverse-Proxy?",
|
||||||
|
"reverseProxyMsg2": "Tjek, hvordan du konfigurerer det til WebSocket",
|
||||||
|
"Cannot connect to the socket server.": "Kan ikke oprette forbindelse til socket-serveren.",
|
||||||
|
"reconnecting...": "Genopretter forbindelse…",
|
||||||
|
"connecting...": "Opretter forbindelse til socket-serveren…",
|
||||||
|
"url": "URL | URL'er",
|
||||||
|
"extra": "Ekstra",
|
||||||
|
"Create your admin account": "Opret din administratorkonto",
|
||||||
|
"addFirstStackMsg": "Compose din første stack!",
|
||||||
|
"deleteStackMsg": "Er du sikker på, at du vil slette denne stack?",
|
||||||
|
"environmentVariable": "Miljøvariabel | miljøvariabler",
|
||||||
|
"addContainer": "Tilføj Container",
|
||||||
|
"disableauth.message1": "Er du sikker på, at du vil <strong>deaktivere godkendelse</strong>?",
|
||||||
|
"disableauth.message2": "Det er designet til scenarier <strong>hvor du har til hensigt at implementere tredjepartsgodkendelse</strong> foran Dockge såsom Cloudflare Access, Authelia eller andre godkendelsesmekanismer.",
|
||||||
|
"Show update if available": "Vis opdatering, hvis tilgængelig",
|
||||||
|
"Lowercase only": "Kun små bogstaver"
|
||||||
|
}
|
@ -6,14 +6,14 @@
|
|||||||
"Repeat Password": "Passwort wiederholen",
|
"Repeat Password": "Passwort wiederholen",
|
||||||
"Create": "Erstellen",
|
"Create": "Erstellen",
|
||||||
"signedInDisp": "Angemeldet als {0}",
|
"signedInDisp": "Angemeldet als {0}",
|
||||||
"signedInDispDisabled": "Authentifizierung deaktiviert.",
|
"signedInDispDisabled": "Anmeldung deaktiviert.",
|
||||||
"home": "Startseite",
|
"home": "Startseite",
|
||||||
"console": "Konsole",
|
"console": "Konsole",
|
||||||
"registry": "Register",
|
"registry": "Container Registry",
|
||||||
"compose": "Zusammenstellen",
|
"compose": "",
|
||||||
"addFirstStackMsg": "Stelle deinen ersten Stack zusammen!",
|
"addFirstStackMsg": "Stelle deinen ersten Stack zusammen!",
|
||||||
"stackName" : "Stack-Name",
|
"stackName": "Stack-Name",
|
||||||
"deployStack": "Bereitstellen",
|
"deployStack": "Deployen",
|
||||||
"deleteStack": "Löschen",
|
"deleteStack": "Löschen",
|
||||||
"stopStack": "Anhalten",
|
"stopStack": "Anhalten",
|
||||||
"restartStack": "Neustarten",
|
"restartStack": "Neustarten",
|
||||||
@ -22,33 +22,33 @@
|
|||||||
"editStack": "Bearbeiten",
|
"editStack": "Bearbeiten",
|
||||||
"discardStack": "Verwerfen",
|
"discardStack": "Verwerfen",
|
||||||
"saveStackDraft": "Speichern",
|
"saveStackDraft": "Speichern",
|
||||||
"notAvailableShort" : "N/A",
|
"notAvailableShort": "N/V",
|
||||||
"deleteStackMsg": "Möchtest du diesen Stack wirklich löschen?",
|
"deleteStackMsg": "Möchtest du diesen Stack wirklich löschen?",
|
||||||
"stackNotManagedByDockgeMsg": "Dieser Stack wird nicht von Dockge verwaltet.",
|
"stackNotManagedByDockgeMsg": "Dieser Stack wird nicht von Dockge verwaltet.",
|
||||||
"primaryHostname": "Primärer Hostname",
|
"primaryHostname": "Primärer Hostname",
|
||||||
"general": "Allgemein",
|
"general": "Allgemein",
|
||||||
"container": "Container | Container",
|
"container": "Container",
|
||||||
"scanFolder": "Stacks-Ordner durchsuchen",
|
"scanFolder": "Stacks-Ordner durchsuchen",
|
||||||
"dockerImage": "Image",
|
"dockerImage": "Image",
|
||||||
"restartPolicyUnlessStopped": "Falls nicht gestoppt",
|
"restartPolicyUnlessStopped": "Falls nicht gestoppt",
|
||||||
"restartPolicyAlways": "Immer",
|
"restartPolicyAlways": "Immer",
|
||||||
"restartPolicyOnFailure": "Bei Fehler",
|
"restartPolicyOnFailure": "Bei Fehler",
|
||||||
"restartPolicyNo": "Kein Neustart",
|
"restartPolicyNo": "Kein Neustart",
|
||||||
"environmentVariable": "Umgebungsvariable | Umgebungsvariablen",
|
"environmentVariable": "Umgebungsvariable/n",
|
||||||
"restartPolicy": "Neustart Richtlinie",
|
"restartPolicy": "Neustart Richtlinie",
|
||||||
"containerName": "Container-Name",
|
"containerName": "Container-Name",
|
||||||
"port": "Port | Ports",
|
"port": "Port / Ports",
|
||||||
"volume": "Volume | Volumes",
|
"volume": "Volume / Volumes",
|
||||||
"network": "Netzwerk | Netzwerke",
|
"network": "Netzwerk | Netzwerke",
|
||||||
"dependsOn": "Container-Abhängigkeit | Container-Abhängigkeiten",
|
"dependsOn": "Container-Abhängigkeit/en",
|
||||||
"addListItem": "{0} hinzufügen",
|
"addListItem": "{0} hinzufügen",
|
||||||
"deleteContainer": "Löschen",
|
"deleteContainer": "Löschen",
|
||||||
"addContainer": "Container hinzufügen",
|
"addContainer": "Container hinzufügen",
|
||||||
"addNetwork": "Netzwerk hinzufügen",
|
"addNetwork": "Netzwerk hinzufügen",
|
||||||
"disableauth.message1": "Bist du sicher, dass du die <strong>Authentifizierung deaktivieren</strong> möchtest?",
|
"disableauth.message1": "Bist du sicher, dass du die <strong>Anmeldung deaktivieren</strong> möchtest?",
|
||||||
"disableauth.message2": "Es ist für Szenarien vorgesehen, <strong>in denen du beabsichtigst, eine Drittanbieter-Authentifizierung</strong> vor Dockge zu implementieren, wie zum Beispiel Cloudflare Access, Authelia oder andere Authentifizierungsmechanismen.",
|
"disableauth.message2": "Es ist für Szenarien vorgesehen, <strong>in denen du beabsichtigst, eine Drittanbieter-Authentifizierung</strong> vor Dockge zu implementieren, wie zum Beispiel Cloudflare Access, Authelia oder andere Authentifizierungsmechanismen.",
|
||||||
"passwordNotMatchMsg": "Das wiederholte Passwort stimmt nicht überein.",
|
"passwordNotMatchMsg": "Das wiederholte Passwort stimmt nicht überein.",
|
||||||
"autoGet": "Automatisch holen",
|
"autoGet": "Automatisch laden",
|
||||||
"add": "Hinzufügen",
|
"add": "Hinzufügen",
|
||||||
"Edit": "Bearbeiten",
|
"Edit": "Bearbeiten",
|
||||||
"applyToYAML": "Auf YAML anwenden",
|
"applyToYAML": "Auf YAML anwenden",
|
||||||
@ -64,8 +64,8 @@
|
|||||||
"Update Password": "Passwort aktualisieren",
|
"Update Password": "Passwort aktualisieren",
|
||||||
"Advanced": "Erweitert",
|
"Advanced": "Erweitert",
|
||||||
"Please use this option carefully!": "Bitte verwende diese Option sorgfältig!",
|
"Please use this option carefully!": "Bitte verwende diese Option sorgfältig!",
|
||||||
"Enable Auth": "Authentifizierung aktivieren",
|
"Enable Auth": "Anmeldung aktivieren",
|
||||||
"Disable Auth": "Authentifizierung deaktivieren",
|
"Disable Auth": "Anmeldung deaktivieren",
|
||||||
"I understand, please disable": "Ich verstehe, bitte deaktivieren",
|
"I understand, please disable": "Ich verstehe, bitte deaktivieren",
|
||||||
"Leave": "Verlassen",
|
"Leave": "Verlassen",
|
||||||
"Frontend Version": "Frontend Version",
|
"Frontend Version": "Frontend Version",
|
||||||
@ -90,5 +90,13 @@
|
|||||||
"Allowed commands:": "Zugelassene Befehle:",
|
"Allowed commands:": "Zugelassene Befehle:",
|
||||||
"Internal Networks": "Interne Netzwerke",
|
"Internal Networks": "Interne Netzwerke",
|
||||||
"External Networks": "Externe Netzwerke",
|
"External Networks": "Externe Netzwerke",
|
||||||
"No External Networks": "Keine externen Netzwerke"
|
"No External Networks": "Keine externen Netzwerke",
|
||||||
}
|
"Cannot connect to the socket server.": "Keine Verbindung zum Socket Server.",
|
||||||
|
"reverseProxyMsg1": "Wird ein Reverse Proxy genutzt?",
|
||||||
|
"reconnecting...": "Erneuter Verbindungsaufbau…",
|
||||||
|
"downStack": "Stoppen & Aus",
|
||||||
|
"extra": "Extra",
|
||||||
|
"url": "URL / URLs",
|
||||||
|
"reverseProxyMsg2": "Lerne wie dieser für WebSockets zu konfigurieren ist.",
|
||||||
|
"connecting...": "Verbindungsaufbau zum Socket Server…"
|
||||||
|
}
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
"registry": "Registry",
|
"registry": "Registry",
|
||||||
"compose": "Compose",
|
"compose": "Compose",
|
||||||
"addFirstStackMsg": "Compose your first stack!",
|
"addFirstStackMsg": "Compose your first stack!",
|
||||||
"stackName" : "Stack Name",
|
"stackName": "Stack Name",
|
||||||
"deployStack": "Deploy",
|
"deployStack": "Deploy",
|
||||||
"deleteStack": "Delete",
|
"deleteStack": "Delete",
|
||||||
"stopStack": "Stop",
|
"stopStack": "Stop",
|
||||||
@ -23,7 +23,7 @@
|
|||||||
"editStack": "Edit",
|
"editStack": "Edit",
|
||||||
"discardStack": "Discard",
|
"discardStack": "Discard",
|
||||||
"saveStackDraft": "Save",
|
"saveStackDraft": "Save",
|
||||||
"notAvailableShort" : "N/A",
|
"notAvailableShort": "N/A",
|
||||||
"deleteStackMsg": "Are you sure you want to delete this stack?",
|
"deleteStackMsg": "Are you sure you want to delete this stack?",
|
||||||
"stackNotManagedByDockgeMsg": "This stack is not managed by Dockge.",
|
"stackNotManagedByDockgeMsg": "This stack is not managed by Dockge.",
|
||||||
"primaryHostname": "Primary Hostname",
|
"primaryHostname": "Primary Hostname",
|
||||||
@ -91,5 +91,13 @@
|
|||||||
"Allowed commands:": "Allowed commands:",
|
"Allowed commands:": "Allowed commands:",
|
||||||
"Internal Networks": "Internal Networks",
|
"Internal Networks": "Internal Networks",
|
||||||
"External Networks": "External Networks",
|
"External Networks": "External Networks",
|
||||||
"No External Networks": "No External Networks"
|
"No External Networks": "No External Networks",
|
||||||
|
"reverseProxyMsg1": "Using a Reverse Proxy?",
|
||||||
|
"reverseProxyMsg2": "Check how to config it for WebSocket",
|
||||||
|
"Cannot connect to the socket server.": "Cannot connect to the socket server.",
|
||||||
|
"reconnecting...": "Reconnecting…",
|
||||||
|
"connecting...": "Connecting to the socket server…",
|
||||||
|
"url": "URL | URLs",
|
||||||
|
"extra": "Extra",
|
||||||
|
"newUpdate": "New Update"
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"languageName": "Francais",
|
"languageName": "Français",
|
||||||
"Create your admin account": "Créez votre compte administrateur",
|
"Create your admin account": "Créez votre compte administrateur",
|
||||||
"authIncorrectCreds": "identifiant ou mot de passe incorrect.",
|
"authIncorrectCreds": "identifiant ou mot de passe incorrect.",
|
||||||
"Repeat Password": "Répéter le mot de passe",
|
"Repeat Password": "Répéter le mot de passe",
|
||||||
@ -11,8 +11,8 @@
|
|||||||
"console": "Console",
|
"console": "Console",
|
||||||
"registry": "Registre",
|
"registry": "Registre",
|
||||||
"compose": "Compose",
|
"compose": "Compose",
|
||||||
"addFirstStackMsg": "Créez votre première pile!",
|
"addFirstStackMsg": "Créez votre première pile !",
|
||||||
"stackName" : "Nom de la pile",
|
"stackName": "Nom de la pile",
|
||||||
"deployStack": "Déployer",
|
"deployStack": "Déployer",
|
||||||
"deleteStack": "Supprimer",
|
"deleteStack": "Supprimer",
|
||||||
"stopStack": "Arrêter",
|
"stopStack": "Arrêter",
|
||||||
@ -22,11 +22,11 @@
|
|||||||
"editStack": "Modifier",
|
"editStack": "Modifier",
|
||||||
"discardStack": "Ignorer",
|
"discardStack": "Ignorer",
|
||||||
"saveStackDraft": "Sauvegarder",
|
"saveStackDraft": "Sauvegarder",
|
||||||
"notAvailableShort" : "N/A",
|
"notAvailableShort": "N/A",
|
||||||
"deleteStackMsg": "Êtes-vous sûr de vouloir supprimer cette pile ?",
|
"deleteStackMsg": "Êtes-vous sûr de vouloir supprimer cette pile ?",
|
||||||
"stackNotManagedByDockgeMsg": "Cette pile n'est pas gérée par Dockge.",
|
"stackNotManagedByDockgeMsg": "Cette pile n'est pas gérée par Dockge.",
|
||||||
"primaryHostname": "Nom d'hôte principal",
|
"primaryHostname": "Nom d'hôte principal",
|
||||||
"general": "Générale",
|
"general": "Général",
|
||||||
"container": "Conteneur | Conteneurs",
|
"container": "Conteneur | Conteneurs",
|
||||||
"scanFolder": "Analyser le dossier des piles",
|
"scanFolder": "Analyser le dossier des piles",
|
||||||
"dockerImage": "Image",
|
"dockerImage": "Image",
|
||||||
@ -51,7 +51,7 @@
|
|||||||
"autoGet": "Obtention automatique",
|
"autoGet": "Obtention automatique",
|
||||||
"add": "Ajouter",
|
"add": "Ajouter",
|
||||||
"Edit": "Modifier",
|
"Edit": "Modifier",
|
||||||
"applyToYAML": "Appliquer à YAML",
|
"applyToYAML": "Appliquer au YAML",
|
||||||
"createExternalNetwork": "Créer",
|
"createExternalNetwork": "Créer",
|
||||||
"addInternalNetwork": "Ajouter",
|
"addInternalNetwork": "Ajouter",
|
||||||
"Save": "Enregistrer",
|
"Save": "Enregistrer",
|
||||||
@ -87,8 +87,16 @@
|
|||||||
"Appearance": "Apparence",
|
"Appearance": "Apparence",
|
||||||
"Security": "Sécurité",
|
"Security": "Sécurité",
|
||||||
"About": "À propos",
|
"About": "À propos",
|
||||||
"Allowed commands:": "Commandes autorisées:",
|
"Allowed commands:": "Commandes autorisées :",
|
||||||
"Internal Networks": "Réseaux Internes",
|
"Internal Networks": "Réseaux Internes",
|
||||||
"External Networks": "Réseaux Externes",
|
"External Networks": "Réseaux Externes",
|
||||||
"No External Networks": "Aucun Réseau Externe"
|
"No External Networks": "Aucun Réseau Externe",
|
||||||
|
"reverseProxyMsg2": "Vérifier comment le configurer pour WebSocket",
|
||||||
|
"connecting...": "Connexion au serveur socket…",
|
||||||
|
"url": "URL | URLs",
|
||||||
|
"extra": "Supplémentaire",
|
||||||
|
"downStack": "Arrêter et désactiver",
|
||||||
|
"reverseProxyMsg1": "Utilisez vous un proxy inverse ?",
|
||||||
|
"Cannot connect to the socket server.": "Impossible de se connecter au serveur socket.",
|
||||||
|
"reconnecting...": "Reconnexion…"
|
||||||
}
|
}
|
||||||
|
101
frontend/src/lang/it-IT.json
Normal file
101
frontend/src/lang/it-IT.json
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
{
|
||||||
|
"languageName": "Italiano",
|
||||||
|
"Create your admin account": "Crea il tuo account amministratore",
|
||||||
|
"authIncorrectCreds": "Username e/o password errati.",
|
||||||
|
"PasswordsDoNotMatch": "Le password non corrispondono.",
|
||||||
|
"Repeat Password": "Ripetere la password",
|
||||||
|
"Create": "Crea",
|
||||||
|
"signedInDisp": "Autenticato come {0}",
|
||||||
|
"signedInDispDisabled": "Autenticazione disabilitata.",
|
||||||
|
"home": "Home",
|
||||||
|
"console": "Console",
|
||||||
|
"registry": "Registro",
|
||||||
|
"compose": "Compose",
|
||||||
|
"addFirstStackMsg": "Componi il tuo primo stack!",
|
||||||
|
"stackName": "Nome dello stack",
|
||||||
|
"deployStack": "Deploy",
|
||||||
|
"deleteStack": "Cancella",
|
||||||
|
"stopStack": "Stop",
|
||||||
|
"restartStack": "Riavvia",
|
||||||
|
"updateStack": "Aggiorna",
|
||||||
|
"startStack": "Avvia",
|
||||||
|
"downStack": "Stop & Down",
|
||||||
|
"editStack": "Modifica",
|
||||||
|
"discardStack": "Annulla",
|
||||||
|
"saveStackDraft": "Salva",
|
||||||
|
"notAvailableShort": "N/D",
|
||||||
|
"deleteStackMsg": "Sei sicuro di voler eliminare questo stack?",
|
||||||
|
"stackNotManagedByDockgeMsg": "Questo stack non è gestito da Dockge.",
|
||||||
|
"primaryHostname": "Hostname primario",
|
||||||
|
"general": "Generale",
|
||||||
|
"container": "Container | Container",
|
||||||
|
"scanFolder": "Scansiona la cartella degli stack",
|
||||||
|
"dockerImage": "Immagine",
|
||||||
|
"restartPolicyUnlessStopped": "A meno che non venga fermato",
|
||||||
|
"restartPolicyAlways": "Sempre",
|
||||||
|
"restartPolicyOnFailure": "Quando fallisce",
|
||||||
|
"restartPolicyNo": "No",
|
||||||
|
"environmentVariable": "Variabile d'ambiente | Variabili d'ambiente",
|
||||||
|
"restartPolicy": "Politica di riavvio",
|
||||||
|
"containerName": "Nome del container",
|
||||||
|
"port": "Porta | Porte",
|
||||||
|
"volume": "Volume | Volumi",
|
||||||
|
"network": "Rete | Reti",
|
||||||
|
"dependsOn": "Dipendenza del container | Dipendenze del container",
|
||||||
|
"addListItem": "Aggiungi {0}",
|
||||||
|
"deleteContainer": "Elimina",
|
||||||
|
"addContainer": "Aggiungi container",
|
||||||
|
"addNetwork": "Aggiungi rete",
|
||||||
|
"disableauth.message1": "Sei sicuro di voler <strong>disabilitare l'autenticazione</strong>?",
|
||||||
|
"disableauth.message2": "È stato progettato per scenari <strong>in cui intendi implementare un'autenticazione di terze parti</strong> davanti a Dockge come ad esempio Cloudflare Access, Authelia o altri meccanismi di autenticazione.",
|
||||||
|
"passwordNotMatchMsg": "La password ripetuta non corrisponde.",
|
||||||
|
"autoGet": "Ottieni automaticamente",
|
||||||
|
"add": "Aggiungi",
|
||||||
|
"Edit": "Modifica",
|
||||||
|
"applyToYAML": "Applica al file YAML",
|
||||||
|
"createExternalNetwork": "Crea",
|
||||||
|
"addInternalNetwork": "Aggiungi",
|
||||||
|
"Save": "Salva",
|
||||||
|
"Language": "Lingua",
|
||||||
|
"Current User": "Utente corrente",
|
||||||
|
"Change Password": "Cambia la password",
|
||||||
|
"Current Password": "Password corrente",
|
||||||
|
"New Password": "Nuova password",
|
||||||
|
"Repeat New Password": "Ripeti la nuova password",
|
||||||
|
"Update Password": "Aggiornamento password",
|
||||||
|
"Advanced": "Avanzato",
|
||||||
|
"Please use this option carefully!": "Per favore usa questa opzione con cautela!",
|
||||||
|
"Enable Auth": "Abilita l'autenticazione",
|
||||||
|
"Disable Auth": "Disabilita l'autenticazione",
|
||||||
|
"I understand, please disable": "Lo capisco, disabilita",
|
||||||
|
"Leave": "Lascia",
|
||||||
|
"Frontend Version": "Versione del frontend",
|
||||||
|
"Check Update On GitHub": "Controlla la presenza di aggiornamenti su GitHub",
|
||||||
|
"Show update if available": "Mostra l'aggiornamento se è disponibile",
|
||||||
|
"Also check beta release": "Controlla anche le release in beta",
|
||||||
|
"Remember me": "Ricordami",
|
||||||
|
"Login": "Login",
|
||||||
|
"Username": "Username",
|
||||||
|
"Password": "Password",
|
||||||
|
"Settings": "Impostazioni",
|
||||||
|
"Logout": "Logout",
|
||||||
|
"Lowercase only": "Solo lettere minuscole",
|
||||||
|
"Convert to Compose": "Converti a Compose",
|
||||||
|
"Docker Run": "Docker Run",
|
||||||
|
"active": "attivo",
|
||||||
|
"exited": "uscito",
|
||||||
|
"inactive": "inattivo",
|
||||||
|
"Appearance": "Aspetto",
|
||||||
|
"Security": "Sicurezza",
|
||||||
|
"About": "Informazioni su",
|
||||||
|
"Allowed commands:": "Comandi permessi:",
|
||||||
|
"Internal Networks": "Reti interne",
|
||||||
|
"External Networks": "Reti esterne",
|
||||||
|
"No External Networks": "Nessuna rete esterna",
|
||||||
|
"reverseProxyMsg1": "Utilizzando un proxy inverso?",
|
||||||
|
"reverseProxyMsg2": "Controlla come configurarlo per WebSocket",
|
||||||
|
"Cannot connect to the socket server.": "Impossibile connettersi al server socket.",
|
||||||
|
"connecting...": "Connessione al server socket…",
|
||||||
|
"extra": "Extra",
|
||||||
|
"reconnecting...": "Riconnessione…"
|
||||||
|
}
|
98
frontend/src/lang/ja.json
Normal file
98
frontend/src/lang/ja.json
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
{
|
||||||
|
"authIncorrectCreds": "ユーザーネームまたはパスワードが正しくありません。",
|
||||||
|
"PasswordsDoNotMatch": "パスワードが一致しません。",
|
||||||
|
"Repeat Password": "パスワードを再度入力してください",
|
||||||
|
"Create": "作成",
|
||||||
|
"signedInDispDisabled": "認証が無効化されています。",
|
||||||
|
"home": "ホーム",
|
||||||
|
"console": "コンソール",
|
||||||
|
"registry": "レジストリ",
|
||||||
|
"stackName": "スタック名",
|
||||||
|
"deployStack": "デプロイ",
|
||||||
|
"deleteStack": "削除",
|
||||||
|
"stopStack": "停止",
|
||||||
|
"restartStack": "再起動",
|
||||||
|
"updateStack": "更新",
|
||||||
|
"startStack": "起動",
|
||||||
|
"editStack": "編集",
|
||||||
|
"discardStack": "破棄",
|
||||||
|
"saveStackDraft": "保存",
|
||||||
|
"stackNotManagedByDockgeMsg": "このスタックはDockgeによって管理されていません。",
|
||||||
|
"general": "一般",
|
||||||
|
"scanFolder": "スタックフォルダをスキャン",
|
||||||
|
"dockerImage": "イメージ",
|
||||||
|
"environmentVariable": "環境変数",
|
||||||
|
"restartPolicy": "再起動ポリシー",
|
||||||
|
"containerName": "コンテナ名",
|
||||||
|
"port": "ポート",
|
||||||
|
"volume": "ボリューム",
|
||||||
|
"network": "ネットワーク",
|
||||||
|
"addListItem": "{0} を追加",
|
||||||
|
"addContainer": "コンテナを追加",
|
||||||
|
"addNetwork": "ネットワークを追加",
|
||||||
|
"compose": "Compose",
|
||||||
|
"primaryHostname": "主ホスト名",
|
||||||
|
"container": "コンテナ",
|
||||||
|
"dependsOn": "コンテナ依存関係",
|
||||||
|
"downStack": "停止して削除",
|
||||||
|
"notAvailableShort": "N/A",
|
||||||
|
"restartPolicyUnlessStopped": "手動で停止されるまで",
|
||||||
|
"restartPolicyAlways": "常時",
|
||||||
|
"restartPolicyOnFailure": "失敗時",
|
||||||
|
"restartPolicyNo": "しない",
|
||||||
|
"passwordNotMatchMsg": "繰り返しのパスワードが一致しません。",
|
||||||
|
"autoGet": "自動取得",
|
||||||
|
"add": "追加",
|
||||||
|
"Edit": "編集",
|
||||||
|
"applyToYAML": "YAMLに適用",
|
||||||
|
"createExternalNetwork": "作成",
|
||||||
|
"addInternalNetwork": "追加",
|
||||||
|
"Save": "保存",
|
||||||
|
"Language": "言語",
|
||||||
|
"Change Password": "パスワードを変更する",
|
||||||
|
"Current Password": "現在のパスワード",
|
||||||
|
"New Password": "新しいパスワード",
|
||||||
|
"Update Password": "パスワードを更新",
|
||||||
|
"Advanced": "高度",
|
||||||
|
"Please use this option carefully!": "このオプションは注意して使用してください!",
|
||||||
|
"Enable Auth": "認証を有効化",
|
||||||
|
"Disable Auth": "認証を無効化",
|
||||||
|
"Check Update On GitHub": "GitHubで更新を確認",
|
||||||
|
"Show update if available": "アップデートがある場合表示",
|
||||||
|
"Also check beta release": "ベータ版のリリースも確認する",
|
||||||
|
"Login": "ログイン",
|
||||||
|
"Username": "ユーザー名",
|
||||||
|
"Password": "パスワード",
|
||||||
|
"Settings": "設定",
|
||||||
|
"Logout": "ログアウト",
|
||||||
|
"Convert to Compose": "Composeに変換",
|
||||||
|
"Appearance": "外観",
|
||||||
|
"Security": "セキュリティ",
|
||||||
|
"Allowed commands:": "許可されたコマンド:",
|
||||||
|
"Internal Networks": "内部ネットワーク",
|
||||||
|
"External Networks": "外部ネットワーク",
|
||||||
|
"reverseProxyMsg2": "WebSocketの設定方法を確認",
|
||||||
|
"Cannot connect to the socket server.": "ソケットサーバーに接続できません。",
|
||||||
|
"reconnecting...": "再接続中…",
|
||||||
|
"Leave": "やめる",
|
||||||
|
"Frontend Version": "フロントエンドバージョン",
|
||||||
|
"Remember me": "覚えておく",
|
||||||
|
"No External Networks": "外部ネットワークなし",
|
||||||
|
"exited": "終了済み",
|
||||||
|
"inactive": "非アクティブ",
|
||||||
|
"active": "アクティブ",
|
||||||
|
"languageName": "日本語",
|
||||||
|
"Create your admin account": "管理者アカウントを作成してください",
|
||||||
|
"signedInDisp": "{0} としてログイン中",
|
||||||
|
"addFirstStackMsg": "最初のスタックを組み立てましょう!",
|
||||||
|
"deleteStackMsg": "本当にこのスタックを削除しますか?",
|
||||||
|
"deleteContainer": "削除",
|
||||||
|
"disableauth.message1": "本当に<strong>認証を無効化</strong>しますか?",
|
||||||
|
"disableauth.message2": "これはCloudflare AccessやAutheliaなどの認証手段をDockgeの前段に置いて<strong>サードパーティー認証を実装することをあなたが意図している</strong>場合のために設計されています。",
|
||||||
|
"Current User": "現在のユーザー",
|
||||||
|
"Repeat New Password": "新しいパスワードを繰り返してください",
|
||||||
|
"I understand, please disable": "理解しました。無効化してください",
|
||||||
|
"Lowercase only": "小文字のみ",
|
||||||
|
"reverseProxyMsg1": "リバースプロキシを使用していますか?",
|
||||||
|
"connecting...": "ソケットサーバーに接続中…"
|
||||||
|
}
|
@ -90,5 +90,13 @@
|
|||||||
"Allowed commands:": "허용된 명령어:",
|
"Allowed commands:": "허용된 명령어:",
|
||||||
"Internal Networks": "내부 네트워크",
|
"Internal Networks": "내부 네트워크",
|
||||||
"External Networks": "외부 네트워크",
|
"External Networks": "외부 네트워크",
|
||||||
"No External Networks": "외부 네트워크 없음"
|
"No External Networks": "외부 네트워크 없음",
|
||||||
|
"reverseProxyMsg2": "여기서 WebSocket을 위한 설정을 확인해 보세요",
|
||||||
|
"downStack": "정지 & Down",
|
||||||
|
"reverseProxyMsg1": "리버스 프록시를 사용하고 계신가요?",
|
||||||
|
"Cannot connect to the socket server.": "소켓 서버에 연결하지 못했습니다.",
|
||||||
|
"connecting...": "소켓 서버에 연결하는 중…",
|
||||||
|
"extra": "기타",
|
||||||
|
"url": "URL | URL",
|
||||||
|
"reconnecting...": "재연결 중…"
|
||||||
}
|
}
|
||||||
|
102
frontend/src/lang/nl.json
Normal file
102
frontend/src/lang/nl.json
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
{
|
||||||
|
"languageName": "Nederlands",
|
||||||
|
"authIncorrectCreds": "Onjuiste gebruikersnaam of wachtwoord.",
|
||||||
|
"PasswordsDoNotMatch": "Paswoorden komen niet overeen.",
|
||||||
|
"Repeat Password": "Herhaal wachtwoord",
|
||||||
|
"Create": "Aanmaken",
|
||||||
|
"signedInDisp": "Ingelogd als {0}",
|
||||||
|
"home": "Startpagina",
|
||||||
|
"console": "Console",
|
||||||
|
"registry": "Register",
|
||||||
|
"compose": "Samenstellen",
|
||||||
|
"stackName": "Stack naam",
|
||||||
|
"deployStack": "Opzetten",
|
||||||
|
"deleteStack": "Verwijder",
|
||||||
|
"stopStack": "Stop",
|
||||||
|
"restartStack": "Herstart",
|
||||||
|
"updateStack": "Update",
|
||||||
|
"startStack": "Start",
|
||||||
|
"downStack": "Stop & Down",
|
||||||
|
"editStack": "Bewerken",
|
||||||
|
"discardStack": "Verwijderen",
|
||||||
|
"saveStackDraft": "Opslaan",
|
||||||
|
"notAvailableShort": "NVT",
|
||||||
|
"stackNotManagedByDockgeMsg": "Deze stack wordt niet beheerd door Dockge.",
|
||||||
|
"primaryHostname": "Primaire hostnaam",
|
||||||
|
"general": "Algemeen",
|
||||||
|
"scanFolder": "Scan stacks folder",
|
||||||
|
"dockerImage": "Image",
|
||||||
|
"restartPolicyUnlessStopped": "Tenzij gestopt",
|
||||||
|
"restartPolicyAlways": "Altijd",
|
||||||
|
"restartPolicyOnFailure": "Bij fout",
|
||||||
|
"restartPolicyNo": "Neen",
|
||||||
|
"environmentVariable": "Omgevings variabele(n)",
|
||||||
|
"restartPolicy": "Herstart policy",
|
||||||
|
"containerName": "Containernaam",
|
||||||
|
"port": "Poort(en)",
|
||||||
|
"volume": "Volume(s)",
|
||||||
|
"network": "Netwerk(en)",
|
||||||
|
"addListItem": "Voeg {0} toe",
|
||||||
|
"deleteContainer": "Verwijder",
|
||||||
|
"addContainer": "Container toevoegen",
|
||||||
|
"addNetwork": "Netwerk toevoegen",
|
||||||
|
"signedInDispDisabled": "Aanmelden uitgeschakeld.",
|
||||||
|
"container": "Container(s)",
|
||||||
|
"autoGet": "Auto ophalen",
|
||||||
|
"add": "Toevoegen",
|
||||||
|
"Edit": "Bewerken",
|
||||||
|
"applyToYAML": "Toevoegen aan YAML",
|
||||||
|
"createExternalNetwork": "Aanmaken",
|
||||||
|
"addInternalNetwork": "Toevoegen",
|
||||||
|
"Save": "Opslaan",
|
||||||
|
"Language": "Taal",
|
||||||
|
"Change Password": "Verander wachtwoord",
|
||||||
|
"Current Password": "Huidig wachtwoord",
|
||||||
|
"New Password": "Nieuw wachtwoord",
|
||||||
|
"Repeat New Password": "Herhaal nieuw wachtwoord",
|
||||||
|
"Update Password": "Update wachtwoord",
|
||||||
|
"Advanced": "Geavanceerd",
|
||||||
|
"I understand, please disable": "Begrepen, dit uitschakelen",
|
||||||
|
"Disable Auth": "Aanmelden uitschakelen",
|
||||||
|
"Enable Auth": "Aanmelden inschakelen",
|
||||||
|
"Leave": "Afmelden",
|
||||||
|
"Frontend Version": "Frontend versie",
|
||||||
|
"Check Update On GitHub": "Controleer via GitHub op updates",
|
||||||
|
"Show update if available": "Toon update indien beschikbaar",
|
||||||
|
"Remember me": "Onthoud mij",
|
||||||
|
"Login": "Inloggen",
|
||||||
|
"Username": "Gebruikersnaam",
|
||||||
|
"Password": "Wachtwoord",
|
||||||
|
"Settings": "Instellingen",
|
||||||
|
"Logout": "Uitloggen",
|
||||||
|
"Lowercase only": "Geen hoofdletters",
|
||||||
|
"Docker Run": "Docker run",
|
||||||
|
"active": "actief",
|
||||||
|
"exited": "gestopt",
|
||||||
|
"inactive": "inactief",
|
||||||
|
"Appearance": "Uiterlijk",
|
||||||
|
"Security": "Beveiliging",
|
||||||
|
"About": "Over",
|
||||||
|
"Allowed commands:": "Toegelaten commando's:",
|
||||||
|
"Internal Networks": "Interne netwerken",
|
||||||
|
"No External Networks": "Geen externe netwerken",
|
||||||
|
"reverseProxyMsg1": "Reverse proxy in gebruik?",
|
||||||
|
"reverseProxyMsg2": "Controleer hoe te configureren voor WebSocket",
|
||||||
|
"Cannot connect to the socket server.": "Kan geen verbinding maken met de socket server.",
|
||||||
|
"reconnecting...": "Herverbinden...",
|
||||||
|
"connecting...": "Verbinden met de socket server...",
|
||||||
|
"url": "Url(s)",
|
||||||
|
"extra": "Extra",
|
||||||
|
"Create your admin account": "Creëer je beheerders-account",
|
||||||
|
"addFirstStackMsg": "Maak je eerste stack!",
|
||||||
|
"deleteStackMsg": "Zeker dat je deze stack wilt verwijderen?",
|
||||||
|
"dependsOn": "Container afhankelijkheid | afhankelijkheden",
|
||||||
|
"disableauth.message1": "Zeker dat u <strong>aanmelden</strong> wilt uitschakelen?",
|
||||||
|
"disableauth.message2": "Dit is enkel bedoeld om te gebruiken wanneer je<strong> third-party autorisatie wilt gebruiken voor Dockge</strong>, zoals Cloudflare Acces, Authelia, ...",
|
||||||
|
"passwordNotMatchMsg": "De wachtwoorden komen niet overeen.",
|
||||||
|
"Current User": "Huidige gebruiker",
|
||||||
|
"Please use this option carefully!": "Wees voorzichtig met deze optie!",
|
||||||
|
"Also check beta release": "Controleer ook op beta releases",
|
||||||
|
"Convert to Compose": "Converteer naar compose",
|
||||||
|
"External Networks": "Externe netwerken"
|
||||||
|
}
|
94
frontend/src/lang/pt-BR.json
Normal file
94
frontend/src/lang/pt-BR.json
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
{
|
||||||
|
"languageName": "Português-Brasil",
|
||||||
|
"Create your admin account": "Crie sua conta de administrador",
|
||||||
|
"authIncorrectCreds": "Nome de usuário ou senha incorretos.",
|
||||||
|
"PasswordsDoNotMatch": "As senhas não correspondem.",
|
||||||
|
"Repeat Password": "Repetir a senha",
|
||||||
|
"Create": "Criar",
|
||||||
|
"signedInDisp": "Logado como {0}",
|
||||||
|
"signedInDispDisabled": "Autenticação desativada.",
|
||||||
|
"home": "Início",
|
||||||
|
"console": "Console",
|
||||||
|
"registry": "Registro",
|
||||||
|
"compose": "Compose",
|
||||||
|
"addFirstStackMsg": "Crie sua primeira stack!",
|
||||||
|
"stackName" : "Nome da stack",
|
||||||
|
"deployStack": "Deploy",
|
||||||
|
"deleteStack": "Excluir",
|
||||||
|
"stopStack": "Parar",
|
||||||
|
"restartStack": "Reiniciar",
|
||||||
|
"updateStack": "Atualizar",
|
||||||
|
"startStack": "Iniciar",
|
||||||
|
"editStack": "Editar",
|
||||||
|
"discardStack": "Descartar",
|
||||||
|
"saveStackDraft": "Salvar",
|
||||||
|
"notAvailableShort" : "N/D",
|
||||||
|
"deleteStackMsg": "Tem certeza que deseja excluir esta stack?",
|
||||||
|
"stackNotManagedByDockgeMsg": "Esta stack não é gerenciada pelo Dockge.",
|
||||||
|
"primaryHostname": "Nome do Host Primário",
|
||||||
|
"general": "Geral",
|
||||||
|
"container": "Contêiner | Contêineres",
|
||||||
|
"scanFolder": "Pesquisar na pasta de stacks",
|
||||||
|
"dockerImage": "Imagem",
|
||||||
|
"restartPolicyUnlessStopped": "A menos que seja parado",
|
||||||
|
"restartPolicyAlways": "Sempre",
|
||||||
|
"restartPolicyOnFailure": "Em caso de falha",
|
||||||
|
"restartPolicyNo": "Não",
|
||||||
|
"environmentVariable": "Variável de ambiente | Variáveis de ambiente",
|
||||||
|
"restartPolicy": "Política de reinicialização",
|
||||||
|
"containerName": "Nome do contêiner",
|
||||||
|
"port": "Porta | Portas",
|
||||||
|
"volume": "Volume | Volumes",
|
||||||
|
"network": "Rede | Redes",
|
||||||
|
"dependsOn": "Dependência do contêiner | Dependências do contêiner",
|
||||||
|
"addListItem": "Adicionar {0}",
|
||||||
|
"deleteContainer": "Excluir",
|
||||||
|
"addContainer": "Adicionar contêiner",
|
||||||
|
"addNetwork": "Adicionar rede",
|
||||||
|
"disableauth.message1": "Tem certeza que deseja <strong>desativar a autenticação</strong>?",
|
||||||
|
"disableauth.message2": "Isso foi projetado para ambientes <strong>onde você pretende implementar autenticação de terceiros</strong> no Dockge, como Cloudflare Access, Authelia entre outros mecanismos de autenticação.",
|
||||||
|
"passwordNotMatchMsg": "A senha repetida não corresponde.",
|
||||||
|
"autoGet": "Obter automaticamente",
|
||||||
|
"add": "Adicionar",
|
||||||
|
"Edit": "Editar",
|
||||||
|
"applyToYAML": "Aplicar ao YAML",
|
||||||
|
"createExternalNetwork": "Criar",
|
||||||
|
"addInternalNetwork": "Adicionar",
|
||||||
|
"Save": "Salvar",
|
||||||
|
"Language": "Idioma",
|
||||||
|
"Current User": "Usuário atual",
|
||||||
|
"Change Password": "Alterar senha",
|
||||||
|
"Current Password": "Senha atual",
|
||||||
|
"New Password": "Nova senha",
|
||||||
|
"Repeat New Password": "Repetir nova senha",
|
||||||
|
"Update Password": "Atualizar senha",
|
||||||
|
"Advanced": "Avançado",
|
||||||
|
"Please use this option carefully!": "Por favor, use esta opção com atenção!",
|
||||||
|
"Enable Auth": "Habilitar autenticação",
|
||||||
|
"Disable Auth": "Desabilitar autenticação",
|
||||||
|
"I understand, please disable": "Entendido, por favor desabilitar",
|
||||||
|
"Leave": "Sair",
|
||||||
|
"Frontend Version": "Versão da interface",
|
||||||
|
"Check Update On GitHub": "Verificar atualização no GitHub",
|
||||||
|
"Show update if available": "Mostrar atualização se disponível",
|
||||||
|
"Also check beta release": "Também verificar versão beta",
|
||||||
|
"Remember me": "Lembrar-me",
|
||||||
|
"Login": "Entrar",
|
||||||
|
"Username": "Nome de usuário",
|
||||||
|
"Password": "Senha",
|
||||||
|
"Settings": "Configurações",
|
||||||
|
"Logout": "Sair",
|
||||||
|
"Lowercase only": "Somente minúsculas",
|
||||||
|
"Convert to Compose": "Converter para compose",
|
||||||
|
"Docker Run": "Executar Docker",
|
||||||
|
"active": "ativo",
|
||||||
|
"exited": "encerrado",
|
||||||
|
"inactive": "inativo",
|
||||||
|
"Appearance": "Aparência",
|
||||||
|
"Security": "Segurança",
|
||||||
|
"About": "Sobre",
|
||||||
|
"Allowed commands:": "Comandos permitidos:",
|
||||||
|
"Internal Networks": "Redes internas",
|
||||||
|
"External Networks": "Redes externas",
|
||||||
|
"No External Networks": "Sem redes externas"
|
||||||
|
}
|
102
frontend/src/lang/ro.json
Normal file
102
frontend/src/lang/ro.json
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
{
|
||||||
|
"Create your admin account": "Creați-vă contul de administrator",
|
||||||
|
"PasswordsDoNotMatch": "Parolele nu se potrivesc.",
|
||||||
|
"Repeat Password": "Repetați parola",
|
||||||
|
"signedInDisp": "Conectat ca {0}",
|
||||||
|
"signedInDispDisabled": "Autentificare dezactivată.",
|
||||||
|
"Create": "Creează",
|
||||||
|
"home": "Acasă",
|
||||||
|
"console": "Consolă",
|
||||||
|
"registry": "Registru",
|
||||||
|
"compose": "Compune",
|
||||||
|
"addFirstStackMsg": "Compune prima ta stivă!",
|
||||||
|
"stackName": "Nume stivă",
|
||||||
|
"deployStack": "Lansează",
|
||||||
|
"deleteStack": "Șterge",
|
||||||
|
"stopStack": "Oprește",
|
||||||
|
"restartStack": "Repornire",
|
||||||
|
"updateStack": "Actualizare",
|
||||||
|
"languageName": "Română",
|
||||||
|
"authIncorrectCreds": "Numele de utilizator sau parola incorectă.",
|
||||||
|
"startStack": "Pornește",
|
||||||
|
"editStack": "Editați",
|
||||||
|
"discardStack": "Renunţa",
|
||||||
|
"notAvailableShort": "N/A",
|
||||||
|
"deleteStackMsg": "Sigur doriți să ștergeți această stivă?",
|
||||||
|
"stackNotManagedByDockgeMsg": "Această stivă nu este gestionată de Dockge.",
|
||||||
|
"primaryHostname": "Numele gazdei principale",
|
||||||
|
"general": "General",
|
||||||
|
"container": "Container | Containere",
|
||||||
|
"scanFolder": "Scanează folderul cu stive",
|
||||||
|
"dockerImage": "Imagine",
|
||||||
|
"restartPolicyOnFailure": "La Defecţiune",
|
||||||
|
"restartPolicyNo": "Nu",
|
||||||
|
"restartPolicy": "Politica de repornire",
|
||||||
|
"restartPolicyAlways": "Mereu",
|
||||||
|
"containerName": "Numele Containerului",
|
||||||
|
"port": "Port | Porturi",
|
||||||
|
"volume": "Volum | Volume",
|
||||||
|
"network": "Reţea | Reţele",
|
||||||
|
"dependsOn": "Dependența containerului | Dependențele containerelor",
|
||||||
|
"addListItem": "Adaugă {0}",
|
||||||
|
"deleteContainer": "Șterge",
|
||||||
|
"addContainer": "Adaugă Container",
|
||||||
|
"addNetwork": "Adaugă Rețea",
|
||||||
|
"addInternalNetwork": "Adaugă",
|
||||||
|
"Save": "Salvează",
|
||||||
|
"Current User": "Utilizator Curent",
|
||||||
|
"Change Password": "Schimbă Parola",
|
||||||
|
"Current Password": "Parolă Curenta",
|
||||||
|
"New Password": "Parolă Nouă",
|
||||||
|
"Repeat New Password": "Repetă Parola Nouă",
|
||||||
|
"Update Password": "Actualizează Parola",
|
||||||
|
"Advanced": "Avansat",
|
||||||
|
"Enable Auth": "Activați Autentificarea",
|
||||||
|
"Disable Auth": "Dezactivați Autentificarea",
|
||||||
|
"I understand, please disable": "Am înțeles, vă rog dezactivați",
|
||||||
|
"Leave": "Părăsiți",
|
||||||
|
"Frontend Version": "Versiunea Frontend",
|
||||||
|
"Check Update On GitHub": "Verificați actualizarea pe GitHub",
|
||||||
|
"Also check beta release": "Verificați și versiunea beta",
|
||||||
|
"Remember me": "Ține-mă minte",
|
||||||
|
"Login": "Autentificare",
|
||||||
|
"Username": "Nume de utilizator",
|
||||||
|
"Password": "Parolă",
|
||||||
|
"passwordNotMatchMsg": "Parola repetată nu se potrivește.",
|
||||||
|
"autoGet": "Obținere automată",
|
||||||
|
"add": "Adăuga",
|
||||||
|
"Edit": "Editați",
|
||||||
|
"applyToYAML": "Aplicați la YAML",
|
||||||
|
"createExternalNetwork": "Creează",
|
||||||
|
"Settings": "Setări",
|
||||||
|
"Logout": "Deconectare",
|
||||||
|
"Lowercase only": "Doar litere mici",
|
||||||
|
"Convert to Compose": "Convertiți în Compose",
|
||||||
|
"Docker Run": "Docker Run",
|
||||||
|
"active": "activ",
|
||||||
|
"exited": "ieșit",
|
||||||
|
"inactive": "inactiv",
|
||||||
|
"Appearance": "Aspect",
|
||||||
|
"Security": "Securitate",
|
||||||
|
"About": "Despre",
|
||||||
|
"Allowed commands:": "Comenzi permise:",
|
||||||
|
"Internal Networks": "Rețele interne",
|
||||||
|
"External Networks": "Rețele externe",
|
||||||
|
"No External Networks": "Fără rețele externe",
|
||||||
|
"reverseProxyMsg1": "Folosești un proxy invers?",
|
||||||
|
"reverseProxyMsg2": "Verificați cum să-l configurați pentru WebSocket",
|
||||||
|
"Cannot connect to the socket server.": "Nu se poate conecta la serverul socket.",
|
||||||
|
"reconnecting...": "Reconectare...",
|
||||||
|
"connecting...": "Se conectează la serverul socket...",
|
||||||
|
"url": "URL | URLs",
|
||||||
|
"extra": "Suplimentar",
|
||||||
|
"downStack": "Opriți & Coborâți",
|
||||||
|
"saveStackDraft": "Salvați",
|
||||||
|
"restartPolicyUnlessStopped": "Dacă nu este oprit",
|
||||||
|
"environmentVariable": "Variabila de mediu | Variabile de mediu",
|
||||||
|
"Language": "Limbă",
|
||||||
|
"Please use this option carefully!": "Vă rugăm să utilizați această opțiune cu atenție!",
|
||||||
|
"Show update if available": "Afișează actualizarea dacă este disponibilă",
|
||||||
|
"disableauth.message1": "Sigur doriți să <strong>dezactivați autentificarea</strong>?",
|
||||||
|
"disableauth.message2": "Este conceput pentru scenarii <strong>în care intenționați să implementați autentificarea terță</strong> în fața Dockge-lui, cum ar fi Cloudflare Access, Authelia sau alte mecanisme de autentificare."
|
||||||
|
}
|
@ -5,14 +5,14 @@
|
|||||||
"PasswordsDoNotMatch": "Пароль не совпадает.",
|
"PasswordsDoNotMatch": "Пароль не совпадает.",
|
||||||
"Repeat Password": "Повторите пароль",
|
"Repeat Password": "Повторите пароль",
|
||||||
"Create": "Создать",
|
"Create": "Создать",
|
||||||
"signedInDisp": "Авторизлван как {0}",
|
"signedInDisp": "Авторизован как",
|
||||||
"signedInDispDisabled": "Авторизация выключена.",
|
"signedInDispDisabled": "Авторизация выключена.",
|
||||||
"home": "Главная",
|
"home": "Главная",
|
||||||
"console": "Консоль",
|
"console": "Консоль",
|
||||||
"registry": "Registry",
|
"registry": "Registry",
|
||||||
"compose": "Compose",
|
"compose": "Compose",
|
||||||
"addFirstStackMsg": "Создайте свой первый стек!",
|
"addFirstStackMsg": "Создайте свой первый стек!",
|
||||||
"stackName" : "Имя стека",
|
"stackName": "Имя стека",
|
||||||
"deployStack": "Развернуть",
|
"deployStack": "Развернуть",
|
||||||
"deleteStack": "Удалить",
|
"deleteStack": "Удалить",
|
||||||
"stopStack": "Остановить",
|
"stopStack": "Остановить",
|
||||||
@ -22,7 +22,7 @@
|
|||||||
"editStack": "Изменить",
|
"editStack": "Изменить",
|
||||||
"discardStack": "Отменить",
|
"discardStack": "Отменить",
|
||||||
"saveStackDraft": "Сохранить",
|
"saveStackDraft": "Сохранить",
|
||||||
"notAvailableShort" : "Н/Д",
|
"notAvailableShort": "Н/Д",
|
||||||
"deleteStackMsg": "Вы уверены что хотите удалить этот стек?",
|
"deleteStackMsg": "Вы уверены что хотите удалить этот стек?",
|
||||||
"stackNotManagedByDockgeMsg": "Данный стек не обслуживается Dockge.",
|
"stackNotManagedByDockgeMsg": "Данный стек не обслуживается Dockge.",
|
||||||
"primaryHostname": "Имя хоста",
|
"primaryHostname": "Имя хоста",
|
||||||
@ -79,7 +79,7 @@
|
|||||||
"Settings": "Настройки",
|
"Settings": "Настройки",
|
||||||
"Logout": "Выйти",
|
"Logout": "Выйти",
|
||||||
"Lowercase only": "Только нижний регистр",
|
"Lowercase only": "Только нижний регистр",
|
||||||
"Convert to Compose": "Преобразовать вCompose",
|
"Convert to Compose": "Преобразовать в Compose",
|
||||||
"Docker Run": "Запустить Docker",
|
"Docker Run": "Запустить Docker",
|
||||||
"active": "активный",
|
"active": "активный",
|
||||||
"exited": "завершенный",
|
"exited": "завершенный",
|
||||||
|
95
frontend/src/lang/sv-SE.json
Normal file
95
frontend/src/lang/sv-SE.json
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
{
|
||||||
|
"languageName": "Svenska",
|
||||||
|
"Create your admin account": "Skapa ditt Admin-konto.",
|
||||||
|
"authIncorrectCreds": "Fel användarnamn eller lösenord.",
|
||||||
|
"PasswordsDoNotMatch": "Lösenorden matchar inte.",
|
||||||
|
"Repeat Password": "Repetera lösenord",
|
||||||
|
"Create": "Skapa",
|
||||||
|
"signedInDisp": "Inloggad som {0}",
|
||||||
|
"signedInDispDisabled": "Auth inaktiverad.",
|
||||||
|
"home": "Hem",
|
||||||
|
"console": "Konsol",
|
||||||
|
"registry": "Register",
|
||||||
|
"compose": "Komponera",
|
||||||
|
"addFirstStackMsg": "Komponera din första stack!",
|
||||||
|
"stackName" : "Stacknamn",
|
||||||
|
"deployStack": "Distribuera",
|
||||||
|
"deleteStack": "Radera",
|
||||||
|
"stopStack": "Stop",
|
||||||
|
"restartStack": "Starta om",
|
||||||
|
"updateStack": "Uppdatera",
|
||||||
|
"startStack": "Starta",
|
||||||
|
"downStack": "Stop & Ner",
|
||||||
|
"editStack": "Redigera",
|
||||||
|
"discardStack": "Kasta",
|
||||||
|
"saveStackDraft": "Spara",
|
||||||
|
"notAvailableShort" : "N/A",
|
||||||
|
"deleteStackMsg": "Är du säker på att du vill radera stacken?",
|
||||||
|
"stackNotManagedByDockgeMsg": "Denna stacken hanteras inte av Dockge.",
|
||||||
|
"primaryHostname": "Primärt värdnamn",
|
||||||
|
"general": "Allmän",
|
||||||
|
"container": "Container | Containrar",
|
||||||
|
"scanFolder": "Scanna Stackfolder",
|
||||||
|
"dockerImage": "Bild",
|
||||||
|
"restartPolicyUnlessStopped": "Om inte stoppas",
|
||||||
|
"restartPolicyAlways": "Alltid",
|
||||||
|
"restartPolicyOnFailure": "Vid Misslyckande",
|
||||||
|
"restartPolicyNo": "Nej",
|
||||||
|
"environmentVariable": "Miljövariabel | Miljövariabler",
|
||||||
|
"restartPolicy": "Omstartspolicy",
|
||||||
|
"containerName": "Containernamn",
|
||||||
|
"port": "Port | Portar",
|
||||||
|
"volume": "Volym | Volymer",
|
||||||
|
"network": "Nätverk | Nätverk",
|
||||||
|
"dependsOn": "Containerberoende | Containerberoenden",
|
||||||
|
"addListItem": "Lägg till {0}",
|
||||||
|
"deleteContainer": "Radera",
|
||||||
|
"addContainer": "Lägg till Container",
|
||||||
|
"addNetwork": "Lägg till Nätverk",
|
||||||
|
"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.",
|
||||||
|
"passwordNotMatchMsg": "Det upprepade lösenordet matchar inte",
|
||||||
|
"autoGet": "Auto Hämta",
|
||||||
|
"add": "Lägg till",
|
||||||
|
"Edit": "Redigera",
|
||||||
|
"applyToYAML": "Lägg till i YAML",
|
||||||
|
"createExternalNetwork": "Skapa",
|
||||||
|
"addInternalNetwork": "Lägg till",
|
||||||
|
"Save": "Spara",
|
||||||
|
"Language": "Språk",
|
||||||
|
"Current User": "Nuvarande användaren",
|
||||||
|
"Change Password": "Byt lösenord",
|
||||||
|
"Current Password": "Nuvarande lösenord",
|
||||||
|
"New Password": "Nytt lösenord",
|
||||||
|
"Repeat New Password": "Upprepa nytt lösenord",
|
||||||
|
"Update Password": "Uppdatera lösenord",
|
||||||
|
"Advanced": "Avancerat",
|
||||||
|
"Please use this option carefully!": "Använd detta alternativ försiktigt!",
|
||||||
|
"Enable Auth": "Aktivera Auth",
|
||||||
|
"Disable Auth": "Avaktivera Auth",
|
||||||
|
"I understand, please disable": "Jag förstår, vänligen inaktivera",
|
||||||
|
"Leave": "Lämna",
|
||||||
|
"Frontend Version": "Frontendversion",
|
||||||
|
"Check Update On GitHub": "Kontrollera Uppdatering på GitHub",
|
||||||
|
"Show update if available": "Visa uppdatering om tillgänglig",
|
||||||
|
"Also check beta release": "Kontrollera även betaversionen",
|
||||||
|
"Remember me": "Kom ihåg mig",
|
||||||
|
"Login": "Logga in",
|
||||||
|
"Username": "Användarnamn",
|
||||||
|
"Password": "Lösenord",
|
||||||
|
"Settings": "Inställningar",
|
||||||
|
"Logout": "Logga ut",
|
||||||
|
"Lowercase only": "Endast små tecken",
|
||||||
|
"Convert to Compose": "Omvandla till Compose",
|
||||||
|
"Docker Run": "Docker Run",
|
||||||
|
"active": "aktiv",
|
||||||
|
"exited": "avslutad",
|
||||||
|
"inactive": "inaktiv",
|
||||||
|
"Appearance": "Utseende",
|
||||||
|
"Security": "Säkerhet",
|
||||||
|
"About": "Om",
|
||||||
|
"Allowed commands:": "Tillåtna kommandon:",
|
||||||
|
"Internal Networks": "Interna Nätverk",
|
||||||
|
"External Networks": "Externa Nätverk",
|
||||||
|
"No External Networks": "Inga Externa Nätverk"
|
||||||
|
}
|
95
frontend/src/lang/th.json
Normal file
95
frontend/src/lang/th.json
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
{
|
||||||
|
"languageName": "ไทย",
|
||||||
|
"Create your admin account": "สร้างบัญชีผู้ดูแลระบบของคุณ",
|
||||||
|
"authIncorrectCreds": "ชื่อผู้ใช้หรือรหัสผ่านไม่ถูกต้อง",
|
||||||
|
"PasswordsDoNotMatch": "รหัสผ่านไม่ตรงกัน",
|
||||||
|
"Repeat Password": "ยืนยันรหัสผ่าน",
|
||||||
|
"Create": "สร้าง",
|
||||||
|
"signedInDisp": "ลงชื่อเข้าใช้ในชื่อ {0}",
|
||||||
|
"signedInDispDisabled": "ปิดใช้งาน Auth",
|
||||||
|
"home": "หน้าหลักe",
|
||||||
|
"console": "คอนโซล",
|
||||||
|
"registry": "Registry",
|
||||||
|
"compose": "Compose",
|
||||||
|
"addFirstStackMsg": "Compose stack แรกของคุณ",
|
||||||
|
"stackName": "ชื่อ Stack",
|
||||||
|
"deployStack": "ปรับใช้",
|
||||||
|
"deleteStack": "ลบ",
|
||||||
|
"stopStack": "หยุด",
|
||||||
|
"restartStack": "เริ่มใหม่",
|
||||||
|
"updateStack": "อัปเดต",
|
||||||
|
"startStack": "เริ่มต้น",
|
||||||
|
"downStack": "หยุดและปิด",
|
||||||
|
"editStack": "แก้ไข",
|
||||||
|
"discardStack": "ยกเลิก",
|
||||||
|
"saveStackDraft": "บันทึก",
|
||||||
|
"notAvailableShort": "N/A",
|
||||||
|
"deleteStackMsg": "คุณแน่ใจหรือไม่ว่าต้องการลบ stack นี้",
|
||||||
|
"stackNotManagedByDockgeMsg": "stack นี้ไม่ได้รับการจัดการโดย Dockge",
|
||||||
|
"primaryHostname": "ชื่อโฮสต์หลัก",
|
||||||
|
"general": "ทั่วไป",
|
||||||
|
"container": "Container | Containers",
|
||||||
|
"scanFolder": "สแกนโฟลเดอร์ Stacks",
|
||||||
|
"dockerImage": "Image",
|
||||||
|
"restartPolicyUnlessStopped": "Unless Stopped",
|
||||||
|
"restartPolicyAlways": "Always",
|
||||||
|
"restartPolicyOnFailure": "On Failure",
|
||||||
|
"restartPolicyNo": "No",
|
||||||
|
"environmentVariable": "Environment Variable | Environment Variables",
|
||||||
|
"restartPolicy": "เริ่มต้น Policy ใหม่",
|
||||||
|
"containerName": "ชื่อ Container",
|
||||||
|
"port": "พอร์ต | พอร์ต",
|
||||||
|
"volume": "ปริมาณ | ปริมาณ",
|
||||||
|
"network": "เครือข่าย | เครือข่าย",
|
||||||
|
"dependsOn": "Container Dependency | Container Dependencies",
|
||||||
|
"addListItem": "เพิ่ม {0}",
|
||||||
|
"deleteContainer": "ลบ",
|
||||||
|
"addContainer": "เพิ่ม Container",
|
||||||
|
"addNetwork": "เพิ่ม เครือข่าย",
|
||||||
|
"disableauth.message1": "คุณแน่ใจหรือไม่ว่าต้องการ <strong>ปิดใช้งานการตรวจสอบสิทธิ์</strong>?",
|
||||||
|
"disableauth.message2": "ได้รับการออกแบบมาสำหรับสถานการณ์ <strong>ที่คุณตั้งใจจะใช้การตรวจสอบสิทธิ์ของบุคคลที่สาม</strong> หน้า Dockge เช่น Cloudflare Access, Authelia หรือกลไกการตรวจสอบสิทธิ์อื่นๆ",
|
||||||
|
"passwordNotMatchMsg": "รหัสผ่านซ้ำไม่ตรงกัน",
|
||||||
|
"autoGet": "รับอัตโนมัติ",
|
||||||
|
"add": "เพิ่ม",
|
||||||
|
"Edit": "แก้ไข",
|
||||||
|
"applyToYAML": "นำไปใช้เป็น YAML",
|
||||||
|
"createExternalNetwork": "สร้าง",
|
||||||
|
"addInternalNetwork": "เพิ่ม",
|
||||||
|
"Save": "บันทึก",
|
||||||
|
"Language": "ภาษา",
|
||||||
|
"Current User": "ผู้ใช้งานปัจจุบัน",
|
||||||
|
"Change Password": "เปลี่ยนรหัสผ่าน",
|
||||||
|
"Current Password": "รหัสผ่านปัจจุบัน",
|
||||||
|
"New Password": "รหัสผ่านใหม่",
|
||||||
|
"Repeat New Password": "รหัสผ่านใหม่ซ้ำ",
|
||||||
|
"Update Password": "อัปเดตรหัสผ่าน",
|
||||||
|
"Advanced": "ขั้นสูง",
|
||||||
|
"Please use this option carefully!": "โปรดใช้ตัวเลือกนี้อย่างระมัดระวัง!",
|
||||||
|
"Enable Auth": "เปิดใช้งาน Auth",
|
||||||
|
"Disable Auth": "ปิดใช้งาน Auth",
|
||||||
|
"I understand, please disable": "ฉันเข้าใจ กรุณาปิดการใช้งาน",
|
||||||
|
"Leave": "ออก",
|
||||||
|
"Frontend Version": "เวอร์ชัน Frontend",
|
||||||
|
"Check Update On GitHub": "ตรวจสอบการอัปเดตบน GitHub",
|
||||||
|
"Show update if available": "แสดงการอัปเดตหากมี",
|
||||||
|
"Also check beta release": "สามารถตรวจสอบรุ่นเบต้าได้",
|
||||||
|
"Remember me": "จดจำฉัน",
|
||||||
|
"Login": "เข้าสู่ระบบ",
|
||||||
|
"Username": "ชื่อผู้ใช้",
|
||||||
|
"Password": "รหัสผ่าน",
|
||||||
|
"Settings": "การตั้งค่า",
|
||||||
|
"Logout": "ออกจากระบบ",
|
||||||
|
"Lowercase only": "ตัวเล็กทั้งหมด",
|
||||||
|
"Convert to Compose": "แปลงเป็น Compose",
|
||||||
|
"Docker Run": "เรียกใช้ Docker",
|
||||||
|
"active": "ใช้งานอยู่",
|
||||||
|
"exited": "ปิดลงแล้ว",
|
||||||
|
"inactive": "ไม่ได้ใช้งาน",
|
||||||
|
"Appearance": "รูปลักษณ์",
|
||||||
|
"Security": "ความปลอดภัย",
|
||||||
|
"About": "เกี่ยวกับ",
|
||||||
|
"Allowed commands:": "คำสั่งที่อนุญาต:",
|
||||||
|
"Internal Networks": "เครือข่ายภายใน",
|
||||||
|
"External Networks": "เครือข่ายภายนอก",
|
||||||
|
"No External Networks": "ไม่มีเครือข่ายภายนอก"
|
||||||
|
}
|
@ -12,7 +12,7 @@
|
|||||||
"registry": "Kayıt Defteri",
|
"registry": "Kayıt Defteri",
|
||||||
"compose": "Compose",
|
"compose": "Compose",
|
||||||
"addFirstStackMsg": "İlk yığınınızı oluşturun!",
|
"addFirstStackMsg": "İlk yığınınızı oluşturun!",
|
||||||
"stackName" : "Yığın Adı",
|
"stackName": "Yığın Adı",
|
||||||
"deployStack": "Dağıtmak",
|
"deployStack": "Dağıtmak",
|
||||||
"deleteStack": "Sil",
|
"deleteStack": "Sil",
|
||||||
"stopStack": "Dudur",
|
"stopStack": "Dudur",
|
||||||
@ -22,7 +22,7 @@
|
|||||||
"editStack": "Düzenle",
|
"editStack": "Düzenle",
|
||||||
"discardStack": "Çıkar",
|
"discardStack": "Çıkar",
|
||||||
"saveStackDraft": "Kaydet",
|
"saveStackDraft": "Kaydet",
|
||||||
"notAvailableShort" : "N/A",
|
"notAvailableShort": "N/A",
|
||||||
"deleteStackMsg": "Bu yığını silmek istediğinizden emin misiniz?",
|
"deleteStackMsg": "Bu yığını silmek istediğinizden emin misiniz?",
|
||||||
"stackNotManagedByDockgeMsg": "Bu yığın Dockge tarafından yönetilmemektedir.",
|
"stackNotManagedByDockgeMsg": "Bu yığın Dockge tarafından yönetilmemektedir.",
|
||||||
"primaryHostname": "Birincil Ana Bilgisayar Adı",
|
"primaryHostname": "Birincil Ana Bilgisayar Adı",
|
||||||
@ -90,5 +90,13 @@
|
|||||||
"Allowed commands:": "İzin verilen komutlar:",
|
"Allowed commands:": "İzin verilen komutlar:",
|
||||||
"Internal Networks": "İç Ağlar",
|
"Internal Networks": "İç Ağlar",
|
||||||
"External Networks": "Dış Ağlar",
|
"External Networks": "Dış Ağlar",
|
||||||
"No External Networks": "Dış Ağ Yok"
|
"No External Networks": "Dış Ağ Yok",
|
||||||
|
"extra": "Ekstra",
|
||||||
|
"reverseProxyMsg1": "Ters Proxy mi kullanıyorsunuz?",
|
||||||
|
"reverseProxyMsg2": "WebSocket için nasıl yapılandırma yapılacağını kontrol edin",
|
||||||
|
"reconnecting...": "Yeniden bağlanıyor…",
|
||||||
|
"connecting...": "Soket sunucusuna bağlanıyor…",
|
||||||
|
"url": "URL | URL’ler",
|
||||||
|
"Cannot connect to the socket server.": "Soket sunucusuna bağlanılamıyor.",
|
||||||
|
"downStack": "Durdur & Kapat"
|
||||||
}
|
}
|
||||||
|
102
frontend/src/lang/uk-UA.json
Normal file
102
frontend/src/lang/uk-UA.json
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
{
|
||||||
|
"languageName": "Українська",
|
||||||
|
"Create your admin account": "Створити акаунт адміністратора",
|
||||||
|
"authIncorrectCreds": "Неправильне ім'я користувача або пароль.",
|
||||||
|
"PasswordsDoNotMatch": "Паролі не збігаються.",
|
||||||
|
"Repeat Password": "Повторіть пароль",
|
||||||
|
"Create": "Створити",
|
||||||
|
"signedInDisp": "Авторизовано як {0}",
|
||||||
|
"signedInDispDisabled": "Авторизацію вимкнено.",
|
||||||
|
"home": "Головна",
|
||||||
|
"console": "Консоль",
|
||||||
|
"registry": "Registry",
|
||||||
|
"compose": "Compose",
|
||||||
|
"addFirstStackMsg": "Додайте свій перший стек!",
|
||||||
|
"stackName": "Назва стеку",
|
||||||
|
"deployStack": "Розгорнути",
|
||||||
|
"deleteStack": "Видалити",
|
||||||
|
"stopStack": "Зупинити",
|
||||||
|
"restartStack": "Перезапустити",
|
||||||
|
"updateStack": "Оновити",
|
||||||
|
"startStack": "Запустити",
|
||||||
|
"editStack": "Редагувати",
|
||||||
|
"discardStack": "Відмінити",
|
||||||
|
"saveStackDraft": "Зберегти",
|
||||||
|
"notAvailableShort": "Н/Д",
|
||||||
|
"deleteStackMsg": "Ви впевнені що хочете видалити цей стек?",
|
||||||
|
"stackNotManagedByDockgeMsg": "Даний стек не управляється Dockge.",
|
||||||
|
"primaryHostname": "Назва хосту",
|
||||||
|
"general": "Загальне",
|
||||||
|
"container": "Контейнер | Контейнери",
|
||||||
|
"scanFolder": "Сканувати папку зі стеками",
|
||||||
|
"dockerImage": "Образ",
|
||||||
|
"restartPolicyUnlessStopped": "Доки не буде зупинено",
|
||||||
|
"restartPolicyAlways": "Завжди",
|
||||||
|
"restartPolicyOnFailure": "При падінні",
|
||||||
|
"restartPolicyNo": "Ніколи",
|
||||||
|
"environmentVariable": "Змінна середовища | змінні середовища",
|
||||||
|
"restartPolicy": "Перезапуск",
|
||||||
|
"containerName": "Назва контейнеру",
|
||||||
|
"port": "Порт | Порти",
|
||||||
|
"volume": "Сховище | Сховища",
|
||||||
|
"network": "Мережа | Мережі",
|
||||||
|
"dependsOn": "Залежність контейнера | Залежності контейнеру",
|
||||||
|
"addListItem": "Додати {0}",
|
||||||
|
"deleteContainer": "Видалити",
|
||||||
|
"addContainer": "Додати Контейнер",
|
||||||
|
"addNetwork": "Додати Мережу",
|
||||||
|
"disableauth.message1": "Ви впевнені що хочете <strong>вимкнути авторизацію</strong>?",
|
||||||
|
"disableauth.message2": "Це призначено для сценаріїв, <strong>де ви збираєтесь використати сторонню авторизацію</strong> перед Dockge, наприклад Cloudflare Access, Authelia чи інші.",
|
||||||
|
"passwordNotMatchMsg": "Повторення паролю не збігається.",
|
||||||
|
"autoGet": "Отримати",
|
||||||
|
"add": "Додати",
|
||||||
|
"Edit": "Змінити",
|
||||||
|
"applyToYAML": "Застосувати для YAML",
|
||||||
|
"createExternalNetwork": "Створити",
|
||||||
|
"addInternalNetwork": "Додати",
|
||||||
|
"Save": "Зберегти",
|
||||||
|
"Language": "Мова",
|
||||||
|
"Current User": "Користувач",
|
||||||
|
"Change Password": "Змінити пароль",
|
||||||
|
"Current Password": "Поточний пароль",
|
||||||
|
"New Password": "Новий пароль",
|
||||||
|
"Repeat New Password": "Повторіть новий пароль",
|
||||||
|
"Update Password": "Оновити пароль",
|
||||||
|
"Advanced": "Розширені опції",
|
||||||
|
"Please use this option carefully!": "Будь ласка, використовуйте цю опцію з обережністю!",
|
||||||
|
"Enable Auth": "Увімкнути автентифікацію",
|
||||||
|
"Disable Auth": "Вимкнути автентифікацію",
|
||||||
|
"I understand, please disable": "Зрозуміло, все одно вимкнути",
|
||||||
|
"Leave": "Покинути",
|
||||||
|
"Frontend Version": "Версія інтерфейсу",
|
||||||
|
"Check Update On GitHub": "Перевірити оновлення на GitHub",
|
||||||
|
"Show update if available": "Показати оновлення, якщо доступно",
|
||||||
|
"Also check beta release": "Перевіряти оновлення до бета-версії",
|
||||||
|
"Remember me": "Запамʼятати мене",
|
||||||
|
"Login": "Логін",
|
||||||
|
"Username": "Імʼя користувача",
|
||||||
|
"Password": "Пароль",
|
||||||
|
"Settings": "Налаштування",
|
||||||
|
"Logout": "Вийти",
|
||||||
|
"Lowercase only": "Тільки нижній регістр",
|
||||||
|
"Convert to Compose": "Конвертувати в Compose",
|
||||||
|
"Docker Run": "Запустити Docker",
|
||||||
|
"active": "активно",
|
||||||
|
"exited": "завершено",
|
||||||
|
"inactive": "неактивно",
|
||||||
|
"Appearance": "Зовнішній вигляд",
|
||||||
|
"Security": "Безпека",
|
||||||
|
"About": "Про продукт",
|
||||||
|
"Allowed commands:": "Дозволені команди:",
|
||||||
|
"Internal Networks": "Внутрішні мережі",
|
||||||
|
"External Networks": "Зовнішні мережі",
|
||||||
|
"No External Networks": "Немає зовнішніх мереж",
|
||||||
|
"downStack": "Зупинити і вимкнути",
|
||||||
|
"reverseProxyMsg1": "Використовувати зворотній проксі?",
|
||||||
|
"Cannot connect to the socket server.": "Не вдається підключитися до сервера сокетів.",
|
||||||
|
"reconnecting...": "Повторне підключення…",
|
||||||
|
"connecting...": "Підключення до сервера сокетів…",
|
||||||
|
"url": "URL-адреса | URL-адреси",
|
||||||
|
"reverseProxyMsg2": "Перевірте, як налаштувати його для WebSocket",
|
||||||
|
"extra": "Додатково"
|
||||||
|
}
|
@ -12,23 +12,23 @@
|
|||||||
"registry": "رجسٹری",
|
"registry": "رجسٹری",
|
||||||
"compose": "تحریر",
|
"compose": "تحریر",
|
||||||
"addFirstStackMsg": "اپنا پہلا اسٹیک کمپوز کریں!",
|
"addFirstStackMsg": "اپنا پہلا اسٹیک کمپوز کریں!",
|
||||||
"stackName" : "اسٹیک کا نام",
|
"stackName": "اسٹیک کا نام",
|
||||||
"deployStack": "تعینات",
|
"deployStack": "تعینات",
|
||||||
"deleteStack": "حذف کریں",
|
"deleteStack": "حذف کریں",
|
||||||
"stopStack": "روکو",
|
"stopStack": "روکو",
|
||||||
"restartStack": "دوبارہ شروع کریں",
|
"restartStack": "دوبارہ شروع کریں",
|
||||||
"updateStack": "اپ ڈیٹ",
|
"updateStack": "اپ ڈیٹ",
|
||||||
"startStack": "شروع کریں۔",
|
"startStack": "شروع کریں",
|
||||||
"editStack": "ترمیم",
|
"editStack": "ترمیم",
|
||||||
"discardStack": "رد کر دیں۔",
|
"discardStack": "رد کر دیں",
|
||||||
"saveStackDraft": "محفوظ کریں۔",
|
"saveStackDraft": "محفوظ کریں۔",
|
||||||
"notAvailableShort" : "N / A",
|
"notAvailableShort": "N / A",
|
||||||
"deleteStackMsg": "کیا آپ واقعی اس اسٹیک کو حذف کرنا چاہتے ہیں؟",
|
"deleteStackMsg": "کیا آپ واقعی اس اسٹیک کو حذف کرنا چاہتے ہیں؟",
|
||||||
"stackNotManagedByDockgeMsg": "یہ اسٹیک Dockge کے زیر انتظام نہیں ہے۔",
|
"stackNotManagedByDockgeMsg": "یہ اسٹیک Dockge کے زیر انتظام نہیں ہے۔",
|
||||||
"primaryHostname": "بنیادی میزبان نام",
|
"primaryHostname": "بنیادی میزبان نام",
|
||||||
"general": "جنرل",
|
"general": "جنرل",
|
||||||
"container": "کنٹینر | کنٹینرز",
|
"container": "کنٹینر | کنٹینرز",
|
||||||
"scanFolder": "اسٹیک فولڈر کو اسکین کریں۔",
|
"scanFolder": "اسٹیک فولڈر کو اسکین کریں",
|
||||||
"dockerImage": "تصویر",
|
"dockerImage": "تصویر",
|
||||||
"restartPolicyUnlessStopped": "جب تک روکا نہیں جاتا",
|
"restartPolicyUnlessStopped": "جب تک روکا نہیں جاتا",
|
||||||
"restartPolicyAlways": "ہمیشہ",
|
"restartPolicyAlways": "ہمیشہ",
|
||||||
@ -51,7 +51,7 @@
|
|||||||
"autoGet": "آٹو حاصل کریں",
|
"autoGet": "آٹو حاصل کریں",
|
||||||
"add": "شامل کریں",
|
"add": "شامل کریں",
|
||||||
"Edit": "ترمیم",
|
"Edit": "ترمیم",
|
||||||
"applyToYAML": "YAML پر درخواست دیں۔",
|
"applyToYAML": "YAML پر درخواست دیں",
|
||||||
"createExternalNetwork": "بنانا",
|
"createExternalNetwork": "بنانا",
|
||||||
"addInternalNetwork": "شامل کریں",
|
"addInternalNetwork": "شامل کریں",
|
||||||
"Save": "محفوظ کریں",
|
"Save": "محفوظ کریں",
|
||||||
@ -64,12 +64,12 @@
|
|||||||
"Update Password": "پاس ورڈ اپ ڈیٹ کریں",
|
"Update Password": "پاس ورڈ اپ ڈیٹ کریں",
|
||||||
"Advanced": "ترقی یافتہ",
|
"Advanced": "ترقی یافتہ",
|
||||||
"Please use this option carefully!": "براہ کرم اس اختیار کو احتیاط سے استعمال کریں!",
|
"Please use this option carefully!": "براہ کرم اس اختیار کو احتیاط سے استعمال کریں!",
|
||||||
"Enable Auth": "تصدیق کو فعال کریں۔",
|
"Enable Auth": "تصدیق کو فعال کریں",
|
||||||
"Disable Auth": "توثیق کو غیر فعال کریں۔",
|
"Disable Auth": "توثیق کو غیر فعال کریں",
|
||||||
"I understand, please disable": "میں سمجھتا ہوں، براہ کرم غیر فعال کریں۔",
|
"I understand, please disable": "میں سمجھتا ہوں، براہ کرم غیر فعال کریں",
|
||||||
"Leave": "چھوڑ دو",
|
"Leave": "چھوڑ دو",
|
||||||
"Frontend Version": "فرنٹ اینڈ ورژن",
|
"Frontend Version": "فرنٹ اینڈ ورژن",
|
||||||
"Check Update On GitHub": "گیتوب پر اپ ڈیٹ چیک کریں۔",
|
"Check Update On GitHub": "گیتوب پر اپ ڈیٹ چیک کریں",
|
||||||
"Show update if available": "اگر دستیاب ہو تو اپ ڈیٹ دکھائیں",
|
"Show update if available": "اگر دستیاب ہو تو اپ ڈیٹ دکھائیں",
|
||||||
"Also check beta release": "بیٹا ریلیز بھی چیک کریں",
|
"Also check beta release": "بیٹا ریلیز بھی چیک کریں",
|
||||||
"Remember me": "مجھے پہچانتے ہو",
|
"Remember me": "مجھے پہچانتے ہو",
|
||||||
@ -90,5 +90,13 @@
|
|||||||
"Allowed commands:": "اجازت شدہ احکامات:",
|
"Allowed commands:": "اجازت شدہ احکامات:",
|
||||||
"Internal Networks": "اندرونی نیٹ ورکس",
|
"Internal Networks": "اندرونی نیٹ ورکس",
|
||||||
"External Networks": "بیرونی نیٹ ورکس",
|
"External Networks": "بیرونی نیٹ ورکس",
|
||||||
"No External Networks": "کوئی بیرونی نیٹ ورک نہیں"
|
"No External Networks": "کوئی بیرونی نیٹ ورک نہیں",
|
||||||
|
"reverseProxyMsg1": "ایک ریورس پراکسی کا استعمال کرتے ہوئے؟",
|
||||||
|
"Cannot connect to the socket server.": "ساکٹ سرور سے منسلک نہیں ہو سکتا۔",
|
||||||
|
"reconnecting...": "دوبارہ منسلک ہو رہا ہے…",
|
||||||
|
"connecting...": "ساکٹ سرور سے منسلک ہو رہا ہے…",
|
||||||
|
"url": "یو آر ایل | یو آر ایل",
|
||||||
|
"extra": "اضافی",
|
||||||
|
"downStack": "اسٹاپ اینڈ ڈاؤن",
|
||||||
|
"reverseProxyMsg2": "اسے WebSocket کے لیے ترتیب دینے کا طریقہ چیک کریں"
|
||||||
}
|
}
|
||||||
|
@ -1,18 +1,18 @@
|
|||||||
{
|
{
|
||||||
"languageName": "简体中文",
|
"languageName": "简体中文",
|
||||||
"Create your admin account": "创建你的管理员账号",
|
"Create your admin account": "创建你的管理员账号",
|
||||||
"authIncorrectCreds": "用户名或密码错误",
|
"authIncorrectCreds": "用户名或密码错误。",
|
||||||
"PasswordsDoNotMatch": "两次输入的密码不一致。",
|
"PasswordsDoNotMatch": "两次输入的密码不一致。",
|
||||||
"Repeat Password": "重复以确认密码",
|
"Repeat Password": "重复以确认密码",
|
||||||
"Create": "创建",
|
"Create": "创建",
|
||||||
"signedInDisp": "当前用户: {0}",
|
"signedInDisp": "当前用户: {0}",
|
||||||
"signedInDispDisabled": "已禁用身份验证",
|
"signedInDispDisabled": "已禁用身份验证。",
|
||||||
"home": "主页",
|
"home": "主页",
|
||||||
"console": "终端",
|
"console": "终端",
|
||||||
"registry": "镜像仓库",
|
"registry": "镜像仓库",
|
||||||
"compose": "Compose",
|
"compose": "Compose",
|
||||||
"addFirstStackMsg": "组合你的第一个堆栈!",
|
"addFirstStackMsg": "组合你的第一个堆栈!",
|
||||||
"stackName" : "堆栈名称",
|
"stackName": "堆栈名称",
|
||||||
"deployStack": "部署",
|
"deployStack": "部署",
|
||||||
"deleteStack": "删除",
|
"deleteStack": "删除",
|
||||||
"stopStack": "停止",
|
"stopStack": "停止",
|
||||||
@ -22,9 +22,9 @@
|
|||||||
"editStack": "编辑",
|
"editStack": "编辑",
|
||||||
"discardStack": "放弃",
|
"discardStack": "放弃",
|
||||||
"saveStackDraft": "保存",
|
"saveStackDraft": "保存",
|
||||||
"notAvailableShort" : "不可用",
|
"notAvailableShort": "不可用",
|
||||||
"deleteStackMsg": "你确定要删除这个堆栈吗?",
|
"deleteStackMsg": "你确定要删除这个堆栈吗?",
|
||||||
"stackNotManagedByDockgeMsg": "这个堆栈不由Dockge管理",
|
"stackNotManagedByDockgeMsg": "这个堆栈不由Dockge管理。",
|
||||||
"primaryHostname": "主机名",
|
"primaryHostname": "主机名",
|
||||||
"general": "常规",
|
"general": "常规",
|
||||||
"container": "容器 | 容器组",
|
"container": "容器 | 容器组",
|
||||||
@ -90,5 +90,13 @@
|
|||||||
"Allowed commands:": "允许使用的指令:",
|
"Allowed commands:": "允许使用的指令:",
|
||||||
"Internal Networks": "内部网络",
|
"Internal Networks": "内部网络",
|
||||||
"External Networks": "外部网络",
|
"External Networks": "外部网络",
|
||||||
"No External Networks": "无外部网络"
|
"No External Networks": "无外部网络",
|
||||||
|
"reconnecting...": "重连中…",
|
||||||
|
"reverseProxyMsg2": "检查如何配置WebSocket",
|
||||||
|
"reverseProxyMsg1": "正在使用反向代理?",
|
||||||
|
"connecting...": "正在连接到socket服务器…",
|
||||||
|
"Cannot connect to the socket server.": "无法连接到socket服务器。",
|
||||||
|
"url": "网址 | 网址",
|
||||||
|
"extra": "额外",
|
||||||
|
"downStack": "停止并删除"
|
||||||
}
|
}
|
||||||
|
102
frontend/src/lang/zh-TW.json
Normal file
102
frontend/src/lang/zh-TW.json
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
{
|
||||||
|
"languageName": "繁體中文(台灣)",
|
||||||
|
"Create your admin account": "建立您的管理員帳號",
|
||||||
|
"authIncorrectCreds": "使用者名稱或密碼錯誤。",
|
||||||
|
"PasswordsDoNotMatch": "兩次輸入的密碼不一致。",
|
||||||
|
"Repeat Password": "重複以確認密碼",
|
||||||
|
"Create": "建立",
|
||||||
|
"signedInDisp": "目前使用者:{0}",
|
||||||
|
"signedInDispDisabled": "已停用身份驗證。",
|
||||||
|
"home": "首頁",
|
||||||
|
"console": "主控台",
|
||||||
|
"registry": "映像倉庫",
|
||||||
|
"compose": "Compose",
|
||||||
|
"addFirstStackMsg": "組合您的第一個堆疊!",
|
||||||
|
"stackName": "堆疊名稱",
|
||||||
|
"deployStack": "部署",
|
||||||
|
"deleteStack": "刪除",
|
||||||
|
"stopStack": "停止",
|
||||||
|
"restartStack": "重啟",
|
||||||
|
"updateStack": "更新",
|
||||||
|
"startStack": "啟動",
|
||||||
|
"editStack": "編輯",
|
||||||
|
"discardStack": "捨棄",
|
||||||
|
"saveStackDraft": "儲存",
|
||||||
|
"notAvailableShort": "不可用",
|
||||||
|
"deleteStackMsg": "您確定要刪除這個堆疊嗎?",
|
||||||
|
"stackNotManagedByDockgeMsg": "這個堆疊不由 Dockge 管理。",
|
||||||
|
"primaryHostname": "主機名稱",
|
||||||
|
"general": "一般",
|
||||||
|
"container": "容器 | 容器群組",
|
||||||
|
"scanFolder": "掃描堆疊資料夾",
|
||||||
|
"dockerImage": "映像",
|
||||||
|
"restartPolicyUnlessStopped": "除非手動停止",
|
||||||
|
"restartPolicyAlways": "始終",
|
||||||
|
"restartPolicyOnFailure": "在失敗時",
|
||||||
|
"restartPolicyNo": "不重啟",
|
||||||
|
"environmentVariable": "環境變數 | 環境變數群組",
|
||||||
|
"restartPolicy": "重啟策略",
|
||||||
|
"containerName": "容器名稱",
|
||||||
|
"port": "連接埠 | 連接埠群組",
|
||||||
|
"volume": "資料卷 | 資料卷群組",
|
||||||
|
"network": "網路 | 網路群組",
|
||||||
|
"dependsOn": "容器依賴 | 容器依賴關係",
|
||||||
|
"addListItem": "新增 {0}",
|
||||||
|
"deleteContainer": "刪除容器",
|
||||||
|
"addContainer": "新增容器",
|
||||||
|
"addNetwork": "新增網路",
|
||||||
|
"disableauth.message1": "您確定要<strong>停用身份驗證</strong>嗎?",
|
||||||
|
"disableauth.message2": "該選項設計用於某些場景,<strong>例如在 Dockge 之上接入第三方認證</strong>,如 Cloudflare Access、Authelia 或其他認證機制。如果您不清楚這個選項的作用,請不要停用驗證!",
|
||||||
|
"passwordNotMatchMsg": "兩次輸入的密碼不一致。",
|
||||||
|
"autoGet": "自動取得",
|
||||||
|
"add": "新增",
|
||||||
|
"Edit": "編輯",
|
||||||
|
"applyToYAML": "應用到YAML",
|
||||||
|
"createExternalNetwork": "建立",
|
||||||
|
"addInternalNetwork": "新增",
|
||||||
|
"Save": "儲存",
|
||||||
|
"Language": "語言",
|
||||||
|
"Current User": "目前使用者",
|
||||||
|
"Change Password": "更換密碼",
|
||||||
|
"Current Password": "目前密碼",
|
||||||
|
"New Password": "新密碼",
|
||||||
|
"Repeat New Password": "重複以確認新密碼",
|
||||||
|
"Update Password": "更新密碼",
|
||||||
|
"Advanced": "進階",
|
||||||
|
"Please use this option carefully!": "請謹慎使用該選項!",
|
||||||
|
"Enable Auth": "啟用驗證",
|
||||||
|
"Disable Auth": "停用驗證",
|
||||||
|
"I understand, please disable": "我已了解風險,確認停用",
|
||||||
|
"Leave": "離開",
|
||||||
|
"Frontend Version": "前端版本",
|
||||||
|
"Check Update On GitHub": "在 GitHub 上檢查更新",
|
||||||
|
"Show update if available": "有更新時提醒我",
|
||||||
|
"Also check beta release": "同時檢查 Beta 渠道更新",
|
||||||
|
"Remember me": "記住我",
|
||||||
|
"Login": "登入",
|
||||||
|
"Username": "使用者名稱",
|
||||||
|
"Password": "密碼",
|
||||||
|
"Settings": "設定",
|
||||||
|
"Logout": "登出",
|
||||||
|
"Lowercase only": "僅小寫字母",
|
||||||
|
"Convert to Compose": "轉換為 Compose 格式",
|
||||||
|
"Docker Run": "Docker 啟動",
|
||||||
|
"active": "已啟動",
|
||||||
|
"exited": "已退出",
|
||||||
|
"inactive": "未啟動",
|
||||||
|
"Appearance": "外觀",
|
||||||
|
"Security": "安全",
|
||||||
|
"About": "關於",
|
||||||
|
"Allowed commands:": "允許使用的指令:",
|
||||||
|
"Internal Networks": "內部網路",
|
||||||
|
"External Networks": "外部網路",
|
||||||
|
"No External Networks": "無外部網路",
|
||||||
|
"downStack": "停止",
|
||||||
|
"reverseProxyMsg1": "在使用反向代理吗?",
|
||||||
|
"reverseProxyMsg2": "點擊這裡了解如何為 WebSocket 配置反向代理",
|
||||||
|
"Cannot connect to the socket server.": "無法連接到 Socket 伺服器。",
|
||||||
|
"reconnecting...": "重新連線中…",
|
||||||
|
"connecting...": "連線至 Socket 伺服器中…",
|
||||||
|
"url": "網址 | 網址",
|
||||||
|
"extra": "額外"
|
||||||
|
}
|
@ -3,6 +3,9 @@
|
|||||||
<div v-if="! $root.socketIO.connected && ! $root.socketIO.firstConnect" class="lost-connection">
|
<div v-if="! $root.socketIO.connected && ! $root.socketIO.firstConnect" class="lost-connection">
|
||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
{{ $root.socketIO.connectionErrorMsg }}
|
{{ $root.socketIO.connectionErrorMsg }}
|
||||||
|
<div v-if="$root.socketIO.showReverseProxyGuide">
|
||||||
|
{{ $t("reverseProxyMsg1") }} <a href="https://github.com/louislam/uptime-kuma/wiki/Reverse-Proxy" target="_blank">{{ $t("reverseProxyMsg2") }}</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -13,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">
|
||||||
@ -82,6 +85,10 @@
|
|||||||
</header>
|
</header>
|
||||||
|
|
||||||
<main>
|
<main>
|
||||||
|
<div v-if="$root.socketIO.connecting" class="container mt-5">
|
||||||
|
<h4>{{ $t("connecting...") }}</h4>
|
||||||
|
</div>
|
||||||
|
|
||||||
<router-view v-if="$root.loggedIn" />
|
<router-view v-if="$root.loggedIn" />
|
||||||
<Login v-if="! $root.loggedIn && $root.allowLoginDialog" />
|
<Login v-if="! $root.loggedIn && $root.allowLoginDialog" />
|
||||||
</main>
|
</main>
|
||||||
|
@ -19,6 +19,7 @@ export default defineComponent({
|
|||||||
initedSocketIO: false,
|
initedSocketIO: false,
|
||||||
connectionErrorMsg: `${this.$t("Cannot connect to the socket server.")} ${this.$t("Reconnecting...")}`,
|
connectionErrorMsg: `${this.$t("Cannot connect to the socket server.")} ${this.$t("Reconnecting...")}`,
|
||||||
showReverseProxyGuide: true,
|
showReverseProxyGuide: true,
|
||||||
|
connecting: false,
|
||||||
},
|
},
|
||||||
info: {
|
info: {
|
||||||
|
|
||||||
@ -103,6 +104,10 @@ export default defineComponent({
|
|||||||
url = location.protocol + "//" + location.host;
|
url = location.protocol + "//" + location.host;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let connectingMsgTimeout = setTimeout(() => {
|
||||||
|
this.socketIO.connecting = true;
|
||||||
|
}, 1500);
|
||||||
|
|
||||||
socket = io(url, {
|
socket = io(url, {
|
||||||
transports: [ "websocket", "polling" ]
|
transports: [ "websocket", "polling" ]
|
||||||
});
|
});
|
||||||
@ -110,6 +115,9 @@ export default defineComponent({
|
|||||||
socket.on("connect", () => {
|
socket.on("connect", () => {
|
||||||
console.log("Connected to the socket server");
|
console.log("Connected to the socket server");
|
||||||
|
|
||||||
|
clearTimeout(connectingMsgTimeout);
|
||||||
|
this.socketIO.connecting = false;
|
||||||
|
|
||||||
this.socketIO.connectCount++;
|
this.socketIO.connectCount++;
|
||||||
this.socketIO.connected = true;
|
this.socketIO.connected = true;
|
||||||
this.socketIO.showReverseProxyGuide = false;
|
this.socketIO.showReverseProxyGuide = false;
|
||||||
@ -143,10 +151,11 @@ export default defineComponent({
|
|||||||
|
|
||||||
socket.on("connect_error", (err) => {
|
socket.on("connect_error", (err) => {
|
||||||
console.error(`Failed to connect to the backend. Socket.io connect_error: ${err.message}`);
|
console.error(`Failed to connect to the backend. Socket.io connect_error: ${err.message}`);
|
||||||
this.socketIO.connectionErrorMsg = `${this.$t("Cannot connect to the socket server.")} [${err}] ${this.$t("Reconnecting...")}`;
|
this.socketIO.connectionErrorMsg = `${this.$t("Cannot connect to the socket server.")} [${err}] ${this.$t("reconnecting...")}`;
|
||||||
this.socketIO.showReverseProxyGuide = true;
|
this.socketIO.showReverseProxyGuide = true;
|
||||||
this.socketIO.connected = false;
|
this.socketIO.connected = false;
|
||||||
this.socketIO.firstConnect = false;
|
this.socketIO.firstConnect = false;
|
||||||
|
this.socketIO.connecting = false;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Custom Events
|
// Custom Events
|
||||||
|
@ -41,7 +41,7 @@
|
|||||||
{{ $t("stopStack") }}
|
{{ $t("stopStack") }}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<BDropdown v-if="!isEditMode && active" right text="" variant="normal">
|
<BDropdown right text="" variant="normal">
|
||||||
<BDropdownItem @click="downStack">
|
<BDropdownItem @click="downStack">
|
||||||
<font-awesome-icon icon="stop" class="me-1" />
|
<font-awesome-icon icon="stop" class="me-1" />
|
||||||
{{ $t("downStack") }}
|
{{ $t("downStack") }}
|
||||||
@ -56,6 +56,13 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- URLs -->
|
||||||
|
<div v-if="urls.length > 0" class="mb-3">
|
||||||
|
<a v-for="(url, index) in urls" :key="index" target="_blank" :href="url.url">
|
||||||
|
<span class="badge bg-secondary me-2">{{ url.display }}</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Progress Terminal -->
|
<!-- Progress Terminal -->
|
||||||
<transition name="slide-fade" appear>
|
<transition name="slide-fade" appear>
|
||||||
<Terminal
|
<Terminal
|
||||||
@ -111,6 +118,20 @@
|
|||||||
|
|
||||||
<button v-if="false && isEditMode && jsonConfig.services && Object.keys(jsonConfig.services).length > 0" class="btn btn-normal mb-3" @click="addContainer">{{ $t("addContainer") }}</button>
|
<button v-if="false && isEditMode && jsonConfig.services && Object.keys(jsonConfig.services).length > 0" class="btn btn-normal mb-3" @click="addContainer">{{ $t("addContainer") }}</button>
|
||||||
|
|
||||||
|
<!-- General -->
|
||||||
|
<div v-if="isEditMode">
|
||||||
|
<h4 class="mb-3">{{ $t("extra") }}</h4>
|
||||||
|
<div class="shadow-box big-padding mb-3">
|
||||||
|
<!-- URLs -->
|
||||||
|
<div class="mb-4">
|
||||||
|
<label class="form-label">
|
||||||
|
{{ $tc("url", 2) }}
|
||||||
|
</label>
|
||||||
|
<ArrayInput name="urls" :display-name="$t('url')" placeholder="https://" object-type="x-dockge" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Combined Terminal Output -->
|
<!-- Combined Terminal Output -->
|
||||||
<div v-show="!isEditMode">
|
<div v-show="!isEditMode">
|
||||||
<h4 class="mb-3">Terminal</h4>
|
<h4 class="mb-3">Terminal</h4>
|
||||||
@ -133,7 +154,7 @@
|
|||||||
ref="editor"
|
ref="editor"
|
||||||
v-model="stack.composeYAML"
|
v-model="stack.composeYAML"
|
||||||
class="yaml-editor"
|
class="yaml-editor"
|
||||||
:highlight="highlighter"
|
:highlight="highlighterYAML"
|
||||||
line-numbers :readonly="!isEditMode"
|
line-numbers :readonly="!isEditMode"
|
||||||
@input="yamlCodeChange"
|
@input="yamlCodeChange"
|
||||||
@focus="editorFocus = true"
|
@focus="editorFocus = true"
|
||||||
@ -144,6 +165,22 @@
|
|||||||
{{ yamlError }}
|
{{ yamlError }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- ENV editor -->
|
||||||
|
<div v-if="isEditMode">
|
||||||
|
<h4 class="mb-3">.env</h4>
|
||||||
|
<div class="shadow-box mb-3 editor-box" :class="{'edit-mode' : isEditMode}">
|
||||||
|
<prism-editor
|
||||||
|
ref="editor"
|
||||||
|
v-model="stack.composeENV"
|
||||||
|
class="env-editor"
|
||||||
|
:highlight="highlighterENV"
|
||||||
|
line-numbers :readonly="!isEditMode"
|
||||||
|
@focus="editorFocus = true"
|
||||||
|
@blur="editorFocus = false"
|
||||||
|
></prism-editor>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div v-if="isEditMode">
|
<div v-if="isEditMode">
|
||||||
<!-- Volumes -->
|
<!-- Volumes -->
|
||||||
<div v-if="false">
|
<div v-if="false">
|
||||||
@ -194,7 +231,7 @@ 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,
|
||||||
@ -202,6 +239,7 @@ import {
|
|||||||
} from "../../../backend/util-common";
|
} from "../../../backend/util-common";
|
||||||
import { BModal } from "bootstrap-vue-next";
|
import { BModal } from "bootstrap-vue-next";
|
||||||
import NetworkInput from "../components/NetworkInput.vue";
|
import NetworkInput from "../components/NetworkInput.vue";
|
||||||
|
import dotenv from "dotenv";
|
||||||
|
|
||||||
const template = `version: "3.8"
|
const template = `version: "3.8"
|
||||||
services:
|
services:
|
||||||
@ -211,10 +249,16 @@ services:
|
|||||||
ports:
|
ports:
|
||||||
- "8080:80"
|
- "8080:80"
|
||||||
`;
|
`;
|
||||||
|
const envDefault = "# VARIABLE=value #comment";
|
||||||
|
|
||||||
let yamlErrorTimeout = null;
|
let yamlErrorTimeout = null;
|
||||||
|
|
||||||
let serviceStatusTimeout = null;
|
let serviceStatusTimeout = null;
|
||||||
|
let prismjsSymbolDefinition = {
|
||||||
|
"symbol": {
|
||||||
|
pattern: /(?<!\$)\$(\{[^{}]*\}|\w+)/,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
@ -234,6 +278,7 @@ export default {
|
|||||||
return {
|
return {
|
||||||
editorFocus: false,
|
editorFocus: false,
|
||||||
jsonConfig: {},
|
jsonConfig: {},
|
||||||
|
envsubstJSONConfig: {},
|
||||||
yamlError: "",
|
yamlError: "",
|
||||||
processing: true,
|
processing: true,
|
||||||
showProgressTerminal: false,
|
showProgressTerminal: false,
|
||||||
@ -252,6 +297,34 @@ export default {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
|
||||||
|
urls() {
|
||||||
|
if (!this.jsonConfig["x-dockge"] || !this.jsonConfig["x-dockge"].urls || !Array.isArray(this.jsonConfig["x-dockge"].urls)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
let urls = [];
|
||||||
|
for (const url of this.jsonConfig["x-dockge"].urls) {
|
||||||
|
let display;
|
||||||
|
try {
|
||||||
|
let obj = new URL(url);
|
||||||
|
let pathname = obj.pathname;
|
||||||
|
if (pathname === "/") {
|
||||||
|
pathname = "";
|
||||||
|
}
|
||||||
|
display = obj.host + pathname + obj.search;
|
||||||
|
} catch (e) {
|
||||||
|
display = url;
|
||||||
|
}
|
||||||
|
|
||||||
|
urls.push({
|
||||||
|
display,
|
||||||
|
url,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return urls;
|
||||||
|
},
|
||||||
|
|
||||||
isAdd() {
|
isAdd() {
|
||||||
return this.$route.path === "/compose" && !this.submitted;
|
return this.$route.path === "/compose" && !this.submitted;
|
||||||
},
|
},
|
||||||
@ -301,6 +374,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) {
|
||||||
@ -319,6 +403,12 @@ export default {
|
|||||||
},
|
},
|
||||||
deep: true,
|
deep: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
$route(to, from) {
|
||||||
|
// Leave Combined Terminal
|
||||||
|
console.debug("leaveCombinedTerminal", from.params.stackName);
|
||||||
|
this.$root.getSocket().emit("leaveCombinedTerminal", this.stack.name, () => {});
|
||||||
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
if (this.isAdd) {
|
if (this.isAdd) {
|
||||||
@ -326,19 +416,26 @@ export default {
|
|||||||
this.isEditMode = true;
|
this.isEditMode = true;
|
||||||
|
|
||||||
let composeYAML;
|
let composeYAML;
|
||||||
|
let composeENV;
|
||||||
|
|
||||||
if (this.$root.composeTemplate) {
|
if (this.$root.composeTemplate) {
|
||||||
composeYAML = this.$root.composeTemplate;
|
composeYAML = this.$root.composeTemplate;
|
||||||
this.$root.composeTemplate = "";
|
this.$root.composeTemplate = "";
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
composeYAML = template;
|
composeYAML = template;
|
||||||
}
|
}
|
||||||
|
if (this.$root.envTemplate) {
|
||||||
|
composeENV = this.$root.envTemplate;
|
||||||
|
this.$root.envTemplate = "";
|
||||||
|
} else {
|
||||||
|
composeENV = envDefault;
|
||||||
|
}
|
||||||
|
|
||||||
// Default Values
|
// Default Values
|
||||||
this.stack = {
|
this.stack = {
|
||||||
name: "",
|
name: "",
|
||||||
composeYAML,
|
composeYAML,
|
||||||
|
composeENV,
|
||||||
isManagedByDockge: true,
|
isManagedByDockge: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -361,7 +458,7 @@ export default {
|
|||||||
clearTimeout(serviceStatusTimeout);
|
clearTimeout(serviceStatusTimeout);
|
||||||
serviceStatusTimeout = setTimeout(async () => {
|
serviceStatusTimeout = setTimeout(async () => {
|
||||||
this.requestServiceStatus();
|
this.requestServiceStatus();
|
||||||
}, 2000);
|
}, 5000);
|
||||||
},
|
},
|
||||||
|
|
||||||
requestServiceStatus() {
|
requestServiceStatus() {
|
||||||
@ -437,7 +534,7 @@ export default {
|
|||||||
|
|
||||||
this.bindTerminal(this.terminalName);
|
this.bindTerminal(this.terminalName);
|
||||||
|
|
||||||
this.$root.getSocket().emit("deployStack", this.stack.name, this.stack.composeYAML, this.isAdd, (res) => {
|
this.$root.getSocket().emit("deployStack", this.stack.name, this.stack.composeYAML, this.stack.composeENV, this.isAdd, (res) => {
|
||||||
this.processing = false;
|
this.processing = false;
|
||||||
this.$root.toastRes(res);
|
this.$root.toastRes(res);
|
||||||
|
|
||||||
@ -451,7 +548,7 @@ export default {
|
|||||||
saveStack() {
|
saveStack() {
|
||||||
this.processing = true;
|
this.processing = true;
|
||||||
|
|
||||||
this.$root.getSocket().emit("saveStack", this.stack.name, this.stack.composeYAML, this.isAdd, (res) => {
|
this.$root.getSocket().emit("saveStack", this.stack.name, this.stack.composeYAML, this.stack.composeENV, this.isAdd, (res) => {
|
||||||
this.processing = false;
|
this.processing = false;
|
||||||
this.$root.toastRes(res);
|
this.$root.toastRes(res);
|
||||||
|
|
||||||
@ -521,36 +618,81 @@ export default {
|
|||||||
this.isEditMode = false;
|
this.isEditMode = false;
|
||||||
},
|
},
|
||||||
|
|
||||||
highlighter(code) {
|
highlighterYAML(code) {
|
||||||
return highlight(code, languages.yaml);
|
if (!languages.yaml_with_symbols) {
|
||||||
|
languages.yaml_with_symbols = languages.insertBefore("yaml", "punctuation", {
|
||||||
|
"symbol": prismjsSymbolDefinition["symbol"]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return highlight(code, languages.yaml_with_symbols);
|
||||||
|
},
|
||||||
|
|
||||||
|
highlighterENV(code) {
|
||||||
|
if (!languages.docker_env) {
|
||||||
|
languages.docker_env = {
|
||||||
|
"comment": {
|
||||||
|
pattern: /(^#| #).*$/m,
|
||||||
|
greedy: true
|
||||||
|
},
|
||||||
|
"keyword": {
|
||||||
|
pattern: /^\w*(?=[:=])/m,
|
||||||
|
greedy: true
|
||||||
|
},
|
||||||
|
"value": {
|
||||||
|
pattern: /(?<=[:=]).*?((?= #)|$)/m,
|
||||||
|
greedy: true,
|
||||||
|
inside: {
|
||||||
|
"string": [
|
||||||
|
{
|
||||||
|
pattern: /^ *'.*?(?<!\\)'/m,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pattern: /^ *".*?(?<!\\)"|^.*$/m,
|
||||||
|
inside: prismjsSymbolDefinition
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return highlight(code, languages.docker_env);
|
||||||
|
},
|
||||||
|
|
||||||
|
yamlToJSON(yaml) {
|
||||||
|
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");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!config.version) {
|
|
||||||
config.version = "3.8";
|
|
||||||
}
|
|
||||||
|
|
||||||
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) {
|
||||||
|
15
package.json
15
package.json
@ -1,7 +1,10 @@
|
|||||||
{
|
{
|
||||||
"name": "dockge",
|
"name": "dockge",
|
||||||
"version": "1.1.1",
|
"version": "1.3.2",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 18.0.0 && <= 18.17.1"
|
||||||
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"fmt": "eslint \"**/*.{ts,vue}\" --fix",
|
"fmt": "eslint \"**/*.{ts,vue}\" --fix",
|
||||||
"lint": "eslint \"**/*.{ts,vue}\"",
|
"lint": "eslint \"**/*.{ts,vue}\"",
|
||||||
@ -14,12 +17,15 @@
|
|||||||
"build:docker-base": "docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/dockge:base -f ./docker/Base.Dockerfile . --push",
|
"build:docker-base": "docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/dockge:base -f ./docker/Base.Dockerfile . --push",
|
||||||
"build:docker": "node ./extra/env2arg.js docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/dockge:latest -t louislam/dockge:1 -t louislam/dockge:$VERSION --target release -f ./docker/Dockerfile . --push",
|
"build:docker": "node ./extra/env2arg.js docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/dockge:latest -t louislam/dockge:1 -t louislam/dockge:$VERSION --target release -f ./docker/Dockerfile . --push",
|
||||||
"build:docker-nightly": "pnpm run build:frontend && docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/dockge:nightly --target nightly -f ./docker/Dockerfile . --push",
|
"build:docker-nightly": "pnpm run build:frontend && docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/dockge:nightly --target nightly -f ./docker/Dockerfile . --push",
|
||||||
|
"build:healthcheck": "docker buildx build -f docker/BuildHealthCheck.Dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/dockge:build-healthcheck . --push",
|
||||||
"start-docker": "docker run --rm -p 5001:5001 --name dockge louislam/dockge:latest",
|
"start-docker": "docker run --rm -p 5001:5001 --name dockge louislam/dockge:latest",
|
||||||
"mark-as-nightly": "tsx ./extra/mark-as-nightly.ts",
|
"mark-as-nightly": "tsx ./extra/mark-as-nightly.ts",
|
||||||
"reformat-changelog": "tsx ./extra/reformat-changelog.ts"
|
"reformat-changelog": "tsx ./extra/reformat-changelog.ts",
|
||||||
|
"reset-password": "tsx ./extra/reset-password.ts"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@homebridge/node-pty-prebuilt-multiarch": "~0.11.11",
|
"@homebridge/node-pty-prebuilt-multiarch": "~0.11.11",
|
||||||
|
"@inventage/envsubst": "^0.16.0",
|
||||||
"@louislam/sqlite3": "~15.1.6",
|
"@louislam/sqlite3": "~15.1.6",
|
||||||
"bcryptjs": "~2.4.3",
|
"bcryptjs": "~2.4.3",
|
||||||
"check-password-strength": "~2.0.7",
|
"check-password-strength": "~2.0.7",
|
||||||
@ -28,6 +34,7 @@
|
|||||||
"composerize": "~1.4.1",
|
"composerize": "~1.4.1",
|
||||||
"croner": "~7.0.5",
|
"croner": "~7.0.5",
|
||||||
"dayjs": "~1.11.10",
|
"dayjs": "~1.11.10",
|
||||||
|
"dotenv": "~16.3.1",
|
||||||
"express": "~4.18.2",
|
"express": "~4.18.2",
|
||||||
"express-static-gzip": "~2.1.7",
|
"express-static-gzip": "~2.1.7",
|
||||||
"http-graceful-shutdown": "~3.1.13",
|
"http-graceful-shutdown": "~3.1.13",
|
||||||
@ -36,16 +43,18 @@
|
|||||||
"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.6.3",
|
||||||
|
"promisify-child-process": "~4.1.2",
|
||||||
"redbean-node": "~0.3.3",
|
"redbean-node": "~0.3.3",
|
||||||
"socket.io": "~4.7.2",
|
"socket.io": "~4.7.2",
|
||||||
"socket.io-client": "~4.7.2",
|
"socket.io-client": "~4.7.2",
|
||||||
"timezones-list": "~3.0.2",
|
"timezones-list": "~3.0.2",
|
||||||
"ts-command-line-args": "~2.5.1",
|
"ts-command-line-args": "~2.5.1",
|
||||||
"tsx": "~3.14.0",
|
"tsx": "~4.6.2",
|
||||||
"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",
|
||||||
"@fontsource/jetbrains-mono": "^5.0.17",
|
"@fontsource/jetbrains-mono": "^5.0.17",
|
||||||
"@fortawesome/fontawesome-svg-core": "6.4.2",
|
"@fortawesome/fontawesome-svg-core": "6.4.2",
|
||||||
"@fortawesome/free-regular-svg-icons": "6.4.2",
|
"@fortawesome/free-regular-svg-icons": "6.4.2",
|
||||||
|
660
pnpm-lock.yaml
generated
660
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user