Compare commits

..

99 Commits

Author SHA1 Message Date
1ce74d1801 Fix: .env code change is not reactive 2023-12-10 02:21:36 +08:00
007eac7b58 Envsubst YAML in order to display the correct values in the UI instead of tags (#268)
* WIP

* Add envsubst

* WIP
2023-12-10 00:59:28 +08:00
b945ddea55 Fix check update (#269)
* Fix check-version.ts
2023-12-10 00:59:05 +08:00
bd58de535e Update README.md (#264)
Correct spelling errors.
2023-12-08 23:49:29 +08:00
8c6bcef987 Stopped "keyword" match from taking \n (#255) 2023-12-07 20:14:16 +08:00
bec5460395 Update README.md 2023-12-06 03:14:14 +08:00
4e899dcf21 Fix: Arabic to RTL 2023-12-05 16:57:10 +08:00
8296c7b18f Update to 1.3.2 2023-12-05 03:01:46 +08:00
607c908f2d Fix #236 2023-12-05 03:01:06 +08:00
bd5dd3c3ad Update to 1.3.1 2023-12-05 02:47:05 +08:00
6eca6dc59f Fix #234 (#235) 2023-12-05 02:41:25 +08:00
54fb2c1ef4 Update to 1.3.0 2023-12-04 23:16:32 +08:00
562abb485d Merge pull request #229 from UptimeKumaBot/weblate-dockge-dockge
Translations update from Kuma Weblate
2023-12-04 19:53:08 +08:00
86bed768ea Translated using Weblate (Korean)
Currently translated at 100.0% (100 of 100 strings)

Translation: Dockge/dockge
Translate-URL: https://weblate.kuma.pet/projects/dockge/dockge/ko/
2023-12-04 10:40:44 +00:00
b79db2375f Translated using Weblate (Italian)
Currently translated at 99.0% (99 of 100 strings)

Translation: Dockge/dockge
Translate-URL: https://weblate.kuma.pet/projects/dockge/dockge/it/
2023-12-04 10:40:44 +00:00
b586cca711 Translated using Weblate (Japanese)
Currently translated at 96.0% (96 of 100 strings)

Translation: Dockge/dockge
Translate-URL: https://weblate.kuma.pet/projects/dockge/dockge/ja/
2023-12-04 10:40:44 +00:00
793a9de50d Env follow up (#231)
* Create the env file only if not empty

* Update

* Check some fs operation to async
2023-12-04 18:40:37 +08:00
0df3fee3f4 Minor 2023-12-03 21:57:27 +08:00
05b79ba50e Fix freeze issue (#227)
* Fix freeze issue

* Fix
2023-12-03 21:30:50 +08:00
a3c4082800 Sort non managed stack to the end (#228) 2023-12-03 21:24:06 +08:00
027d9e9b59 Add new languages in the list 2023-12-03 20:41:59 +08:00
80876a463d Merge pull request #190 from UptimeKumaBot/weblate-dockge-dockge
Translations update from Kuma Weblate
2023-12-03 20:41:22 +08:00
beefe41264 Translated using Weblate (Danish)
Currently translated at 100.0% (100 of 100 strings)

Translation: Dockge/dockge
Translate-URL: https://weblate.kuma.pet/projects/dockge/dockge/da/
2023-12-03 11:28:43 +00:00
e4f9b9c9fe Added translation using Weblate (Danish) 2023-12-03 10:40:02 +00:00
a85813ab95 Translated using Weblate (Dutch)
Currently translated at 100.0% (100 of 100 strings)

Translation: Dockge/dockge
Translate-URL: https://weblate.kuma.pet/projects/dockge/dockge/nl/
2023-12-03 10:13:01 +00:00
47debeddc9 Translated using Weblate (Russian)
Currently translated at 91.0% (91 of 100 strings)

Translation: Dockge/dockge
Translate-URL: https://weblate.kuma.pet/projects/dockge/dockge/ru/
2023-12-03 10:13:01 +00:00
9ce68f67fa Translated using Weblate (German)
Currently translated at 99.0% (99 of 100 strings)

Translation: Dockge/dockge
Translate-URL: https://weblate.kuma.pet/projects/dockge/dockge/de/
2023-12-03 10:13:01 +00:00
c3b9db8549 Translated using Weblate (Russian)
Currently translated at 92.0% (92 of 100 strings)

Translation: Dockge/dockge
Translate-URL: https://weblate.kuma.pet/projects/dockge/dockge/ru/
2023-12-03 10:13:01 +00:00
39279cc7df Translated using Weblate (French)
Currently translated at 100.0% (100 of 100 strings)

Translation: Dockge/dockge
Translate-URL: https://weblate.kuma.pet/projects/dockge/dockge/fr/
2023-12-03 10:13:01 +00:00
f4eeb38d18 Translated using Weblate (Urdu)
Currently translated at 100.0% (100 of 100 strings)

Translation: Dockge/dockge
Translate-URL: https://weblate.kuma.pet/projects/dockge/dockge/ur/
2023-12-03 10:13:01 +00:00
49187577ca Translated using Weblate (Romanian)
Currently translated at 100.0% (100 of 100 strings)

Translation: Dockge/dockge
Translate-URL: https://weblate.kuma.pet/projects/dockge/dockge/ro/
2023-12-03 10:13:01 +00:00
7ffb36ec54 Translated using Weblate (Romanian)
Currently translated at 19.0% (19 of 100 strings)

Translation: Dockge/dockge
Translate-URL: https://weblate.kuma.pet/projects/dockge/dockge/ro/
2023-12-03 10:13:01 +00:00
cd596b2d37 Translated using Weblate (Japanese)
Currently translated at 96.0% (96 of 100 strings)

Translation: Dockge/dockge
Translate-URL: https://weblate.kuma.pet/projects/dockge/dockge/ja/
2023-12-03 10:13:01 +00:00
1711e4f47c Translated using Weblate (Dutch)
Currently translated at 100.0% (100 of 100 strings)

Translation: Dockge/dockge
Translate-URL: https://weblate.kuma.pet/projects/dockge/dockge/nl/
2023-12-03 10:13:01 +00:00
2c0fb7f7e0 Translated using Weblate (Chinese (Traditional))
Currently translated at 100.0% (100 of 100 strings)

Translation: Dockge/dockge
Translate-URL: https://weblate.kuma.pet/projects/dockge/dockge/zh_Hant/
2023-12-03 10:13:01 +00:00
ef7f26b142 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (100 of 100 strings)

Translation: Dockge/dockge
Translate-URL: https://weblate.kuma.pet/projects/dockge/dockge/zh_Hans/
2023-12-03 10:13:01 +00:00
80b907577f Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (100 of 100 strings)

Translation: Dockge/dockge
Translate-URL: https://weblate.kuma.pet/projects/dockge/dockge/zh_Hans/
2023-12-03 10:13:01 +00:00
e54211de1b Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (100 of 100 strings)

Translation: Dockge/dockge
Translate-URL: https://weblate.kuma.pet/projects/dockge/dockge/zh_Hans/
2023-12-03 10:13:01 +00:00
fc42639cb5 Translated using Weblate (Urdu)
Currently translated at 100.0% (100 of 100 strings)

Translation: Dockge/dockge
Translate-URL: https://weblate.kuma.pet/projects/dockge/dockge/ur/
2023-12-03 10:13:01 +00:00
fde2ef4869 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (100 of 100 strings)

Translation: Dockge/dockge
Translate-URL: https://weblate.kuma.pet/projects/dockge/dockge/uk/
2023-12-03 10:13:01 +00:00
d554adb0c7 Translated using Weblate (Turkish)
Currently translated at 100.0% (100 of 100 strings)

Translation: Dockge/dockge
Translate-URL: https://weblate.kuma.pet/projects/dockge/dockge/tr/
2023-12-03 10:13:01 +00:00
c1e788d22a Translated using Weblate (French)
Currently translated at 100.0% (100 of 100 strings)

Translation: Dockge/dockge
Translate-URL: https://weblate.kuma.pet/projects/dockge/dockge/fr/
2023-12-03 10:13:01 +00:00
972179b4a8 Translated using Weblate (German)
Currently translated at 100.0% (100 of 100 strings)

Translation: Dockge/dockge
Translate-URL: https://weblate.kuma.pet/projects/dockge/dockge/de/
2023-12-03 10:13:01 +00:00
d5e1ce51ed Translated using Weblate (Arabic)
Currently translated at 100.0% (100 of 100 strings)

Translation: Dockge/dockge
Translate-URL: https://weblate.kuma.pet/projects/dockge/dockge/ar/
2023-12-03 10:13:01 +00:00
9155cf14a5 Translated using Weblate (English)
Currently translated at 100.0% (100 of 100 strings)

Translation: Dockge/dockge
Translate-URL: https://weblate.kuma.pet/projects/dockge/dockge/en/
2023-12-03 10:13:01 +00:00
17eb6583df Added translation using Weblate (Romanian) 2023-12-03 10:13:01 +00:00
b717fc6655 Added translation using Weblate (Japanese) 2023-12-03 10:13:01 +00:00
da50860211 Added translation using Weblate (Dutch) 2023-12-03 10:13:01 +00:00
340ea0abe9 Translated using Weblate (Arabic)
Currently translated at 100.0% (100 of 100 strings)

Translation: Dockge/dockge
Translate-URL: https://weblate.kuma.pet/projects/dockge/dockge/ar/
2023-12-03 10:13:01 +00:00
d53cb7ca71 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (100 of 100 strings)

Translation: Dockge/dockge
Translate-URL: https://weblate.kuma.pet/projects/dockge/dockge/uk/
2023-12-03 10:13:01 +00:00
e29eed4602 Translated using Weblate (French)
Currently translated at 100.0% (100 of 100 strings)

Translation: Dockge/dockge
Translate-URL: https://weblate.kuma.pet/projects/dockge/dockge/fr/
2023-12-03 10:13:01 +00:00
8e9f2209c2 Translated using Weblate (Arabic)
Currently translated at 100.0% (100 of 100 strings)

Translation: Dockge/dockge
Translate-URL: https://weblate.kuma.pet/projects/dockge/dockge/ar/
2023-12-03 10:13:01 +00:00
a8d95d06b9 UI for editing .env file (#218)
* Add .env file editing with syntax highlighting

* Add example env file to new compose

* Changed .env editing section title

* Better stack constuctor parameter order

* Minor

---------

Co-authored-by: Louis Lam <louislam@users.noreply.github.com>
2023-12-03 18:12:54 +08:00
e2c81bd3e0 Add pnpm run reset-password (#212) 2023-11-30 16:11:00 +08:00
bf2d4c95f9 WIP (#211) 2023-11-30 16:10:37 +08:00
b4aa1b83a2 Add ARM github runner and change Node.js version (#210)
* WIP

* WIP
2023-11-29 14:59:39 +08:00
157a74aafc Update README.md 2023-11-29 09:28:47 +08:00
b7a2bab808 Update README.md 2023-11-29 09:27:40 +08:00
71446b9eb9 add missing 'stackName' param to return value (#206)
Maybe a mishap, but this was causing issues with opening the correct terminals for each container

Co-authored-by: Thales <thcd@cock.li>
2023-11-29 07:04:30 +08:00
06dbc3fa28 Update compose.yaml 2023-11-28 00:53:18 +08:00
a0fca4df4d Update Base.Dockerfile 2023-11-27 21:01:11 +08:00
694923cd42 Update README.md 2023-11-27 18:58:15 +08:00
bfaa8fd795 Update README.md 2023-11-26 23:25:52 +08:00
d5721dd8ca chore(ci): Add workflow to prevent changing lang files (#191)
* Create prevent-file-change.yml

* fix
2023-11-26 22:17:45 +08:00
5c35b09e93 Update CONTRIBUTING.md 2023-11-26 18:53:17 +08:00
9a5d403219 Update to 1.2.0 2023-11-26 18:02:36 +08:00
9ca65ec94d Merge pull request #184 from UptimeKumaBot/weblate-dockge-dockge
Translations update from Uptime Kuma Weblate
2023-11-26 17:54:57 +08:00
5b02b63c95 Translated using Weblate (Urdu)
Currently translated at 100.0% (100 of 100 strings)

Translation: Dockge/dockge
Translate-URL: https://weblate.kuma.pet/projects/dockge/dockge/ur/
2023-11-26 09:51:33 +00:00
88d33aace8 Translated using Weblate (Chinese (Simplified))
Currently translated at 97.0% (97 of 100 strings)

Translation: Dockge/dockge
Translate-URL: https://weblate.kuma.pet/projects/dockge/dockge/zh_Hans/
2023-11-26 09:51:33 +00:00
b5f6919bab Fix 100% cpu usage and terminal issue by downgrading Node.js to 18.17.1 (#186)
* Downgrade from Node.js 20 to Node.js 18.17.1

* Enable back socket.io polling

* Avoid multiple commands at the same for a stack
2023-11-26 17:51:26 +08:00
0546f6a24e chore(docs): Update Translation Guide (#185)
* update

* Update README.md
2023-11-26 13:30:57 +08:00
7385d216a3 Fix 2023-11-25 22:26:12 +08:00
631bc60cb2 Add support for extra info: URLs (#182)
* Always show Down button

* Add URL and improve ArrayInput handling

* Minor
2023-11-25 22:14:21 +08:00
d23e2d8aa1 Enforce Websocket (#181)
* Always show Down button

* Force WebSocket

* Force WebSocket
2023-11-25 21:21:23 +08:00
457f038108 Fix css 2023-11-25 13:50:15 +08:00
f862bbc7cd Added Traditional Chinese (#96)
* Fix a typo

* Added Traditional Chinese

* Removed zh-HK

* Update zh-TW.json
2023-11-25 13:08:14 +08:00
3d56846cd6 Add Ukrainian translation (#175)
* Create uk-UA.json

Ukrainian translation

* Update i18n.ts

Add Ukrainian translation

* Update i18n.ts

* Update i18n.ts
2023-11-25 12:06:24 +08:00
cff929c69d Remove compose version (#174) 2023-11-25 03:08:48 +08:00
766e751522 Close terminal if there is no clients (#60)
* Close terminal if there is no clients connecting

* Only enable

* Join the terminal only if it is managed by Dockge

* Done
2023-11-25 02:04:16 +08:00
45ab36db98 Fix: dockge cannot be self-managed (#171) 2023-11-25 01:02:04 +08:00
47435d41cd Added Swedish (#167) 2023-11-24 13:27:55 +08:00
ee8f39699a Add Italian translation (#166)
* Create it-IT.json

* Add Italian translation to i18n.ts
2023-11-24 02:33:22 +08:00
204c776b0d feat : Added Thai language (#164) 2023-11-23 21:27:38 +08:00
724b5d6d7e Update README.md 2023-11-23 11:03:20 +08:00
766ecb070d Arabic translation (#162)
* Create ar.json

Added Arabic translation

* Update i18n.ts

Add Arabic translation

* Update i18n.ts

---------

Co-authored-by: Louis Lam <louislam@users.noreply.github.com>
2023-11-23 10:19:58 +08:00
d1d3a54377 Add step to build the app aswell (#158) 2023-11-23 08:49:34 +08:00
d17a63fcab Created Brazilian-Portuguese lang (#159) 2023-11-23 08:46:20 +08:00
5454b44a1c Fix cs-CZ language (#160)
* fix cs-CZ

* fix admin acc translation
2023-11-23 08:45:14 +08:00
9e8bccbf2f Update ask-for-help.yml 2023-11-22 23:13:34 +08:00
866fa380dd Merge pull request #157 from louislam/rebase-discussion-form
[Rebase to master] Add Discussion Forms for Feature Request and Help (#156)
2023-11-22 23:10:56 +08:00
49b28d0e36 Add Discussion Forms for Feature Request and Help (#156)
* created DISCUSSIONS folder and added feature request form

* Added ask-for-help discussion form

* Updated file formats to GH approved

* Updated naming for feature request. Fixed  to

* Updated ISSUE_TEMPLATE to point to the discussion forms

* Removed description from checkbox

* Removed description from checkboxes

---------

Co-authored-by: Justin Masse <justin@Justins-MacBook-Pro.local>
2023-11-22 23:08:33 +08:00
1e7dd0504b Čeština language file [cs-CZ] (#154) 2023-11-22 18:53:42 +08:00
05191b14de Readme reference compose file contents (#117)
* Reference compose.yaml directly

* fixup! Reference compose.yaml directly
2023-11-22 04:36:09 +08:00
81cacbdddd Fix the port issue 2023-11-22 02:22:38 +08:00
0279d431e0 Add @actions/github 2023-11-22 01:12:54 +08:00
0e768abfb4 Try to fix "close-incorrect-issue.yml" 2023-11-22 01:07:51 +08:00
62e952f1e6 Try to fix "close-incorrect-issue.yml" 2023-11-22 01:04:28 +08:00
f044c2e328 Try to fix issue template 2023-11-22 00:59:11 +08:00
dc8787d204 Add workflows, templates and docs (#134)
* Copy workflows, templates and docs from Uptime Kuma

* Update bug report template

* Add exclude.txt

* Fix tsconfig.json

* Update CONTRIBUTING.md

* Update

* Update
2023-11-22 00:55:34 +08:00
58 changed files with 2829 additions and 398 deletions

View File

@ -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

View File

@ -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: "..."

View File

@ -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

View File

@ -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

View File

@ -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..

View File

@ -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 }}

View 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

View File

@ -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

View File

@ -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`.

View File

@ -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;

View File

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

View File

@ -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

View File

@ -9,10 +9,10 @@ import composerize from "composerize";
export class DockerSocketHandler extends SocketHandler { export class DockerSocketHandler extends SocketHandler {
create(socket : DockgeSocket, server : DockgeServer) { create(socket : DockgeSocket, server : DockgeServer) {
socket.on("deployStack", async (name : unknown, composeYAML : unknown, 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;
} }

View File

@ -101,7 +101,7 @@ export class TerminalSocketHandler extends SocketHandler {
log.debug("interactiveTerminal", "Service name: " + serviceName); log.debug("interactiveTerminal", "Service name: " + serviceName);
// Get stack // Get stack
const stack = 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

View File

@ -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;
} }
} }

View File

@ -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;
}
} }
/** /**

View File

@ -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);
}
}

View File

@ -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);
}

View File

@ -13,10 +13,10 @@ services:
# If you want to use private registries, you need to share the auth file with Dockge: # If you want to use private registries, you need to share the auth file with Dockge:
# - /root/.docker/:/root/.docker # - /root/.docker/:/root/.docker
# 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

View File

@ -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

View 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

View File

@ -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
############################################ ############################################

View File

@ -1,4 +1,4 @@
const github = require("@actions/github"); import github from "@actions/github";
(async () => { (async () => {
try { try {

74
extra/healthcheck.go Normal file
View 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
View 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();
}

View File

@ -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']

View File

@ -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) {

View File

@ -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) {

View File

@ -9,7 +9,7 @@
<div v-if="!isEditMode"> <div v-if="!isEditMode">
<span class="badge me-1" :class="bgStyle">{{ status }}</span> <span class="badge me-1" :class="bgStyle">{{ status }}</span>
<a v-for="port in 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;

View File

@ -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;

View File

@ -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>

View File

@ -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

View File

@ -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
View 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...": "جاري الاتصال بخادم المقبس…"
}

View 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
View 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"
}

View File

@ -6,14 +6,14 @@
"Repeat Password": "Passwort wiederholen", "Repeat Password": "Passwort wiederholen",
"Create": "Erstellen", "Create": "Erstellen",
"signedInDisp": "Angemeldet als {0}", "signedInDisp": "Angemeldet als {0}",
"signedInDispDisabled": "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…"
}

View File

@ -12,7 +12,7 @@
"registry": "Registry", "registry": "Registry",
"compose": "Compose", "compose": "Compose",
"addFirstStackMsg": "Compose your first stack!", "addFirstStackMsg": "Compose your first stack!",
"stackName" : "Stack Name", "stackName": "Stack Name",
"deployStack": "Deploy", "deployStack": "Deploy",
"deleteStack": "Delete", "deleteStack": "Delete",
"stopStack": "Stop", "stopStack": "Stop",
@ -23,7 +23,7 @@
"editStack": "Edit", "editStack": "Edit",
"discardStack": "Discard", "discardStack": "Discard",
"saveStackDraft": "Save", "saveStackDraft": "Save",
"notAvailableShort" : "N/A", "notAvailableShort": "N/A",
"deleteStackMsg": "Are you sure you want to delete this stack?", "deleteStackMsg": "Are you sure you want to delete this stack?",
"stackNotManagedByDockgeMsg": "This stack is not managed by Dockge.", "stackNotManagedByDockgeMsg": "This stack is not managed by Dockge.",
"primaryHostname": "Primary Hostname", "primaryHostname": "Primary Hostname",
@ -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"
} }

View File

@ -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…"
} }

View 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
View 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...": "ソケットサーバーに接続中…"
}

View File

@ -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
View 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"
}

View 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
View 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."
}

View File

@ -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": "завершенный",

View 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
View 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": "ไม่มีเครือข่ายภายนอก"
}

View File

@ -12,7 +12,7 @@
"registry": "Kayıt Defteri", "registry": "Kayıt Defteri",
"compose": "Compose", "compose": "Compose",
"addFirstStackMsg": "İlk yığınınızı oluşturun!", "addFirstStackMsg": "İlk yığınınızı oluşturun!",
"stackName" : "Yığın Adı", "stackName": "Yığın Adı",
"deployStack": "Dağıtmak", "deployStack": "Dağıtmak",
"deleteStack": "Sil", "deleteStack": "Sil",
"stopStack": "Dudur", "stopStack": "Dudur",
@ -22,7 +22,7 @@
"editStack": "Düzenle", "editStack": "Düzenle",
"discardStack": ıkar", "discardStack": ıkar",
"saveStackDraft": "Kaydet", "saveStackDraft": "Kaydet",
"notAvailableShort" : "N/A", "notAvailableShort": "N/A",
"deleteStackMsg": "Bu yığını silmek istediğinizden emin misiniz?", "deleteStackMsg": "Bu yığını silmek istediğinizden emin misiniz?",
"stackNotManagedByDockgeMsg": "Bu yığın Dockge tarafından yönetilmemektedir.", "stackNotManagedByDockgeMsg": "Bu yığın Dockge tarafından yönetilmemektedir.",
"primaryHostname": "Birincil Ana Bilgisayar Adı", "primaryHostname": "Birincil Ana Bilgisayar Adı",
@ -90,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 | URLler",
"Cannot connect to the socket server.": "Soket sunucusuna bağlanılamıyor.",
"downStack": "Durdur & Kapat"
} }

View 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": "Додатково"
}

View File

@ -12,23 +12,23 @@
"registry": "رجسٹری", "registry": "رجسٹری",
"compose": "تحریر", "compose": "تحریر",
"addFirstStackMsg": "اپنا پہلا اسٹیک کمپوز کریں!", "addFirstStackMsg": "اپنا پہلا اسٹیک کمپوز کریں!",
"stackName" : "اسٹیک کا نام", "stackName": "اسٹیک کا نام",
"deployStack": "تعینات", "deployStack": "تعینات",
"deleteStack": "حذف کریں", "deleteStack": "حذف کریں",
"stopStack": "روکو", "stopStack": "روکو",
"restartStack": "دوبارہ شروع کریں", "restartStack": "دوبارہ شروع کریں",
"updateStack": "اپ ڈیٹ", "updateStack": "اپ ڈیٹ",
"startStack": "شروع کریں۔", "startStack": "شروع کریں",
"editStack": "ترمیم", "editStack": "ترمیم",
"discardStack": "رد کر دیں۔", "discardStack": "رد کر دیں",
"saveStackDraft": "محفوظ کریں۔", "saveStackDraft": "محفوظ کریں۔",
"notAvailableShort" : "N / A", "notAvailableShort": "N / A",
"deleteStackMsg": "کیا آپ واقعی اس اسٹیک کو حذف کرنا چاہتے ہیں؟", "deleteStackMsg": "کیا آپ واقعی اس اسٹیک کو حذف کرنا چاہتے ہیں؟",
"stackNotManagedByDockgeMsg": "یہ اسٹیک Dockge کے زیر انتظام نہیں ہے۔", "stackNotManagedByDockgeMsg": "یہ اسٹیک Dockge کے زیر انتظام نہیں ہے۔",
"primaryHostname": "بنیادی میزبان نام", "primaryHostname": "بنیادی میزبان نام",
"general": "جنرل", "general": "جنرل",
"container": "کنٹینر | کنٹینرز", "container": "کنٹینر | کنٹینرز",
"scanFolder": "اسٹیک فولڈر کو اسکین کریں۔", "scanFolder": "اسٹیک فولڈر کو اسکین کریں",
"dockerImage": "تصویر", "dockerImage": "تصویر",
"restartPolicyUnlessStopped": "جب تک روکا نہیں جاتا", "restartPolicyUnlessStopped": "جب تک روکا نہیں جاتا",
"restartPolicyAlways": "ہمیشہ", "restartPolicyAlways": "ہمیشہ",
@ -51,7 +51,7 @@
"autoGet": "آٹو حاصل کریں", "autoGet": "آٹو حاصل کریں",
"add": "شامل کریں", "add": "شامل کریں",
"Edit": "ترمیم", "Edit": "ترمیم",
"applyToYAML": "YAML پر درخواست دیں۔", "applyToYAML": "YAML پر درخواست دیں",
"createExternalNetwork": "بنانا", "createExternalNetwork": "بنانا",
"addInternalNetwork": "شامل کریں", "addInternalNetwork": "شامل کریں",
"Save": "محفوظ کریں", "Save": "محفوظ کریں",
@ -64,12 +64,12 @@
"Update Password": "پاس ورڈ اپ ڈیٹ کریں", "Update Password": "پاس ورڈ اپ ڈیٹ کریں",
"Advanced": "ترقی یافتہ", "Advanced": "ترقی یافتہ",
"Please use this option carefully!": "براہ کرم اس اختیار کو احتیاط سے استعمال کریں!", "Please use this option carefully!": "براہ کرم اس اختیار کو احتیاط سے استعمال کریں!",
"Enable Auth": "تصدیق کو فعال کریں۔", "Enable Auth": "تصدیق کو فعال کریں",
"Disable Auth": "توثیق کو غیر فعال کریں۔", "Disable Auth": "توثیق کو غیر فعال کریں",
"I understand, please disable": "میں سمجھتا ہوں، براہ کرم غیر فعال کریں۔", "I understand, please disable": "میں سمجھتا ہوں، براہ کرم غیر فعال کریں",
"Leave": "چھوڑ دو", "Leave": "چھوڑ دو",
"Frontend Version": "فرنٹ اینڈ ورژن", "Frontend Version": "فرنٹ اینڈ ورژن",
"Check Update On GitHub": "گیتوب پر اپ ڈیٹ چیک کریں۔", "Check Update On GitHub": "گیتوب پر اپ ڈیٹ چیک کریں",
"Show update if available": "اگر دستیاب ہو تو اپ ڈیٹ دکھائیں", "Show update if available": "اگر دستیاب ہو تو اپ ڈیٹ دکھائیں",
"Also check beta release": "بیٹا ریلیز بھی چیک کریں", "Also check beta release": "بیٹا ریلیز بھی چیک کریں",
"Remember me": "مجھے پہچانتے ہو", "Remember me": "مجھے پہچانتے ہو",
@ -90,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 کے لیے ترتیب دینے کا طریقہ چیک کریں"
} }

View File

@ -1,18 +1,18 @@
{ {
"languageName": "简体中文", "languageName": "简体中文",
"Create your admin account": "创建你的管理员账号", "Create your admin account": "创建你的管理员账号",
"authIncorrectCreds": "用户名或密码错误", "authIncorrectCreds": "用户名或密码错误",
"PasswordsDoNotMatch": "两次输入的密码不一致。", "PasswordsDoNotMatch": "两次输入的密码不一致。",
"Repeat Password": "重复以确认密码", "Repeat Password": "重复以确认密码",
"Create": "创建", "Create": "创建",
"signedInDisp": "当前用户: {0}", "signedInDisp": "当前用户: {0}",
"signedInDispDisabled": "已禁用身份验证", "signedInDispDisabled": "已禁用身份验证",
"home": "主页", "home": "主页",
"console": "终端", "console": "终端",
"registry": "镜像仓库", "registry": "镜像仓库",
"compose": "Compose", "compose": "Compose",
"addFirstStackMsg": "组合你的第一个堆栈!", "addFirstStackMsg": "组合你的第一个堆栈!",
"stackName" : "堆栈名称", "stackName": "堆栈名称",
"deployStack": "部署", "deployStack": "部署",
"deleteStack": "删除", "deleteStack": "删除",
"stopStack": "停止", "stopStack": "停止",
@ -22,9 +22,9 @@
"editStack": "编辑", "editStack": "编辑",
"discardStack": "放弃", "discardStack": "放弃",
"saveStackDraft": "保存", "saveStackDraft": "保存",
"notAvailableShort" : "不可用", "notAvailableShort": "不可用",
"deleteStackMsg": "你确定要删除这个堆栈吗?", "deleteStackMsg": "你确定要删除这个堆栈吗?",
"stackNotManagedByDockgeMsg": "这个堆栈不由Dockge管理", "stackNotManagedByDockgeMsg": "这个堆栈不由Dockge管理",
"primaryHostname": "主机名", "primaryHostname": "主机名",
"general": "常规", "general": "常规",
"container": "容器 | 容器组", "container": "容器 | 容器组",
@ -90,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": "停止并删除"
} }

View 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": "額外"
}

View File

@ -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>

View File

@ -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

View File

@ -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) {

View File

@ -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

File diff suppressed because it is too large Load Diff