Compare commits

...

143 commits

Author SHA1 Message Date
Louis Lam bec5460395
Update README.md 2023-12-06 03:14:14 +08:00
Louis Lam 4e899dcf21 Fix: Arabic to RTL 2023-12-05 16:57:10 +08:00
Louis Lam 8296c7b18f Update to 1.3.2 2023-12-05 03:01:46 +08:00
Louis Lam 607c908f2d Fix #236 2023-12-05 03:01:06 +08:00
Louis Lam bd5dd3c3ad Update to 1.3.1 2023-12-05 02:47:05 +08:00
Louis Lam 6eca6dc59f
Fix #234 (#235) 2023-12-05 02:41:25 +08:00
Louis Lam 54fb2c1ef4 Update to 1.3.0 2023-12-04 23:16:32 +08:00
Louis Lam 562abb485d
Merge pull request #229 from UptimeKumaBot/weblate-dockge-dockge
Translations update from Kuma Weblate
2023-12-04 19:53:08 +08:00
Alanimdeo 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
DevMirza 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
Louis Lam 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
Louis Lam 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
Louis Lam 0df3fee3f4 Minor 2023-12-03 21:57:27 +08:00
Louis Lam 05b79ba50e
Fix freeze issue (#227)
* Fix freeze issue

* Fix
2023-12-03 21:30:50 +08:00
Louis Lam a3c4082800
Sort non managed stack to the end (#228) 2023-12-03 21:24:06 +08:00
Louis Lam 027d9e9b59 Add new languages in the list 2023-12-03 20:41:59 +08:00
Louis Lam 80876a463d
Merge pull request #190 from UptimeKumaBot/weblate-dockge-dockge
Translations update from Kuma Weblate
2023-12-03 20:41:22 +08:00
Freddie 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
Freddie e4f9b9c9fe Added translation using Weblate (Danish) 2023-12-03 10:40:02 +00:00
syko9000 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
MSalim7 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
Lucas Reiners 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
MSalim7 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
Cyril59310 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
DevMirza 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
Dragos Bunduc 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
Dragos Bunduc 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
nazo6 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
bjornclauw 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
AICHIGUA 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
MaxX 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
401Unauthorized 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
AICHIGUA 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
MaxX 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
MaxX 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
Furkan İ 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
MaxX 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
MaxX 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
MaxX 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
MaxX 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
Dragos Bunduc 17eb6583df Added translation using Weblate (Romanian) 2023-12-03 10:13:01 +00:00
nazo6 b717fc6655 Added translation using Weblate (Japanese) 2023-12-03 10:13:01 +00:00
bjornclauw da50860211 Added translation using Weblate (Dutch) 2023-12-03 10:13:01 +00:00
abosaad11 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
stanol 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
Cyril59310 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
abosaad11 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
syko9000 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
Louis Lam e2c81bd3e0
Add pnpm run reset-password (#212) 2023-11-30 16:11:00 +08:00
Louis Lam bf2d4c95f9
WIP (#211) 2023-11-30 16:10:37 +08:00
Louis Lam b4aa1b83a2
Add ARM github runner and change Node.js version (#210)
* WIP

* WIP
2023-11-29 14:59:39 +08:00
Louis Lam 157a74aafc
Update README.md 2023-11-29 09:28:47 +08:00
Louis Lam b7a2bab808
Update README.md 2023-11-29 09:27:40 +08:00
ThalesC 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
Louis Lam 06dbc3fa28
Update compose.yaml 2023-11-28 00:53:18 +08:00
Louis Lam a0fca4df4d
Update Base.Dockerfile 2023-11-27 21:01:11 +08:00
Louis Lam 694923cd42
Update README.md 2023-11-27 18:58:15 +08:00
Louis Lam bfaa8fd795
Update README.md 2023-11-26 23:25:52 +08:00
DevMirza 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
Louis Lam 5c35b09e93
Update CONTRIBUTING.md 2023-11-26 18:53:17 +08:00
Louis Lam 9a5d403219 Update to 1.2.0 2023-11-26 18:02:36 +08:00
Louis Lam 9ca65ec94d
Merge pull request #184 from UptimeKumaBot/weblate-dockge-dockge
Translations update from Uptime Kuma Weblate
2023-11-26 17:54:57 +08:00
DevMirza 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
401Unauthorized 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
Louis Lam 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
DevMirza 0546f6a24e
chore(docs): Update Translation Guide (#185)
* update

* Update README.md
2023-11-26 13:30:57 +08:00
Louis Lam 7385d216a3 Fix 2023-11-25 22:26:12 +08:00
Louis Lam 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
Louis Lam d23e2d8aa1
Enforce Websocket (#181)
* Always show Down button

* Force WebSocket

* Force WebSocket
2023-11-25 21:21:23 +08:00
Louis Lam 457f038108 Fix css 2023-11-25 13:50:15 +08:00
AICHIGUA 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
sxikari 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
Louis Lam cff929c69d
Remove compose version (#174) 2023-11-25 03:08:48 +08:00
Louis Lam 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
Louis Lam 45ab36db98
Fix: dockge cannot be self-managed (#171) 2023-11-25 01:02:04 +08:00
annorberg98 47435d41cd
Added Swedish (#167) 2023-11-24 13:27:55 +08:00
Davide Trainini ee8f39699a
Add Italian translation (#166)
* Create it-IT.json

* Add Italian translation to i18n.ts
2023-11-24 02:33:22 +08:00
watchakorn-18k 204c776b0d
feat : Added Thai language (#164) 2023-11-23 21:27:38 +08:00
Louis Lam 724b5d6d7e
Update README.md 2023-11-23 11:03:20 +08:00
abosaad11 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
DevMirza d1d3a54377
Add step to build the app aswell (#158) 2023-11-23 08:49:34 +08:00
Renan Altendorf d17a63fcab
Created Brazilian-Portuguese lang (#159) 2023-11-23 08:46:20 +08:00
ArtyomCZ 5454b44a1c
Fix cs-CZ language (#160)
* fix cs-CZ

* fix admin acc translation
2023-11-23 08:45:14 +08:00
Louis Lam 9e8bccbf2f
Update ask-for-help.yml 2023-11-22 23:13:34 +08:00
Louis Lam 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
Lord0fBytes 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
Furkan İ 1e7dd0504b
Čeština language file [cs-CZ] (#154) 2023-11-22 18:53:42 +08:00
Ryan Balfanz 05191b14de
Readme reference compose file contents (#117)
* Reference compose.yaml directly

* fixup! Reference compose.yaml directly
2023-11-22 04:36:09 +08:00
Louis Lam 81cacbdddd Fix the port issue 2023-11-22 02:22:38 +08:00
Louis Lam 0279d431e0 Add @actions/github 2023-11-22 01:12:54 +08:00
Louis Lam 0e768abfb4 Try to fix "close-incorrect-issue.yml" 2023-11-22 01:07:51 +08:00
Louis Lam 62e952f1e6 Try to fix "close-incorrect-issue.yml" 2023-11-22 01:04:28 +08:00
Louis Lam f044c2e328 Try to fix issue template 2023-11-22 00:59:11 +08:00
Louis Lam 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
Aonodensetsu d92ea2dac9
Added Polski (#132)
* Create pl-PL.json

from https://github.com/louislam/dockge/pull/90 with merged review

* Update pl-PL.json

missed a few and noticed a few things that could be changed

* Add to list

* No overflow text

---------

Co-authored-by: Louis Lam <louislam@users.noreply.github.com>
2023-11-22 00:04:51 +08:00
Louis Lam 67819ecd73 Added a script for preparing changelog 2023-11-21 20:44:27 +08:00
Louis Lam 73d23373cf Update to 1.1.1 2023-11-21 20:18:38 +08:00
Louis Lam 891a217682
Show the correct version 2023-11-21 20:11:02 +08:00
Louis Lam 31726315c3
Improvements (#124)
* Improvements

* Add inspect to dev:backend

* Skip debug log at the beginning
2023-11-21 18:51:38 +08:00
Louis Lam e95ef66ca1
Add "docker compose down" (#122) 2023-11-21 18:17:11 +08:00
Rumplin b7c6bbba67
Slovenian language file (#108)
* Slovenian language file

This is a translation from the en.json file to the Slovenian language

* Update for Slovenian language in i18n.ts

Slovenian languge added to the language list

* Update i18n.ts

Removed trailing spaces
2023-11-21 18:12:54 +08:00
Furkan İ dc8c3a7568
Update Turkish language (#121) 2023-11-21 15:13:12 +08:00
Ryan Balfanz b50b1cc6e1
Trim trailing whitespace (#116) 2023-11-21 13:28:04 +08:00
DevMirza 2e26178d2d
feat: Add Translation Guide (#111)
* add README for translations

* add ref in README aswell
2023-11-21 12:15:09 +08:00
Cello Spring 6ef861c989
german language added (#101)
Co-authored-by: cellerich <cellerich@cello.ch>
2023-11-21 03:51:32 +08:00
Alex McArrow 853b43a876
Add Russian language translation (#107) 2023-11-21 03:50:58 +08:00
MrEddX 16a4dd63ac
Changed: Readme (#97) 2023-11-21 00:06:31 +08:00
DevMirza 0847a4a0c0
chore: Add Urdu language (#77)
* Update i18n.ts

* Create ur.json

* complete
2023-11-20 23:52:35 +08:00
MrEddX 889f0c133f
Added: Bulgarian (#93)
* Added: Bulgarian

Added Bulgarian Language Support.

* Fix: Bulgarian

* Create bg-BG.json

Bulgarian translation
2023-11-20 18:14:00 +08:00
Alanimdeo 7cff52f614
Added 한국어 (#86) 2023-11-20 13:23:41 +08:00
Diomedes Ignacio Domínguez Ureña 01398aa498
Added Español and Português (#82)
* Created spanish lang.

* Included Portuguese

* Added missing line of code.
2023-11-20 13:23:08 +08:00
AICHIGUA afe0bc561f
Added Simplified Chinese (#70)
* Added Simplified Chinese

* Added Simplified Chinese

* Fix a typo
2023-11-20 00:55:00 +08:00
Louis Lam c8770a9605 Improve handling of stack status, close #11 2023-11-20 00:52:33 +08:00
Louis Lam 0208684b50 Update a stopped stack, will no longer start the stack 2023-11-20 00:15:37 +08:00
Louis Lam a007ec56f7 Update to 1.1.0 2023-11-19 17:44:30 +08:00
Louis Lam 7bb0a1cb08 Minor 2023-11-19 17:36:19 +08:00
Furkan İ 4df799b5b6
Added Turkish Language (#61)
* Added Turkish Language

* Add to list

---------

Co-authored-by: Louis Lam <louislam@users.noreply.github.com>
2023-11-19 17:30:09 +08:00
Cyril59310 03bc2b6a34
Added Fr language and added missing translation keys (#66)
* Added Fr language and added missing translation keys

* forgotten key

* forgotten key

* fix
2023-11-19 17:19:33 +08:00
Louis Lam 53b052c1e5
Check TypeScript for backend (#64)
* Check Typescript

* Fix backend typescript issues

* Update
2023-11-18 15:54:43 +08:00
Louis Lam 13c3dac44d
ESLint, update vite to 5.0.0 and other dependencies (#63)
* Update vite to 5.0.0 and other dependencies

* Eslint

* Update workflow
2023-11-18 13:59:40 +08:00
broetchenrackete36 5ce6b90546
Support compose.y[a]ml and docker-compose.y[a]ml (#55)
* Support compose.y[a]ml and docker-compose.y[a]ml

* using for-loop to iterate over supported compose filenames

* Fix lint

---------

Co-authored-by: Louis Lam <louislam@users.noreply.github.com>
2023-11-18 13:36:57 +08:00
ThalesC a488518f6e
Add health status check (#58)
* set Health value to Status if existent

Check if Health has any value and save it to be displayed.
If Health is empty, continue as normal.

* add healthy and unhealthy status to be displayed

Check if status is either Running or Healthy to set span class to bg-primary,
and check if status is Unhealthy to set span class to bg-danger.

* Add lint to workflow

* Fix lint

---------

Co-authored-by: Thales <thcd@cock.li>
Co-authored-by: Louis Lam <louislam@users.noreply.github.com>
2023-11-18 13:27:39 +08:00
Louis Lam 8c4004f32d Update to 1.0.4 2023-11-17 01:04:14 +08:00
Louis Lam 393bbcae79 Fix #52 2023-11-17 01:02:20 +08:00
DevMirza 9fbf94586b
fix ci (#54) 2023-11-16 22:10:53 +08:00
DevMirza 0a46a7df1a
Add Github Workflow (#38)
* Create ci..yml

* Rename ci..yml to ci.yml
2023-11-16 21:53:40 +08:00
Louis Lam d1732af529
Update README.md 2023-11-14 00:33:11 +08:00
Louis Lam 87a6436f28
Update compose.yaml 2023-11-13 23:32:37 +08:00
Louis Lam ac75283b0f
Update README.md 2023-11-13 23:31:34 +08:00
Louis Lam 8d6160ec5b Update to 1.0.3 2023-11-13 20:48:23 +08:00
Louis Lam ecb16ae007
Update README.md 2023-11-13 20:38:42 +08:00
Louis Lam c296069a8d Update dependencies 2023-11-13 18:10:33 +08:00
Louis Lam d76442434f Fix #19 2023-11-13 18:10:33 +08:00
Louis Lam 54e8484efd
Update README.md 2023-11-13 13:47:30 +08:00
Louis Lam 2cd10ad16d Remove --rmi 2023-11-13 13:38:24 +08:00
Louis Lam 96a4f2fd0c
Update README.md 2023-11-13 13:10:07 +08:00
DevMirza 700a24171b
Add Badges (#13)
* Update README.md

* Update README.md

* Update README.md
2023-11-13 02:26:39 +08:00
Louis Lam 6ce75a2df3
Update README.md for Podman 2023-11-13 02:04:10 +08:00
Louis Lam 317c97650d Update to 1.0.2 2023-11-12 23:24:38 +08:00
Louis Lam 9295583727 Fix #9 2023-11-12 23:09:31 +08:00
Louis Lam 6dc998bedf Fix frontend version do not match 2023-11-12 16:47:05 +08:00
Louis Lam f5552b3344 Update README.md 2023-11-12 16:44:49 +08:00
Louis Lam b90fd35348
Merge pull request #5 from louislam/release-process
Release process
2023-11-12 16:43:30 +08:00
86 changed files with 4866 additions and 515 deletions

View file

@ -92,6 +92,9 @@ module.exports = {
"one-var": [ "error", "never" ],
"max-statements-per-line": [ "error", { "max": 1 }],
"@typescript-eslint/ban-ts-comment": "off",
"@typescript-eslint/no-unused-vars": [ "warn", {
"args": "none"
}],
"prefer-const" : "off",
},
};

View file

@ -0,0 +1,71 @@
labels: [help]
body:
- type: checkboxes
id: no-duplicate-issues
attributes:
label: "⚠️ Please verify that this bug has NOT been raised before."
description: "Search in the issues sections by clicking [HERE](https://github.com/louislam/dockge/discussions/categories/ask-for-help)"
options:
- label: "I checked and didn't find similar issue"
required: true
- type: checkboxes
attributes:
label: "🛡️ Security Policy"
description: Please review the security policy before reporting security related issues/bugs.
options:
- label: I agree to have read this project [Security Policy](https://github.com/louislam/dockge/security/policy)
required: true
- type: textarea
id: steps-to-reproduce
validations:
required: true
attributes:
label: "📝 Describe your problem"
description: "Please walk us through it step by step."
placeholder: "Describe what are you asking for..."
- type: textarea
id: error-msg
validations:
required: false
attributes:
label: "📝 Error Message(s) or Log"
- type: input
id: dockge-version
attributes:
label: "🐻 Dockge Version"
description: "Which version of Dockge are you running? Please do NOT provide the docker tag such as latest or 1"
placeholder: "Ex. 1.10.0"
validations:
required: true
- type: input
id: operating-system
attributes:
label: "💻 Operating System and Arch"
description: "Which OS is your server/device running on? (For Replit, please do not report this bug)"
placeholder: "Ex. Ubuntu 20.04 x86"
validations:
required: true
- type: input
id: browser-vendor
attributes:
label: "🌐 Browser"
description: "Which browser are you running on? (For Replit, please do not report this bug)"
placeholder: "Ex. Google Chrome 95.0.4638.69"
validations:
required: true
- type: input
id: docker-version
attributes:
label: "🐋 Docker Version"
description: "If running with Docker, which version are you running?"
placeholder: "Ex. Docker 20.10.9 / K8S / Podman"
validations:
required: false
- type: input
id: nodejs-version
attributes:
label: "🟩 NodeJS Version"
description: "If running with Node.js? which version are you running?"
placeholder: "Ex. 14.18.0"
validations:
required: false

View file

@ -0,0 +1,54 @@
labels: [feature-request]
body:
- type: checkboxes
id: no-duplicate-issues
attributes:
label: "⚠️ Please verify that this feature request has NOT been suggested before."
description: "Search in the issues sections by clicking [HERE](https://github.com/louislam/dockge/discussions/categories/feature-request)"
options:
- label: "I checked and didn't find similar feature request"
required: true
- type: dropdown
id: feature-area
attributes:
label: "🏷️ Feature Request Type"
description: "What kind of feature request is this?"
multiple: true
options:
- API
- UI Feature
- Other
validations:
required: true
- type: textarea
id: feature-description
validations:
required: true
attributes:
label: "🔖 Feature description"
description: "A clear and concise description of what the feature request is."
placeholder: "You should add ..."
- type: textarea
id: solution
validations:
required: true
attributes:
label: "✔️ Solution"
description: "A clear and concise description of what you want to happen."
placeholder: "In my use-case, ..."
- type: textarea
id: alternatives
validations:
required: false
attributes:
label: "❓ Alternatives"
description: "A clear and concise description of any alternative solutions or features you've considered."
placeholder: "I have considered ..."
- type: textarea
id: additional-context
validations:
required: false
attributes:
label: "📝 Additional Context"
description: "Add any other context or screenshots about the feature request here."
placeholder: "..."

View file

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

99
.github/ISSUE_TEMPLATE/bug_report.yaml vendored Normal file
View file

@ -0,0 +1,99 @@
name: "🐛 Bug Report"
description: "Submit a bug report to help us improve"
#title: "[Bug] "
labels: [bug]
body:
- type: checkboxes
id: no-duplicate-issues
attributes:
label: "⚠️ Please verify that this bug has NOT been reported before."
description: "Search in the issues sections by clicking [HERE](https://github.com/louislam/dockge/issues?q=)"
options:
- label: "I checked and didn't find similar issue"
required: true
- type: checkboxes
attributes:
label: "🛡️ Security Policy"
description: Please review the security policy before reporting security related issues/bugs.
options:
- label: I agree to have read this project [Security Policy](https://github.com/louislam/dockge/security/policy)
required: true
- type: textarea
id: description
validations:
required: false
attributes:
label: "Description"
description: "You could also upload screenshots"
- type: textarea
id: steps-to-reproduce
validations:
required: true
attributes:
label: "👟 Reproduction steps"
description: "How do you trigger this bug? Please walk us through it step by step."
placeholder: "..."
- type: textarea
id: expected-behavior
validations:
required: true
attributes:
label: "👀 Expected behavior"
description: "What did you think would happen?"
placeholder: "..."
- type: textarea
id: actual-behavior
validations:
required: true
attributes:
label: "😓 Actual Behavior"
description: "What actually happen?"
placeholder: "..."
- type: input
id: dockge-version
attributes:
label: "Dockge Version"
description: "Which version of Dockge are you running? Please do NOT provide the docker tag such as latest or 1"
placeholder: "Ex. 1.1.1"
validations:
required: true
- type: input
id: operating-system
attributes:
label: "💻 Operating System and Arch"
description: "Which OS is your server/device running on?"
placeholder: "Ex. Ubuntu 20.04 x64 "
validations:
required: true
- type: input
id: browser-vendor
attributes:
label: "🌐 Browser"
description: "Which browser are you running on?"
placeholder: "Ex. Google Chrome 95.0.4638.69"
validations:
required: true
- type: input
id: docker-version
attributes:
label: "🐋 Docker Version"
description: "If running with Docker, which version are you running?"
placeholder: "Ex. Docker 20.10.9 / K8S / Podman"
validations:
required: false
- type: input
id: nodejs-version
attributes:
label: "🟩 NodeJS Version"
description: "If running with Node.js? which version are you running?"
placeholder: "Ex. 14.18.0"
validations:
required: false
- type: textarea
id: logs
attributes:
label: "📝 Relevant log output"
description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks.
render: shell
validations:
required: false

View file

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

19
.github/ISSUE_TEMPLATE/security.md vendored Normal file
View file

@ -0,0 +1,19 @@
---
name: "Security Issue"
about: "Just for alerting @louislam, do not provide any details here"
title: "Security Issue"
ref: "main"
labels:
- security
---
DO NOT PROVIDE ANY DETAILS HERE. Please privately report to https://github.com/louislam/dockge/security/advisories/new.
Why need this issue? It is because GitHub Advisory do not send a notification to @louislam, it is a workaround to do so.
Your GitHub Advisory URL:

34
.github/PULL_REQUEST_TEMPLATE.md vendored Normal file
View file

@ -0,0 +1,34 @@
⚠️⚠️⚠️ Since we do not accept all types of pull requests and do not want to waste your time. Please be sure that you have read pull request rules:
https://github.com/louislam/dockge/blob/master/CONTRIBUTING.md
Tick the checkbox if you understand [x]:
- [ ] I have read and understand the pull request rules.
# Description
Fixes #(issue)
## Type of change
Please delete any options that are not relevant.
- Bug fix (non-breaking change which fixes an issue)
- User interface (UI)
- New feature (non-breaking change which adds functionality)
- Breaking change (fix or feature that would cause existing functionality to not work as expected)
- Other
- This change requires a documentation update
## Checklist
- [ ] My code follows the style guidelines of this project
- [ ] I ran ESLint and other linters for modified files
- [ ] I have performed a self-review of my own code and tested it
- [ ] I have commented my code, particularly in hard-to-understand areas
(including JSDoc for methods)
- [ ] My changes generate no new warnings
- [ ] My code needed automated testing. I have added them (this is optional task)
## Screenshots (if any)
Please do not use any external image service. Instead, just paste in or drag and drop the image here, and it will be uploaded automatically.

1
.github/config/exclude.txt vendored Normal file
View file

@ -0,0 +1 @@
# This is a .gitignore style file for 'GrantBirki/json-yaml-validate' Action workflow

63
.github/workflows/ci.yml vendored Normal file
View file

@ -0,0 +1,63 @@
name: Node.js CI - Dockge
on:
push:
branches: [master]
paths-ignore:
- '*.md'
pull_request:
branches: [master]
paths-ignore:
- '*.md'
jobs:
ci:
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest, ARM64]
node: [18.17.1] # Can be changed
runs-on: ${{ matrix.os }}
steps:
- name: Checkout Code
uses: actions/checkout@v4
- run: git config --global core.autocrlf false # Mainly for Windows
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: ${{matrix.node}}
- uses: pnpm/action-setup@v2
name: Install pnpm
with:
version: 8
run_install: false
- name: Get pnpm store directory
shell: bash
run: |
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
- uses: actions/cache@v3
name: Setup pnpm cache
with:
path: ${{ env.STORE_PATH }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-
- name: Install dependencies
run: pnpm install
- name: Lint
run: pnpm run lint
- name: Check Typescript
run: pnpm run check-ts
- name: Build
run: pnpm run build:frontend
# more things can be add later like tests etc..

View file

@ -0,0 +1,42 @@
name: Close Incorrect Issue
on:
issues:
types: [opened]
jobs:
close-incorrect-issue:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest]
node-version: [16]
steps:
- uses: actions/checkout@v3
- uses: pnpm/action-setup@v2
name: Install pnpm
with:
version: 8
run_install: false
- name: Get pnpm store directory
shell: bash
run: |
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
- uses: actions/cache@v3
name: Setup pnpm cache
with:
path: ${{ env.STORE_PATH }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-
- name: Install dependencies
run: pnpm install
- name: Close Incorrect Issue
run: node extra/close-incorrect-issue.js ${{ secrets.GITHUB_TOKEN }} ${{ github.event.issue.number }} ${{ github.event.issue.user.login }}

View file

@ -0,0 +1,27 @@
name: json-yaml-validate
on:
push:
branches:
- master
pull_request:
branches:
- master
- 2.0.X
workflow_dispatch:
permissions:
contents: read
pull-requests: write # enable write permissions for pull request comments
jobs:
json-yaml-validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: json-yaml-validate
id: json-yaml-validate
uses: GrantBirki/json-yaml-validate@v1.3.0
with:
comment: "false" # enable comment mode
exclude_file: ".github/config/exclude.txt" # gitignore style file for exclusions

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

141
CONTRIBUTING.md Normal file
View file

@ -0,0 +1,141 @@
## Can I create a pull request for Dockge?
Yes or no, it depends on what you will try to do. Since I don't want to waste your time, be sure to **create open a discussion, so we can have a discussion first**. Especially for a large pull request or you don't know if it will be merged or not.
Here are some references:
### ✅ Usually accepted:
- Bug fix
- Security fix
- 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:
- Large pull requests
- New features
### ❌ 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
- Any breaking changes
- Duplicated pull requests
- Buggy
- UI/UX is not close to Dockge
- Modifications or deletions of existing logic without a valid reason.
- Adding functions that is completely out of scope
- Converting existing code into other programming languages
- Unnecessarily large code changes that are hard to review and cause conflicts with other PRs.
The above cases may not cover all possible situations.
I (@louislam) have the final say. If your pull request does not meet my expectations, I will reject it, no matter how much time you spend on it. Therefore, it is essential to have a discussion beforehand.
I will assign your pull request to a [milestone](https://github.com/louislam/dockge/milestones), if I plan to review and merge it.
Also, please don't rush or ask for an ETA, because I have to understand the pull request, make sure it is no breaking changes and stick to my vision of this project, especially for large pull requests.
## Project Styles
I personally do not like something that requires so many configurations before you can finally start the app.
- Settings should be configurable in the frontend. Environment variables are discouraged, unless it is related to startup such as `DOCKGE_STACKS_DIR`
- Easy to use
- The web UI styling should be consistent and nice
- No native build dependency
## Coding Styles
- 4 spaces indentation
- Follow `.editorconfig`
- Follow ESLint
- Methods and functions should be documented with JSDoc
## Name Conventions
- Javascript/Typescript: camelCaseType
- SQLite: snake_case (Underscore)
- CSS/SCSS: kebab-case (Dash)
## Tools
- [`Node.js`](https://nodejs.org/) >= 20
- [`pnpm`](https://pnpm.io/)
- [`git`](https://git-scm.com/)
- IDE that supports [`ESLint`](https://eslint.org/) and EditorConfig (I am using [`IntelliJ IDEA`](https://www.jetbrains.com/idea/))
- A SQLite GUI tool (f.ex. [`SQLite Expert Personal`](https://www.sqliteexpert.com/download.html) or [`DBeaver Community`](https://dbeaver.io/download/))
## Install Dependencies for Development
```bash
pnpm install
```
## Dev Server
```
pnpm run dev:frontend
pnpm run dev:backend
```
## Backend Dev Server
It binds to `0.0.0.0:5001` by default.
It is mainly a socket.io app + express.js.
## Frontend Dev Server
It binds to `0.0.0.0:5000` by default. The frontend dev server is used for development only.
For production, it is not used. It will be compiled to `frontend-dist` directory instead.
You can use Vue.js devtools Chrome extension for debugging.
### Build the frontend
```bash
pnpm run build
```
## Database Migration
TODO
## Dependencies
Both frontend and backend share the same package.json. However, the frontend dependencies are eventually not used in the production environment, because it is usually also baked into dist files. So:
- Frontend dependencies = "devDependencies"
- Examples: vue, chart.js
- Backend dependencies = "dependencies"
- Examples: socket.io, sqlite3
- Development dependencies = "devDependencies"
- Examples: eslint, sass
### Update Dependencies
Should only be done by the maintainer.
```bash
pnpm update
````
It should update the patch release version only.
Patch release = the third digit ([Semantic Versioning](https://semver.org/))
If for security / bug / other reasons, a library must be updated, breaking changes need to be checked by the person proposing the change.
## Translations
Please add **all** the strings which are translatable to `src/lang/en.json` (If translation keys are omitted, they can not be translated).
**Don't include any other languages in your initial Pull-Request** (even if this is your mother tongue), to avoid merge-conflicts between weblate and `master`.
The translations can then (after merging a PR into `master`) be translated by awesome people donating their language skills.
If you want to help by translating Uptime Kuma into your language, please visit the [instructions on how to translate using weblate](https://github.com/louislam/uptime-kuma/blob/master/src/lang/README.md).
## Spelling & Grammar
Feel free to correct the grammar in the documentation or code.
My mother language is not English and my grammar is not that great.

117
README.md
View file

@ -6,6 +6,8 @@
A fancy, easy-to-use and reactive self-hosted docker compose.yaml stack-oriented manager.
![GitHub Repo stars](https://img.shields.io/github/stars/louislam/dockge?logo=github) ![GitHub issues](https://img.shields.io/github/issues/louislam/dockge?logo=github) ![GitHub pull requests](https://img.shields.io/github/issues-pr/louislam/dockge?logo=github) ![Docker Pulls](https://img.shields.io/docker/pulls/louislam/dockge?logo=docker) ![Docker Image Version (latest semver)](https://img.shields.io/docker/v/louislam/dockge/latest?label=docker%20image%20ver.) ![GitHub last commit (branch)](https://img.shields.io/github/last-commit/louislam/dockge/master?logo=github) ![GitHub](https://img.shields.io/github/license/louislam/dockge?logo=github)
<img src="https://github.com/louislam/dockge/assets/1336778/26a583e1-ecb1-4a8d-aedf-76157d714ad7" width="900" alt="" />
View Video: https://youtu.be/AWAlOQeNpgU?t=48
@ -20,10 +22,10 @@ View Video: https://youtu.be/AWAlOQeNpgU?t=48
- Reactive
- Everything is just responsive. Progress (Pull/Up/Down) and terminal output are in real-time
- Easy-to-use & fancy UI
- If you love Uptime Kuma's UI/UX, you will love this too
- If you love Uptime Kuma's UI/UX, you will love this one too
- Convert `docker run ...` commands into `compose.yaml`
- File based structure
- Dockge won't kidnap your compose files, they stored on your drive as usual. You can interact with them using normal `docker compose` commands
- Dockge won't kidnap your compose files, they are stored on your drive as usual. You can interact with them using normal `docker compose` commands
<img src="https://github.com/louislam/dockge/assets/1336778/cc071864-592e-4909-b73a-343a57494002" width=300 />
@ -32,11 +34,18 @@ View Video: https://youtu.be/AWAlOQeNpgU?t=48
## 🔧 How to Install
Requirements:
- [Docker CE](https://docs.docker.com/engine/install/) 20+ is recommended
- [Docker Compose V2](https://docs.docker.com/compose/install/linux/)
- OS:
- As long as you can run Docker CE, it should be fine, but:
- Debian/Raspbian Buster or lower is not supported, please upgrade to Bullseye
- [Docker](https://docs.docker.com/engine/install/) 20+ / Podman
- (Podman only) podman-docker (Debian: `apt install podman-docker`)
- OS:
- Major Linux distros that can run Docker/Podman such as:
- ✅ 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)
### Basic
@ -45,58 +54,42 @@ Requirements:
- 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
cd /opt/dockge
# Download the compose.yaml
curl https://raw.githubusercontent.com/louislam/dockge/master/compose.yaml --output compose.yaml
# Start Server
# Start the server
docker compose up -d
# If you are using docker-compose V1
# docker-compose up -d
# If you are using docker-compose V1 or Podman
# docker-compose up -d
```
Dockge is now running on http://localhost:5001
### 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.
For example, if you want to store your stacks in `/my-stacks`:
```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)
- /my-stacks:/my-stacks
environment:
# Tell Dockge where is your stacks directory
- DOCKGE_STACKS_DIR=/my-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
```bash
cd /opt/stacks
docker compose pull
docker compose up -d
cd /opt/dockge
docker compose pull && docker compose up -d
```
## Screenshots
@ -114,28 +107,37 @@ docker compose up -d
## Motivations
- I have been using Portainer for some time, but for the stack management, I am sometimes not satisfied with it. For example, sometimes when I try to deploy a stack, the loading icon keeps spinning for a few minutes without progress. And sometimes error messages are not clear.
- Try to develop with ES Module + TypeScript (Originally, I planned to use Deno or Bun.js, but they do not support for arm64, so I stepped back to Node.js)
- Try to develop with ES Module + TypeScript (Originally, I planned to use Deno or Bun.js, but they don't have support for arm64, so I stepped back to Node.js)
If you love this project, please consider giving this project a ⭐.
If you love this project, please consider giving it a ⭐.
## 🗣️ Discussion / Ask for Help
## 🗣️ Community and Contribution
Please go to https://github.com/louislam/dockge/discussions
### Bug Report
https://github.com/louislam/dockge/issues
### Ask for Help / Discussions
https://github.com/louislam/dockge/discussions
### 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)
### 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
#### "Dockge"?
"Dockge" is a coinage word which is created by myself. I hope it sounds like `Badge` but replacing with `Dock` - `Dock-ge`.
"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 was coming from Twitch emotes like `sadge`, `bedge` or `wokege`. They are all ending with `-ge`.
If you are not comfortable with the pronunciation, you can call it `Dockage`.
The naming idea came from Twitch emotes like `sadge`, `bedge` or `wokege`. They all end in `-ge`.
#### Can I manage a single container without `compose.yaml`?
The main objective of Dockge is that try to use docker `compose.yaml` for everything. If you want to manage a single container, you can just use Portainer or Docker CLI.
The main objective of Dockge is to try to use the docker `compose.yaml` for everything. If you want to manage a single container, you can just use Portainer or Docker CLI.
#### Can I manage existing stacks?
@ -146,19 +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
4. Now you should see your stack in the list
## More Ideas?
#### Is Dockge a Portainer replcement?
- Stats
- File manager
- App store for yaml templates
- Get app icons
- Switch Docker context
- Support Dockerfile and build
- Support Docker swarm
Yes or no. Portainer provides a lot of Docker features. While Dockge is currently only focusing on docker-compose with a better user interface and better user experience.
If you want to manage your container with docker-compose only, the answer may be yes.
# Others
If you still need to manage something like docker networks, signle containers, the answer may be no.
Dockge is built on top of [Compose V2](https://docs.docker.com/compose/migrate/). `compose.yaml` is also known as `docker-compose.yml`.
#### 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`.

12
SECURITY.md Normal file
View file

@ -0,0 +1,12 @@
# Security Policy
## Reporting a Vulnerability
1. Please report security issues to https://github.com/louislam/dockge/security/advisories/new.
1. Please also create an empty security issue to alert me, as GitHub Advisories do not send a notification, I probably will miss it without this. https://github.com/louislam/dockge/issues/new?assignees=&labels=help&template=security.md
Do not use the public issue tracker or discuss it in public as it will cause more damage.
## Do you accept other 3rd-party bug bounty platforms?
At this moment, I DO NOT accept other bug bounty platforms, because I am not familiar with these platforms and someone has tried to send a phishing link to me by doing this already. To minimize my own risk, please report through GitHub Advisories only. I will ignore all 3rd-party bug bounty platforms emails.

View file

@ -5,6 +5,7 @@ import fs from "fs";
import path from "path";
import knex from "knex";
// @ts-ignore
import Dialect from "knex/lib/dialects/sqlite3/index.js";
import sqlite from "@louislam/sqlite3";
@ -12,6 +13,11 @@ import { sleep } from "./util-common";
interface DBConfig {
type?: "sqlite" | "mysql";
hostname?: string;
port?: string;
database?: string;
username?: string;
password?: string;
}
export class Database {
@ -19,7 +25,7 @@ export class Database {
* SQLite file path (Default: ./data/dockge.db)
* @type {string}
*/
static sqlitePath;
static sqlitePath : string;
static noReject = true;
@ -51,7 +57,7 @@ export class Database {
* @typedef {string|undefined} envString
* @returns {{type: "sqlite"} | {type:envString, hostname:envString, port:envString, database:envString, username:envString, password:envString}} Database config
*/
static readDBConfig() {
static readDBConfig() : DBConfig {
const dbConfigString = fs.readFileSync(path.join(this.server.config.dataDir, "db-config.json")).toString("utf-8");
const dbConfig = JSON.parse(dbConfigString);
@ -67,10 +73,10 @@ export class Database {
/**
* @typedef {string|undefined} envString
* @param {{type: "sqlite"} | {type:envString, hostname:envString, port:envString, database:envString, username:envString, password:envString}} dbConfig the database configuration that should be written
* @param dbConfig the database configuration that should be written
* @returns {void}
*/
static writeDBConfig(dbConfig) {
static writeDBConfig(dbConfig : DBConfig) {
fs.writeFileSync(path.join(this.server.config.dataDir, "db-config.json"), JSON.stringify(dbConfig, null, 4));
}
@ -80,14 +86,17 @@ export class Database {
* @param {boolean} noLog Should logs not be output?
* @returns {Promise<void>}
*/
static async connect(autoloadModels = true, noLog = false) {
static async connect(autoloadModels = true) {
const acquireConnectionTimeout = 120 * 1000;
let dbConfig;
let dbConfig : DBConfig;
try {
dbConfig = this.readDBConfig();
Database.dbConfig = dbConfig;
} catch (err) {
log.warn("db", err.message);
if (err instanceof Error) {
log.warn("db", err.message);
}
dbConfig = {
type: "sqlite",
};
@ -176,13 +185,15 @@ export class Database {
directory: Database.knexMigrationsPath,
});
} catch (e) {
// Allow missing patch files for downgrade or testing pr.
if (e.message.includes("the following files are missing:")) {
log.warn("db", e.message);
log.warn("db", "Database migration failed, you may be downgrading Dockge.");
} else {
log.error("db", "Database migration failed");
throw e;
if (e instanceof Error) {
// Allow missing patch files for downgrade or testing pr.
if (e.message.includes("the following files are missing:")) {
log.warn("db", e.message);
log.warn("db", "Database migration failed, you may be downgrading Dockge.");
} else {
log.error("db", "Database migration failed");
throw e;
}
}
}
}

View file

@ -29,7 +29,10 @@ import { Stack } from "./stack";
import { Cron } from "croner";
import gracefulShutdown from "http-graceful-shutdown";
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 {
app : Express;
@ -60,7 +63,7 @@ export class DockgeServer {
*/
needSetup = false;
jwtSecret? : string;
jwtSecret : string = "";
stacksDir : string = "";
@ -129,7 +132,7 @@ export class DockgeServer {
this.config.sslKey = args.sslKey || process.env.DOCKGE_SSL_KEY || undefined;
this.config.sslCert = args.sslCert || process.env.DOCKGE_SSL_CERT || undefined;
this.config.sslKeyPassphrase = args.sslKeyPassphrase || process.env.DOCKGE_SSL_KEY_PASSPHRASE || undefined;
this.config.port = args.port || parseInt(process.env.DOCKGE_PORT) || 5001;
this.config.port = args.port || Number(process.env.DOCKGE_PORT) || 5001;
this.config.hostname = args.hostname || process.env.DOCKGE_HOSTNAME || undefined;
this.config.dataDir = args.dataDir || process.env.DOCKGE_DATA_DIR || "./data/";
this.config.stacksDir = args.stacksDir || process.env.DOCKGE_STACKS_DIR || defaultStacksDir;
@ -149,9 +152,6 @@ export class DockgeServer {
}
}
// Create all the necessary directories
this.initDataDir();
// Create express
this.app = express();
@ -218,7 +218,7 @@ export class DockgeServer {
log.debug("auth", "check auto login");
if (await Settings.get("disableAuth")) {
log.info("auth", "Disabled Auth: auto login to admin");
this.afterLogin(socket as DockgeSocket, await R.findOne("user"));
this.afterLogin(socket as DockgeSocket, await R.findOne("user") as User);
socket.emit("autoLogin");
} else {
log.debug("auth", "need auth");
@ -230,6 +230,11 @@ export class DockgeServer {
});
if (isDev) {
setInterval(() => {
log.debug("terminal", "Terminal count: " + Terminal.getTerminalCount());
}, 5000);
}
}
async afterLogin(socket : DockgeSocket, user : User) {
@ -249,11 +254,16 @@ export class DockgeServer {
*
*/
async serve() {
// Create all the necessary directories
this.initDataDir();
// Connect to database
try {
await Database.init(this);
} catch (e) {
log.error("server", "Failed to prepare your database: " + e.message);
if (e instanceof Error) {
log.error("server", "Failed to prepare your database: " + e.message);
}
process.exit(1);
}
@ -283,18 +293,18 @@ export class DockgeServer {
}
// Listen
this.httpServer.listen(5001, this.config.hostname, () => {
this.httpServer.listen(this.config.port, this.config.hostname, () => {
if (this.config.hostname) {
log.info( "server", `Listening on ${this.config.hostname}:${this.config.port}`);
} else {
log.info("server", `Listening on ${this.config.port}`);
}
// Run every 5 seconds
const job = Cron("*/2 * * * * *", {
// Run every 10 seconds
Cron("*/10 * * * * *", {
protect: true, // Enabled over-run protection.
}, () => {
log.debug("server", "Cron job running");
//log.debug("server", "Cron job running");
this.sendStackList(true);
});
@ -376,7 +386,9 @@ export class DockgeServer {
return process.env.TZ;
}
} catch (e) {
log.warn("timezone", e.message + " in process.env.TZ");
if (e instanceof Error) {
log.warn("timezone", e.message + " in process.env.TZ");
}
}
const timezone = await Settings.get("serverTimezone");
@ -389,7 +401,9 @@ export class DockgeServer {
return timezone;
}
} catch (e) {
log.warn("timezone", e.message + " in settings");
if (e instanceof Error) {
log.warn("timezone", e.message + " in settings");
}
}
// Guess
@ -471,7 +485,7 @@ export class DockgeServer {
return jwtSecretBean;
}
sendStackList(useCache = false) {
async sendStackList(useCache = false) {
let roomList = this.io.sockets.adapter.rooms.keys();
let map : Map<string, object> | undefined;
@ -482,7 +496,7 @@ export class DockgeServer {
// Get the list only if there is a room
if (!map) {
map = new Map();
let stackList = Stack.getStackList(this, useCache);
let stackList = await Stack.getStackList(this, useCache);
for (let [ stackName, stack ] of stackList) {
map.set(stackName, stack.toSimpleJSON());
@ -498,8 +512,8 @@ export class DockgeServer {
}
}
sendStackStatusList() {
let statusList = Stack.getStatusList();
async sendStackStatusList() {
let statusList = await Stack.getStatusList();
let roomList = this.io.sockets.adapter.rooms.keys();
@ -517,8 +531,15 @@ export class DockgeServer {
}
}
getDockerNetworkList() : string[] {
let res = childProcess.spawnSync("docker", [ "network", "ls", "--format", "{{.Name}}" ]);
async getDockerNetworkList() : Promise<string[]> {
let res = await childProcessAsync.spawn("docker", [ "network", "ls", "--format", "{{.Name}}" ], {
encoding: "utf-8",
});
if (!res.stdout) {
return [];
}
let list = res.stdout.toString().split("\n");
// Remove empty string item

View file

@ -103,6 +103,10 @@ class Logger {
* @param level Log level. One of INFO, WARN, ERROR, DEBUG or can be customized.
*/
log(module: string, msg: unknown, level: string) {
if (level === "DEBUG" && !isDev) {
return;
}
if (this.hideLog[level] && this.hideLog[level].includes(module.toLowerCase())) {
return;
}

View file

@ -17,7 +17,7 @@ export function generatePasswordHash(password : string) {
* @param {string} hash Hash to verify against
* @returns {boolean} Does the password match the hash?
*/
export function verifyPassword(password, hash) {
export function verifyPassword(password : string, hash : string) {
return bcrypt.compareSync(password, hash);
}
@ -37,7 +37,7 @@ export const SHAKE256_LENGTH = 16;
* @param {number} len Output length of the hash
* @returns {string} The hashed data in hex format
*/
export function shake256(data, len) {
export function shake256(data : string, len : number) {
if (!data) {
return "";
}

View file

@ -1,8 +1,14 @@
// "limit" is bugged in Typescript, use "limiter-es6-compat" instead
// See https://github.com/jhurliman/node-rate-limiter/issues/80
import { RateLimiter } from "limiter-es6-compat";
import { RateLimiter, RateLimiterOpts } from "limiter-es6-compat";
import { log } from "./log";
export interface KumaRateLimiterOpts extends RateLimiterOpts {
errorMessage : string;
}
export type KumaRateLimiterCallback = (err : object) => void;
class KumaRateLimiter {
errorMessage : string;
@ -11,7 +17,7 @@ class KumaRateLimiter {
/**
* @param {object} config Rate limiter configuration object
*/
constructor(config) {
constructor(config : KumaRateLimiterOpts) {
this.errorMessage = config.errorMessage;
this.rateLimiter = new RateLimiter(config);
}
@ -24,11 +30,11 @@ class KumaRateLimiter {
/**
* Should the request be passed through
* @param {passCB} callback Callback function to call with decision
* @param callback Callback function to call with decision
* @param {number} num Number of tokens to remove
* @returns {Promise<boolean>} Should the request be allowed?
*/
async pass(callback, num = 1) {
async pass(callback : KumaRateLimiterCallback, num = 1) {
const remainingRequests = await this.removeTokens(num);
log.info("rate-limit", "remaining requests: " + remainingRequests);
if (remainingRequests < 0) {

View file

@ -1,4 +1,4 @@
import { DockgeServer } from "../dockgeServer";
import { DockgeServer } from "../dockge-server";
import { Router } from "../router";
import express, { Express, Router as ExpressRouter } from "express";

View file

@ -1,5 +1,6 @@
import { R } from "redbean-node";
import { log } from "./log";
import { LooseObject } from "./util-common";
export class Settings {
@ -15,20 +16,19 @@ export class Settings {
* timestamp: 12345678
* },
* }
* @type {{}}
*/
static cacheList = {
static cacheList : LooseObject = {
};
static cacheCleaner = null;
static cacheCleaner? : NodeJS.Timeout;
/**
* Retrieve value of setting based on key
* @param {string} key Key of setting to retrieve
* @returns {Promise<any>} Value
* @param key Key of setting to retrieve
* @returns Value
*/
static async get(key) {
static async get(key : string) {
// Start cache clear if not started yet
if (!Settings.cacheCleaner) {
@ -72,12 +72,12 @@ export class Settings {
/**
* Sets the specified setting to specified value
* @param {string} key Key of setting to set
* @param {any} value Value to set to
* @param key Key of setting to set
* @param value Value to set to
* @param {?string} type Type of setting
* @returns {Promise<void>}
*/
static async set(key, value, type = null) {
static async set(key : string, value : object | string | number | boolean, type : string | null = null) {
let bean = await R.findOne("setting", " `key` = ? ", [
key,
@ -95,15 +95,15 @@ export class Settings {
/**
* Get settings based on type
* @param {string} type The type of setting
* @returns {Promise<Bean>} Settings
* @param type The type of setting
* @returns Settings
*/
static async getSettings(type) {
static async getSettings(type : string) {
const list = await R.getAll("SELECT `key`, `value` FROM setting WHERE `type` = ? ", [
type,
]);
const result = {};
const result : LooseObject = {};
for (const row of list) {
try {
@ -118,11 +118,11 @@ export class Settings {
/**
* Set settings based on type
* @param {string} type Type of settings to set
* @param {object} data Values of settings
* @param type Type of settings to set
* @param data Values of settings
* @returns {Promise<void>}
*/
static async setSettings(type, data) {
static async setSettings(type : string, data : LooseObject) {
const keyList = Object.keys(data);
const promiseList = [];
@ -154,7 +154,7 @@ export class Settings {
* @param {string[]} keyList Keys to remove
* @returns {void}
*/
static deleteCache(keyList) {
static deleteCache(keyList : string[]) {
for (const key of keyList) {
delete Settings.cacheList[key];
}
@ -167,7 +167,7 @@ export class Settings {
static stopCacheCleaner() {
if (Settings.cacheCleaner) {
clearInterval(Settings.cacheCleaner);
Settings.cacheCleaner = null;
Settings.cacheCleaner = undefined;
}
}
}

View file

@ -9,10 +9,10 @@ import composerize from "composerize";
export class DockerSocketHandler extends SocketHandler {
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 {
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);
server.sendStackList();
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 {
checkLogin(socket);
this.saveStack(socket, server, name, composeYAML, isAdd);
this.saveStack(socket, server, name, composeYAML, composeENV, isAdd);
callback({
ok: true,
"msg": "Saved"
@ -45,7 +45,7 @@ export class DockerSocketHandler extends SocketHandler {
if (typeof(name) !== "string") {
throw new ValidationError("Name must be a string");
}
const stack = Stack.getStack(server, name);
const stack = await Stack.getStack(server, name);
try {
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 {
checkLogin(socket);
@ -73,9 +73,11 @@ export class DockerSocketHandler extends SocketHandler {
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({
ok: true,
@ -109,7 +111,7 @@ export class DockerSocketHandler extends SocketHandler {
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);
callback({
ok: true,
@ -133,7 +135,7 @@ export class DockerSocketHandler extends SocketHandler {
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);
callback({
ok: true,
@ -154,7 +156,7 @@ export class DockerSocketHandler extends SocketHandler {
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);
callback({
ok: true,
@ -175,7 +177,7 @@ export class DockerSocketHandler extends SocketHandler {
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);
callback({
ok: true,
@ -187,6 +189,27 @@ export class DockerSocketHandler extends SocketHandler {
}
});
// down stack
socket.on("downStack", async (stackName : unknown, callback) => {
try {
checkLogin(socket);
if (typeof(stackName) !== "string") {
throw new ValidationError("Stack name must be a string");
}
const stack = await Stack.getStack(server, stackName);
await stack.down(socket);
callback({
ok: true,
msg: "Downed"
});
server.sendStackList();
} catch (e) {
callbackError(e, callback);
}
});
// Services status
socket.on("serviceStatusList", async (stackName : unknown, callback) => {
try {
@ -196,7 +219,7 @@ export class DockerSocketHandler extends SocketHandler {
throw new ValidationError("Stack name must be a string");
}
const stack = Stack.getStack(server, stackName);
const stack = await Stack.getStack(server, stackName, true);
const serviceStatusList = Object.fromEntries(await stack.getServiceStatusList());
callback({
ok: true,
@ -211,7 +234,7 @@ export class DockerSocketHandler extends SocketHandler {
socket.on("getDockerNetworkList", async (callback) => {
try {
checkLogin(socket);
const dockerNetworkList = server.getDockerNetworkList();
const dockerNetworkList = await server.getDockerNetworkList();
callback({
ok: true,
dockerNetworkList,
@ -241,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
if (typeof(name) !== "string") {
throw new ValidationError("Name must be a string");
@ -249,12 +272,15 @@ export class DockerSocketHandler extends SocketHandler {
if (typeof(composeYAML) !== "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") {
throw new ValidationError("isAdd must be a boolean");
}
const stack = new Stack(server, name, composeYAML);
stack.save(isAdd);
const stack = new Stack(server, name, composeYAML, composeENV, false);
await stack.save(isAdd);
return stack;
}

View file

@ -1,12 +1,11 @@
import { SocketHandler } from "../socket-handler.js";
import { Socket } from "socket.io";
import { DockgeServer } from "../dockge-server";
import { log } from "../log";
import { R } from "redbean-node";
import { loginRateLimiter, twoFaRateLimiter } from "../rate-limiter";
import { generatePasswordHash, needRehashPassword, shake256, SHAKE256_LENGTH, verifyPassword } from "../password-hash";
import { User } from "../models/user";
import { checkLogin, DockgeSocket, doubleCheckPassword } from "../util-server";
import { checkLogin, DockgeSocket, doubleCheckPassword, JWTDecoded } from "../util-server";
import { passwordStrength } from "check-password-strength";
import jwt from "jsonwebtoken";
import { Settings } from "../settings";
@ -43,10 +42,12 @@ export class MainSocketHandler extends SocketHandler {
});
} catch (e) {
callback({
ok: false,
msg: e.message,
});
if (e instanceof Error) {
callback({
ok: false,
msg: e.message,
});
}
}
});
@ -57,7 +58,7 @@ export class MainSocketHandler extends SocketHandler {
log.info("auth", `Login by token. IP=${clientIP}`);
try {
const decoded = jwt.verify(token, server.jwtSecret);
const decoded = jwt.verify(token, server.jwtSecret) as JWTDecoded;
log.info("auth", "Username from JWT: " + decoded.username);
@ -91,9 +92,13 @@ export class MainSocketHandler extends SocketHandler {
});
}
} catch (error) {
if (!(error instanceof Error)) {
console.error("Unknown error:", error);
return;
}
log.error("auth", `Invalid token. IP=${clientIP}`);
if (error.message) {
log.error("auth", error.message, `IP=${clientIP}`);
log.error("auth", error.message + ` IP=${clientIP}`);
}
callback({
ok: false,
@ -149,6 +154,7 @@ export class MainSocketHandler extends SocketHandler {
}
if (data.token) {
// @ts-ignore
const verify = notp.totp.verify(data.token, user.twofa_secret, twoFAVerifyOptions);
if (user.twofa_last_token !== data.token && verify) {
@ -211,10 +217,12 @@ export class MainSocketHandler extends SocketHandler {
});
} catch (e) {
callback({
ok: false,
msg: e.message,
});
if (e instanceof Error) {
callback({
ok: false,
msg: e.message,
});
}
}
});
@ -229,10 +237,12 @@ export class MainSocketHandler extends SocketHandler {
});
} catch (e) {
callback({
ok: false,
msg: e.message,
});
if (e instanceof Error) {
callback({
ok: false,
msg: e.message,
});
}
}
});
@ -262,22 +272,24 @@ export class MainSocketHandler extends SocketHandler {
server.sendInfo(socket);
} catch (e) {
callback({
ok: false,
msg: e.message,
});
if (e instanceof Error) {
callback({
ok: false,
msg: e.message,
});
}
}
});
}
async login(username : string, password : string) {
async login(username : string, password : string) : Promise<User | null> {
if (typeof username !== "string" || typeof password !== "string") {
return null;
}
const user = await R.findOne("user", " username = ? AND active = 1 ", [
username,
]);
]) as User;
if (user && verifyPassword(password, user.password)) {
// Upgrade the hash to bcrypt

View file

@ -38,10 +38,12 @@ export class TerminalSocketHandler extends SocketHandler {
throw new Error("Terminal not found or it is not a Interactive Terminal.");
}
} catch (e) {
errorCallback({
ok: false,
msg: e.message,
});
if (e instanceof Error) {
errorCallback({
ok: false,
msg: e.message,
});
}
}
});
@ -99,7 +101,7 @@ export class TerminalSocketHandler extends SocketHandler {
log.debug("interactiveTerminal", "Service name: " + serviceName);
// Get stack
const stack = Stack.getStack(server, stackName);
const stack = await Stack.getStack(server, stackName);
stack.joinContainerTerminal(socket, serviceName, shell);
callback({
@ -138,9 +140,26 @@ export class TerminalSocketHandler extends SocketHandler {
}
});
// Close Terminal
socket.on("terminalClose", async (terminalName : unknown, callback : unknown) => {
// Leave Combined Terminal
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

View file

@ -1,8 +1,8 @@
import { DockgeServer } from "./dockge-server";
import fs from "fs";
import fs, { promises as fsAsync } from "fs";
import { log } from "./log";
import yaml from "yaml";
import { DockgeSocket, ValidationError } from "./util-server";
import { DockgeSocket, fileExists, ValidationError } from "./util-server";
import path from "path";
import {
COMBINED_TERMINAL_COLS,
@ -16,24 +16,38 @@ import {
UNKNOWN
} from "./util-common";
import { InteractiveTerminal, Terminal } from "./terminal";
import childProcess from "child_process";
import childProcessAsync from "promisify-child-process";
export class Stack {
name: string;
protected _status: number = UNKNOWN;
protected _composeYAML?: string;
protected _composeENV?: string;
protected _configFilePath?: string;
protected _composeFileName: string = "compose.yaml";
protected server: DockgeServer;
protected combinedTerminal? : Terminal;
protected static managedStackList: Map<string, Stack> = new Map();
constructor(server : DockgeServer, name : string, composeYAML? : string) {
constructor(server : DockgeServer, name : string, composeYAML? : string, composeENV? : string, skipFSOperations = false) {
this.name = name;
this.server = server;
this._composeYAML = composeYAML;
this._composeENV = composeENV;
if (!skipFSOperations) {
// Check if compose file name is different from compose.yaml
const supportedFileNames = [ "compose.yaml", "compose.yml", "docker-compose.yml", "docker-compose.yaml" ];
for (const filename of supportedFileNames) {
if (fs.existsSync(path.join(this.path, filename))) {
this._composeFileName = filename;
break;
}
}
}
}
toJSON() : object {
@ -41,6 +55,7 @@ export class Stack {
return {
...obj,
composeYAML: this.composeYAML,
composeENV: this.composeENV,
};
}
@ -50,17 +65,22 @@ export class Stack {
status: this._status,
tags: [],
isManagedByDockge: this.isManagedByDockge,
composeFileName: this._composeFileName,
};
}
/**
* Get the status of the stack from `docker compose ps --format json`
*/
ps() : object {
let res = childProcess.execSync("docker compose ps --format json", {
cwd: this.path
async ps() : Promise<object> {
let res = await childProcessAsync.spawn("docker", [ "compose", "ps", "--format", "json" ], {
cwd: this.path,
encoding: "utf-8",
});
return JSON.parse(res.toString());
if (!res.stdout) {
return {};
}
return JSON.parse(res.stdout.toString());
}
get isManagedByDockge() : boolean {
@ -72,19 +92,28 @@ export class Stack {
}
validate() {
// Check name, allows [a-z][A-Z][0-9] _ - only
if (!this.name.match(/^[a-zA-Z0-9_-]+$/)) {
throw new ValidationError("Stack name can only contain [a-z][A-Z][0-9] _ - only");
// Check name, allows [a-z][0-9] _ - only
if (!this.name.match(/^[a-z0-9_-]+$/)) {
throw new ValidationError("Stack name can only contain [a-z][0-9] _ - only");
}
// Check YAML format
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 {
if (this._composeYAML === undefined) {
try {
this._composeYAML = fs.readFileSync(path.join(this.path, "compose.yaml"), "utf-8");
this._composeYAML = fs.readFileSync(path.join(this.path, this._composeFileName), "utf-8");
} catch (e) {
this._composeYAML = "";
}
@ -92,6 +121,17 @@ export class Stack {
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 {
return path.join(this.server.stacksDir, this.name);
}
@ -115,27 +155,35 @@ export class Stack {
* Save the stack to the disk
* @param isAdd
*/
save(isAdd : boolean) {
async save(isAdd : boolean) {
this.validate();
let dir = this.path;
// Check if the name is used if isAdd
if (isAdd) {
if (fs.existsSync(dir)) {
if (await fileExists(dir)) {
throw new ValidationError("Stack name already exists");
}
// Create the stack folder
fs.mkdirSync(dir);
await fsAsync.mkdir(dir);
} else {
if (!fs.existsSync(dir)) {
if (!await fileExists(dir)) {
throw new ValidationError("Stack not found");
}
}
// Write or overwrite the compose.yaml
fs.writeFileSync(path.join(dir, "compose.yaml"), 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> {
@ -149,13 +197,13 @@ export class Stack {
async delete(socket?: DockgeSocket) : Promise<number> {
const terminalName = getComposeTerminalName(this.name);
let exitCode = await Terminal.exec(this.server, socket, terminalName, "docker", [ "compose", "down", "--remove-orphans", "--rmi", "all" ], this.path);
let exitCode = await Terminal.exec(this.server, socket, terminalName, "docker", [ "compose", "down", "--remove-orphans" ], this.path);
if (exitCode !== 0) {
throw new Error("Failed to delete, please check the terminal output for more information.");
}
// Remove the stack folder
fs.rmSync(this.path, {
await fsAsync.rm(this.path, {
recursive: true,
force: true
});
@ -163,25 +211,44 @@ export class Stack {
return exitCode;
}
static getStackList(server : DockgeServer, useCacheForManaged = false) : Map<string, Stack> {
async updateStatus() {
let statusList = await Stack.getStatusList();
let status = statusList.get(this.name);
if (status) {
this._status = status;
} else {
this._status = UNKNOWN;
}
}
static async getStackList(server : DockgeServer, useCacheForManaged = false) : Promise<Map<string, Stack>> {
let stacksDir = server.stacksDir;
let stackList : Map<string, Stack>;
// Use cached stack list?
if (useCacheForManaged && this.managedStackList.size > 0) {
stackList = this.managedStackList;
} else {
stackList = new Map<string, Stack>();
// 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) {
try {
let stack = this.getStack(server, filename);
// Check if it is a directory
let stat = await fsAsync.stat(path.join(stacksDir, filename));
if (!stat.isDirectory()) {
continue;
}
let stack = await this.getStack(server, filename);
stack._status = CREATED_FILE;
stackList.set(filename, stack);
} catch (e) {
log.warn("getStackList", `Failed to get stack ${filename}, error: ${e.message}`);
if (e instanceof Error) {
log.warn("getStackList", `Failed to get stack ${filename}, error: ${e.message}`);
}
}
}
@ -189,22 +256,26 @@ export class Stack {
this.managedStackList = new Map(stackList);
}
// Also get the list from `docker compose ls --all --format json`
let res = childProcess.execSync("docker compose ls --all --format json");
let composeList = JSON.parse(res.toString());
// Get status from docker compose ls
let res = await childProcessAsync.spawn("docker", [ "compose", "ls", "--all", "--format", "json" ], {
encoding: "utf-8",
});
if (!res.stdout) {
return stackList;
}
let composeList = JSON.parse(res.stdout.toString());
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);
// This stack probably is not managed by Dockge, but we still want to show it
if (!stack) {
// Skip the dockge stack if it is not managed by Dockge
if (composeStack.Name === "dockge") {
continue;
}
stack = new Stack(server, composeStack.Name);
stackList.set(composeStack.Name, stack);
}
@ -220,11 +291,18 @@ export class Stack {
* 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
*/
static getStatusList() : Map<string, number> {
static async getStatusList() : Promise<Map<string, number>> {
let statusList = new Map<string, number>();
let res = childProcess.execSync("docker compose ls --all --format json");
let composeList = JSON.parse(res.toString());
let res = await childProcessAsync.spawn("docker", [ "compose", "ls", "--all", "--format", "json" ], {
encoding: "utf-8",
});
if (!res.stdout) {
return statusList;
}
let composeList = JSON.parse(res.stdout.toString());
for (let composeStack of composeList) {
statusList.set(composeStack.Name, this.statusConvert(composeStack.Status));
@ -235,37 +313,51 @@ export class Stack {
/**
* Convert the status string from `docker compose ls` to the status number
* Input Example: "exited(1), running(1)"
* @param status
*/
static statusConvert(status : string) : number {
if (status.startsWith("created")) {
return CREATED_STACK;
} else if (status.startsWith("running")) {
return RUNNING;
} else if (status.startsWith("exited")) {
} else if (status.includes("exited")) {
// If one of the service is exited, we consider the stack is exited
return EXITED;
} else if (status.startsWith("running")) {
// If there is no exited services, there should be only running services
return RUNNING;
} else {
return UNKNOWN;
}
}
static getStack(server: DockgeServer, stackName: string) : Stack {
static async getStack(server: DockgeServer, stackName: string, skipFSOperations = false) : Promise<Stack> {
let dir = path.join(server.stacksDir, stackName);
if (!fs.existsSync(dir) || !fs.statSync(dir).isDirectory()) {
// Maybe it is a stack managed by docker compose directly
let stackList = this.getStackList(server);
let stack = stackList.get(stackName);
if (!skipFSOperations) {
if (!await fileExists(dir) || !(await fsAsync.stat(dir)).isDirectory()) {
// Maybe it is a stack managed by docker compose directly
let stackList = await this.getStackList(server, true);
let stack = stackList.get(stackName);
if (stack) {
return stack;
} else {
// Really not found
throw new ValidationError("Stack not found");
if (stack) {
return stack;
} else {
// Really not found
throw new ValidationError("Stack not found");
}
}
} else {
//log.debug("getStack", "Skip FS operations");
}
let stack : Stack;
if (!skipFSOperations) {
stack = new Stack(server, stackName);
} else {
stack = new Stack(server, stackName, undefined, undefined, true);
}
let stack = new Stack(server, stackName);
stack._status = UNKNOWN;
stack._configFilePath = path.resolve(dir);
return stack;
@ -298,12 +390,29 @@ export class Stack {
return exitCode;
}
async down(socket: DockgeSocket) : Promise<number> {
const terminalName = getComposeTerminalName(this.name);
let exitCode = await Terminal.exec(this.server, socket, terminalName, "docker", [ "compose", "down" ], this.path);
if (exitCode !== 0) {
throw new Error("Failed to down, please check the terminal output for more information.");
}
return exitCode;
}
async update(socket: DockgeSocket) {
const terminalName = getComposeTerminalName(this.name);
let exitCode = await Terminal.exec(this.server, socket, terminalName, "docker", [ "compose", "pull" ], this.path);
if (exitCode !== 0) {
throw new Error("Failed to pull, please check the terminal output for more information.");
}
// If the stack is not running, we don't need to restart it
await this.updateStatus();
log.debug("update", "Status: " + this.status);
if (this.status !== RUNNING) {
return exitCode;
}
exitCode = await Terminal.exec(this.server, socket, terminalName, "docker", [ "compose", "up", "-d", "--remove-orphans" ], this.path);
if (exitCode !== 0) {
throw new Error("Failed to restart, please check the terminal output for more information.");
@ -314,12 +423,21 @@ export class Stack {
async joinCombinedTerminal(socket: DockgeSocket) {
const terminalName = getCombinedTerminalName(this.name);
const terminal = Terminal.getOrCreateTerminal(this.server, terminalName, "docker", [ "compose", "logs", "-f", "--tail", "100" ], this.path);
terminal.enableKeepAlive = true;
terminal.rows = COMBINED_TERMINAL_ROWS;
terminal.cols = COMBINED_TERMINAL_COLS;
terminal.join(socket);
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) {
const terminalName = getContainerExecTerminalName(this.name, serviceName, index);
let terminal = Terminal.getTerminal(terminalName);
@ -337,20 +455,35 @@ export class Stack {
async getServiceStatusList() {
let statusList = new Map<string, number>();
let res = childProcess.execSync("docker compose ps --format json", {
cwd: this.path,
});
try {
let res = await childProcessAsync.spawn("docker", [ "compose", "ps", "--format", "json" ], {
cwd: this.path,
encoding: "utf-8",
});
let lines = res.toString().split("\n");
for (let line of lines) {
try {
let obj = JSON.parse(line);
statusList.set(obj.Service, obj.State);
} catch (e) {
if (!res.stdout) {
return statusList;
}
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 {
allowedCommandList, allowedRawKeys,
getComposeTerminalName,
getCryptoRandomInt,
PROGRESS_TERMINAL_ROWS,
TERMINAL_COLS,
TERMINAL_ROWS
@ -34,6 +32,9 @@ export class Terminal {
protected _rows : number = TERMINAL_ROWS;
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) {
this.server = server;
this._name = name;
@ -54,7 +55,9 @@ export class Terminal {
try {
this.ptyProcess?.resize(this.cols, this.rows);
} catch (e) {
log.debug("Terminal", "Failed to resize terminal: " + e.message);
if (e instanceof Error) {
log.debug("Terminal", "Failed to resize terminal: " + e.message);
}
}
}
@ -67,7 +70,9 @@ export class Terminal {
try {
this.ptyProcess?.resize(this.cols, this.rows);
} catch (e) {
log.debug("Terminal", "Failed to resize terminal: " + e.message);
if (e instanceof Error) {
log.debug("Terminal", "Failed to resize terminal: " + e.message);
}
}
}
@ -76,37 +81,76 @@ export class Terminal {
return;
}
this._ptyProcess = pty.spawn(this.file, this.args, {
name: this.name,
cwd: this.cwd,
cols: TERMINAL_COLS,
rows: this.rows,
});
if (this.enableKeepAlive) {
log.debug("Terminal", "Keep alive enabled for terminal " + this.name);
// On Data
this._ptyProcess.onData((data) => {
this.buffer.push(data);
if (this.server.io) {
this.server.io.to(this.name).emit("terminalWrite", this.name, data);
// 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 {
this._ptyProcess = pty.spawn(this.file, this.args, {
name: this.name,
cwd: this.cwd,
cols: TERMINAL_COLS,
rows: this.rows,
});
// On Data
this._ptyProcess.onData((data) => {
this.buffer.pushItem(data);
if (this.server.io) {
this.server.io.to(this.name).emit("terminalWrite", this.name, data);
}
});
// On Exit
this._ptyProcess.onExit(this.exit);
} catch (error) {
if (error instanceof Error) {
clearInterval(this.keepAliveInterval);
log.error("Terminal", "Failed to start terminal: " + error.message);
const exitCode = Number(error.message.split(" ").pop());
this.exit({
exitCode,
});
}
});
// On Exit
this._ptyProcess.onExit((res) => {
this.server.io.to(this.name).emit("terminalExit", this.name, res.exitCode);
// Remove room
this.server.io.in(this.name).socketsLeave(this.name);
Terminal.terminalMap.delete(this.name);
log.debug("Terminal", "Terminal " + this.name + " exited with code " + res.exitCode);
if (this.callback) {
this.callback(res.exitCode);
}
});
}
}
/**
* Exit event handler
* @param res
*/
protected exit = (res : {exitCode: number, signal?: number | undefined}) => {
this.server.io.to(this.name).emit("terminalExit", this.name, res.exitCode);
// Remove room
this.server.io.in(this.name).socketsLeave(this.name);
Terminal.terminalMap.delete(this.name);
log.debug("Terminal", "Terminal " + this.name + " exited with code " + res.exitCode);
clearInterval(this.keepAliveInterval);
if (this.callback) {
this.callback(res.exitCode);
}
};
public onExit(callback : (exitCode : number) => void) {
this.callback = callback;
}
@ -138,7 +182,9 @@ export class Terminal {
}
close() {
this._ptyProcess?.kill();
clearInterval(this.keepAliveInterval);
// Send Ctrl+C to the terminal
this.ptyProcess?.write("\x03");
}
/**
@ -159,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> {
const terminal = new Terminal(server, terminalName, file, args, cwd);
terminal.rows = PROGRESS_TERMINAL_ROWS;
return new Promise((resolve, reject) => {
// check if terminal exists
if (Terminal.terminalMap.has(terminalName)) {
reject("Another operation is already running, please try again later.");
return;
}
if (socket) {
terminal.join(socket);
}
let terminal = new Terminal(server, terminalName, file, args, cwd);
terminal.rows = PROGRESS_TERMINAL_ROWS;
if (socket) {
terminal.join(socket);
}
return new Promise((resolve) => {
terminal.onExit((exitCode : number) => {
resolve(exitCode);
});
terminal.start();
});
}
public static getTerminalCount() {
return Terminal.terminalMap.size;
}
}
/**

View file

@ -12,6 +12,11 @@ dayjs.extend(utc);
dayjs.extend(timezone);
dayjs.extend(relativeTime);
export interface LooseObject {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
[key: string]: any
}
let randomBytes : (numBytes: number) => Uint8Array;
initRandomBytes();
@ -198,7 +203,7 @@ export function getContainerTerminalName(container : string) {
}
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) {

View file

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

View file

@ -4,14 +4,14 @@
*/
export class LimitQueue<T> extends Array<T> {
__limit;
__onExceed = null;
__onExceed? : (item : T | undefined) => void;
constructor(limit: number) {
super();
this.__limit = limit;
}
push(value : T) {
pushItem(value : T) {
super.push(value);
if (this.length > this.__limit) {
const item = this.shift();

View file

@ -7,14 +7,16 @@ services:
# 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
# Docker Socket
- /var/run/docker.sock:/var/run/docker.sock
# Dockge Config
- ./data:/app/data
# Your stacks directory in the host (The paths inside container must be the same as the host)
# Stacks Directory
# ⚠️ READ IT CAREFULLY. If you did it wrong, your data could end up writing into a WRONG PATH.
# ⚠️ 1. FULL path only. No relative path (MUST)
# ⚠️ 2. Left Stacks Path === Right Stacks Path (MUST)
- /opt/stacks:/opt/stacks
environment:
# 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 PATH="$PNPM_HOME:$PATH"
# COPY --from=docker:dind /usr/local/bin/docker /usr/local/bin/
RUN apt update && apt install --yes --no-install-recommends \
curl \
ca-certificates \
@ -24,16 +22,3 @@ RUN apt update && apt install --yes --no-install-recommends \
&& rm -rf /var/lib/apt/lists/* \
&& npm install pnpm -g \
&& 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
############################################
@ -12,16 +17,17 @@ RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --prod --frozen-l
############################################
FROM louislam/dockge:base AS release
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 --chown=node:node . .
RUN mkdir ./data
VOLUME /app/data
EXPOSE 5001
HEALTHCHECK --interval=60s --timeout=30s --start-period=60s --retries=5 CMD extra/healthcheck
ENTRYPOINT ["/usr/bin/dumb-init", "--"]
CMD ["tsx", "./backend/index.ts"]
############################################
# Mark as Nightly
############################################

View file

@ -0,0 +1,57 @@
import github from "@actions/github";
(async () => {
try {
const token = process.argv[2];
const issueNumber = process.argv[3];
const username = process.argv[4];
const client = github.getOctokit(token).rest;
const issue = {
owner: "louislam",
repo: "dockge",
number: issueNumber,
};
const labels = (
await client.issues.listLabelsOnIssue({
owner: issue.owner,
repo: issue.repo,
issue_number: issue.number
})
).data.map(({ name }) => name);
if (labels.length === 0) {
console.log("Bad format here");
await client.issues.addLabels({
owner: issue.owner,
repo: issue.repo,
issue_number: issue.number,
labels: [ "invalid-format" ]
});
// Add the issue closing comment
await client.issues.createComment({
owner: issue.owner,
repo: issue.repo,
issue_number: issue.number,
body: `@${username}: Hello! :wave:\n\nThis issue is being automatically closed because it does not follow the issue template. Please DO NOT open a blank issue.`
});
// Close the issue
await client.issues.update({
owner: issue.owner,
repo: issue.repo,
issue_number: issue.number,
state: "closed"
});
} else {
console.log("Pass!");
}
} catch (e) {
console.log(e);
}
})();

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

View file

@ -0,0 +1,42 @@
// Generate on GitHub
const input = `
* Add Korean translation by @Alanimdeo in https://github.com/louislam/dockge/pull/86
`;
const template = `
### 🆕 New Features
### Improvements
### 🐞 Bug Fixes
### 🦎 Translation Contributions
### Others
- Other small changes, code refactoring and comment/doc updates in this repo:
`;
const lines = input.split("\n").filter((line) => line.trim() !== "");
for (const line of lines) {
// Split the last " by "
const usernamePullRequesURL = line.split(" by ").pop();
if (!usernamePullRequesURL) {
console.log("Unable to parse", line);
continue;
}
const [ username, pullRequestURL ] = usernamePullRequesURL.split(" in ");
const pullRequestID = "#" + pullRequestURL.split("/").pop();
let message = line.split(" by ").shift();
if (!message) {
console.log("Unable to parse", line);
continue;
}
message = message.split("* ").pop();
console.log("-", pullRequestID, message, `(Thanks ${username})`);
}
console.log(template);

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

@ -11,6 +11,8 @@ declare module 'vue' {
Appearance: typeof import('./src/components/settings/Appearance.vue')['default']
ArrayInput: typeof import('./src/components/ArrayInput.vue')['default']
ArraySelect: typeof import('./src/components/ArraySelect.vue')['default']
BDropdown: typeof import('bootstrap-vue-next')['BDropdown']
BDropdownItem: typeof import('bootstrap-vue-next')['BDropdownItem']
BModal: typeof import('bootstrap-vue-next')['BModal']
Confirm: typeof import('./src/components/Confirm.vue')['default']
Container: typeof import('./src/components/Container.vue')['default']

View file

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

View file

@ -5,7 +5,7 @@
<li v-for="(value, index) in array" :key="index" class="list-group-item">
<select v-model="array[index]" class="no-bg domain-input">
<option value="">Select a network...</option>
<option v-for="option in options" :value="option">{{ option }}</option>
<option v-for="option in options" :key="option" :value="option">{{ option }}</option>
</select>
<font-awesome-icon icon="times" class="action remove ms-2 me-3 text-danger" @click="remove(index)" />
@ -49,8 +49,7 @@ export default {
array() {
// Create the array if not exists, it should be safe.
if (!this.service[this.name]) {
// eslint-disable-next-line vue/no-side-effects-in-computed-properties
this.service[this.name] = [];
return [];
}
return this.service[this.name];
},
@ -89,6 +88,10 @@ export default {
},
methods: {
addField() {
// Create the array if not exists.
if (!this.service[this.name]) {
this.service[this.name] = [];
}
this.array.push("");
},
remove(index) {

View file

@ -9,7 +9,7 @@
<div v-if="!isEditMode">
<span class="badge me-1" :class="bgStyle">{{ status }}</span>
<a v-for="port in service.ports" :href="parsePort(port).url" target="_blank">
<a v-for="port in service.ports" :key="port" :href="parsePort(port).url" target="_blank">
<span class="badge me-1 bg-secondary">{{ parsePort(port).display }}</span>
</a>
</div>
@ -27,7 +27,7 @@
<div v-if="isEditMode" class="mt-2">
<button class="btn btn-normal me-2" @click="showConfig = !showConfig">
<font-awesome-icon icon="edit" />
Edit
{{ $t("Edit") }}
</button>
<button v-if="false" class="btn btn-normal me-2">Rename</button>
<button class="btn btn-danger me-2" @click="remove">
@ -179,8 +179,10 @@ export default defineComponent({
},
bgStyle() {
if (this.status === "running") {
if (this.status === "running" || this.status === "healthy") {
return "bg-primary";
} else if (this.status === "unhealthy") {
return "bg-danger";
} else {
return "bg-secondary";
}

View file

@ -1,6 +1,6 @@
<template>
<div>
<h5>Internal Networks</h5>
<h5>{{ $t("Internal Networks") }}</h5>
<ul class="list-group">
<li v-for="(networkRow, index) in networkList" :key="index" class="list-group-item">
<input v-model="networkRow.key" type="text" class="no-bg domain-input" placeholder="Network name..." />
@ -10,10 +10,10 @@
<button class="btn btn-normal btn-sm mt-3 me-2" @click="addField">{{ $t("addInternalNetwork") }}</button>
<h5 class="mt-3">External Networks</h5>
<h5 class="mt-3">{{ $t("External Networks") }}</h5>
<div v-if="externalNetworkList.length === 0">
No External Networks
{{ $t("No External Networks") }}
</div>
<div v-for="(networkName, index) in externalNetworkList" :key="networkName" class="form-check form-switch my-3">
@ -32,7 +32,7 @@
class="form-control"
@keyup.enter="createExternelNetwork"
/>
<button class="btn btn-normal btn-sm me-2" type="button" @click="">
<button class="btn btn-normal btn-sm me-2" type="button">
{{ $t("createExternalNetwork") }}
</button>
</div>

View file

@ -152,6 +152,14 @@ export default {
});
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 (m2.status === RUNNING) {
return 1;

View file

@ -19,7 +19,6 @@ export default {
computed: {
uptime() {
return "0.00%";
return this.$t("notAvailableShort");
},
@ -46,9 +45,12 @@ export default {
<style scoped>
.badge {
min-width: 62px;
}
.fixed-width {
width: 62px;
overflow: hidden;
text-overflow: ellipsis;
}
</style>

View file

@ -47,10 +47,10 @@
<input
v-model="settings.primaryHostname"
class="form-control"
placeholder="localhost"
placeholder="(Unset: Follow current hostname)"
/>
<button class="btn btn-outline-primary" type="button" @click="autoGetPrimaryHostname">
{{ $t("Auto Get") }}
{{ $t("autoGet") }}
</button>
</div>
@ -68,13 +68,13 @@
</template>
<script>
import HiddenInput from "../../components/HiddenInput.vue";
import dayjs from "dayjs";
import { timezoneList } from "../../util-frontend";
export default {
components: {
HiddenInput,
},
data() {

View file

@ -3,7 +3,30 @@ import { createI18n } from "vue-i18n/dist/vue-i18n.esm-browser.prod.js";
import en from "./lang/en.json";
const languageList = {
"bg-BG": "Български",
"es": "Español",
"de": "Deutsch",
"fr": "Français",
"pl-PL": "Polski",
"pt": "Português",
"pt-BR": "Português-Brasil",
"sl": "Slovenščina",
"tr": "Türkçe",
"zh-CN": "简体中文",
"zh-TW": "繁體中文(台灣)",
"ur": "Urdu",
"ko-KR": "한국어",
"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 = {
@ -16,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
|| languageList[navigator.language] && navigator.language

View file

@ -0,0 +1,19 @@
# Translations
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
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)
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.

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,94 @@
{
"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" : "N/A",
"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": "Не са налични външни мрежи"
}

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

102
frontend/src/lang/de.json Normal file
View file

@ -0,0 +1,102 @@
{
"languageName": "Deutsch",
"Create your admin account": "Erstelle dein Admin-Konto",
"authIncorrectCreds": "Falscher Benutzername oder falsches Passwort.",
"PasswordsDoNotMatch": "Passwörter stimmen nicht überein.",
"Repeat Password": "Passwort wiederholen",
"Create": "Erstellen",
"signedInDisp": "Angemeldet als {0}",
"signedInDispDisabled": "Anmeldung deaktiviert.",
"home": "Startseite",
"console": "Konsole",
"registry": "Container Registry",
"compose": "",
"addFirstStackMsg": "Stelle deinen ersten Stack zusammen!",
"stackName": "Stack-Name",
"deployStack": "Deployen",
"deleteStack": "Löschen",
"stopStack": "Anhalten",
"restartStack": "Neustarten",
"updateStack": "Aktualisieren",
"startStack": "Starten",
"editStack": "Bearbeiten",
"discardStack": "Verwerfen",
"saveStackDraft": "Speichern",
"notAvailableShort": "N/V",
"deleteStackMsg": "Möchtest du diesen Stack wirklich löschen?",
"stackNotManagedByDockgeMsg": "Dieser Stack wird nicht von Dockge verwaltet.",
"primaryHostname": "Primärer Hostname",
"general": "Allgemein",
"container": "Container",
"scanFolder": "Stacks-Ordner durchsuchen",
"dockerImage": "Image",
"restartPolicyUnlessStopped": "Falls nicht gestoppt",
"restartPolicyAlways": "Immer",
"restartPolicyOnFailure": "Bei Fehler",
"restartPolicyNo": "Kein Neustart",
"environmentVariable": "Umgebungsvariable/n",
"restartPolicy": "Neustart Richtlinie",
"containerName": "Container-Name",
"port": "Port / Ports",
"volume": "Volume / Volumes",
"network": "Netzwerk | Netzwerke",
"dependsOn": "Container-Abhängigkeit/en",
"addListItem": "{0} hinzufügen",
"deleteContainer": "Löschen",
"addContainer": "Container hinzufügen",
"addNetwork": "Netzwerk hinzufügen",
"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.",
"passwordNotMatchMsg": "Das wiederholte Passwort stimmt nicht überein.",
"autoGet": "Automatisch laden",
"add": "Hinzufügen",
"Edit": "Bearbeiten",
"applyToYAML": "Auf YAML anwenden",
"createExternalNetwork": "Erstellen",
"addInternalNetwork": "Hinzufügen",
"Save": "Speichern",
"Language": "Sprache",
"Current User": "Aktueller Benutzer",
"Change Password": "Passwort ändern",
"Current Password": "Aktuelles Passwort",
"New Password": "Neues Passwort",
"Repeat New Password": "Neues Passwort wiederholen",
"Update Password": "Passwort aktualisieren",
"Advanced": "Erweitert",
"Please use this option carefully!": "Bitte verwende diese Option sorgfältig!",
"Enable Auth": "Anmeldung aktivieren",
"Disable Auth": "Anmeldung deaktivieren",
"I understand, please disable": "Ich verstehe, bitte deaktivieren",
"Leave": "Verlassen",
"Frontend Version": "Frontend Version",
"Check Update On GitHub": "Update auf GitHub überprüfen",
"Show update if available": "Update anzeigen, wenn verfügbar",
"Also check beta release": "Auch Beta-Version überprüfen",
"Remember me": "Anmeldung beibehalten",
"Login": "Anmelden",
"Username": "Benutzername",
"Password": "Passwort",
"Settings": "Einstellungen",
"Logout": "Abmelden",
"Lowercase only": "Nur Kleinbuchstaben",
"Convert to Compose": "In Compose Syntax umwandeln",
"Docker Run": "Docker ausführen",
"active": "aktiv",
"exited": "beendet",
"inactive": "inaktiv",
"Appearance": "Erscheinungsbild",
"Security": "Sicherheit",
"About": "Über",
"Allowed commands:": "Zugelassene Befehle:",
"Internal Networks": "Interne Netzwerke",
"External Networks": "Externe 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

@ -1,7 +1,10 @@
{
"languageName": "English",
"Create your admin account": "Create your admin account",
"authIncorrectCreds": "Incorrect username or password.",
"PasswordsDoNotMatch": "Passwords do not match.",
"Repeat Password": "Repeat Password",
"Create": "Create",
"signedInDisp": "Signed in as {0}",
"signedInDispDisabled": "Auth Disabled.",
"home": "Home",
@ -9,17 +12,18 @@
"registry": "Registry",
"compose": "Compose",
"addFirstStackMsg": "Compose your first stack!",
"stackName" : "Stack Name",
"stackName": "Stack Name",
"deployStack": "Deploy",
"deleteStack": "Delete",
"stopStack": "Stop",
"restartStack": "Restart",
"updateStack": "Update",
"startStack": "Start",
"downStack": "Stop & Down",
"editStack": "Edit",
"discardStack": "Discard",
"saveStackDraft": "Save",
"notAvailableShort" : "N/A",
"notAvailableShort": "N/A",
"deleteStackMsg": "Are you sure you want to delete this stack?",
"stackNotManagedByDockgeMsg": "This stack is not managed by Dockge.",
"primaryHostname": "Primary Hostname",
@ -43,11 +47,56 @@
"addContainer": "Add Container",
"addNetwork": "Add Network",
"disableauth.message1": "Are you sure want to <strong>disable authentication</strong>?",
"disableauth.message2": "It is designed for scenarios <strong>where you intend to implement third-party authentication</strong> in front of Uptime Kuma such as Cloudflare Access, Authelia or other authentication mechanisms.",
"disableauth.message2": "It is designed for scenarios <strong>where you intend to implement third-party authentication</strong> in front of Dockge such as Cloudflare Access, Authelia or other authentication mechanisms.",
"passwordNotMatchMsg": "The repeat password does not match.",
"autoGet": "Auto Get",
"add": "Add",
"Edit": "Edit",
"applyToYAML": "Apply to YAML",
"createExternalNetwork": "Create",
"addInternalNetwork": "Add"
"addInternalNetwork": "Add",
"Save": "Save",
"Language": "Language",
"Current User": "Current User",
"Change Password": "Change Password",
"Current Password": "Current Password",
"New Password": "New Password",
"Repeat New Password": "Repeat New Password",
"Update Password": "Update Password",
"Advanced": "Advanced",
"Please use this option carefully!": "Please use this option carefully!",
"Enable Auth": "Enable Auth",
"Disable Auth": "Disable Auth",
"I understand, please disable": "I understand, please disable",
"Leave": "Leave",
"Frontend Version": "Frontend Version",
"Check Update On GitHub": "Check Update On GitHub",
"Show update if available": "Show update if available",
"Also check beta release": "Also check beta release",
"Remember me": "Remember me",
"Login": "Login",
"Username": "Username",
"Password": "Password",
"Settings": "Settings",
"Logout": "Logout",
"Lowercase only": "Lowercase only",
"Convert to Compose": "Convert to Compose",
"Docker Run": "Docker Run",
"active": "active",
"exited": "exited",
"inactive": "inactive",
"Appearance": "Appearance",
"Security": "Security",
"About": "About",
"Allowed commands:": "Allowed commands:",
"Internal Networks": "Internal Networks",
"External Networks": "External Networks",
"No External Networks": "No External Networks",
"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"
}

94
frontend/src/lang/es.json Normal file
View file

@ -0,0 +1,94 @@
{
"languageName": "Español",
"Create your admin account": "Crea tu cuenta de administrador",
"authIncorrectCreds": "Nombre de usuario o contraseña incorrectos.",
"PasswordsDoNotMatch": "Las contraseñas no coinciden.",
"Repeat Password": "Repetir Contraseña",
"Create": "Crear",
"signedInDisp": "Sesión iniciada como {0}",
"signedInDispDisabled": "Autenticación deshabilitada.",
"home": "Inicio",
"console": "Consola",
"registry": "Registro",
"compose": "Componer",
"addFirstStackMsg": "¡Compón tu primera pila!",
"stackName" : "Nombre de la Pila",
"deployStack": "Desplegar",
"deleteStack": "Eliminar",
"stopStack": "Detener",
"restartStack": "Reiniciar",
"updateStack": "Actualizar",
"startStack": "Iniciar",
"editStack": "Editar",
"discardStack": "Descartar",
"saveStackDraft": "Guardar",
"notAvailableShort" : "N/D",
"deleteStackMsg": "¿Estás seguro de que quieres eliminar esta pila?",
"stackNotManagedByDockgeMsg": "Esta pila no está gestionada por Dockge.",
"primaryHostname": "Nombre de Host Primario",
"general": "General",
"container": "Contenedor | Contenedores",
"scanFolder": "Escanear Carpeta de Pilas",
"dockerImage": "Imagen",
"restartPolicyUnlessStopped": "A menos que se detenga",
"restartPolicyAlways": "Siempre",
"restartPolicyOnFailure": "En caso de fallo",
"restartPolicyNo": "No",
"environmentVariable": "Variable de Entorno | Variables de Entorno",
"restartPolicy": "Política de Reinicio",
"containerName": "Nombre del Contenedor",
"port": "Puerto | Puertos",
"volume": "Volumen | Volúmenes",
"network": "Red | Redes",
"dependsOn": "Dependencia del Contenedor | Dependencias del Contenedor",
"addListItem": "Agregar {0}",
"deleteContainer": "Eliminar",
"addContainer": "Agregar Contenedor",
"addNetwork": "Agregar Red",
"disableauth.message1": "¿Estás seguro de que deseas <strong>desactivar la autenticación</strong>?",
"disableauth.message2": "Está diseñado para escenarios <strong>donde pretendes implementar autenticación de terceros</strong> frente a Dockge, como Cloudflare Access, Authelia u otros mecanismos de autenticación.",
"passwordNotMatchMsg": "La contraseña repetida no coincide.",
"autoGet": "Obtener Automáticamente",
"add": "Agregar",
"Edit": "Editar",
"applyToYAML": "Aplicar a YAML",
"createExternalNetwork": "Crear",
"addInternalNetwork": "Agregar",
"Save": "Guardar",
"Language": "Idioma",
"Current User": "Usuario Actual",
"Change Password": "Cambiar Contraseña",
"Current Password": "Contraseña Actual",
"New Password": "Nueva Contraseña",
"Repeat New Password": "Repetir Nueva Contraseña",
"Update Password": "Actualizar Contraseña",
"Advanced": "Avanzado",
"Please use this option carefully!": "¡Por favor, usa esta opción con cuidado!",
"Enable Auth": "Habilitar Autenticación",
"Disable Auth": "Deshabilitar Autenticación",
"I understand, please disable": "Entiendo, por favor deshabilitar",
"Leave": "Salir",
"Frontend Version": "Versión del Frontend",
"Check Update On GitHub": "Comprobar Actualización en GitHub",
"Show update if available": "Mostrar actualización si está disponible",
"Also check beta release": "También verificar la versión beta",
"Remember me": "Recuérdame",
"Login": "Iniciar Sesión",
"Username": "Nombre de Usuario",
"Password": "Contraseña",
"Settings": "Configuración",
"Logout": "Cerrar Sesión",
"Lowercase only": "Solo minúsculas",
"Convert to Compose": "Convertir a Compose",
"Docker Run": "Ejecutar Docker",
"active": "activo",
"exited": "finalizado",
"inactive": "inactivo",
"Appearance": "Apariencia",
"Security": "Seguridad",
"About": "Acerca de",
"Allowed commands:": "Comandos permitidos:",
"Internal Networks": "Redes Internas",
"External Networks": "Redes Externas",
"No External Networks": "Sin Redes Externas"
}

102
frontend/src/lang/fr.json Normal file
View file

@ -0,0 +1,102 @@
{
"languageName": "Français",
"Create your admin account": "Créez votre compte administrateur",
"authIncorrectCreds": "identifiant ou mot de passe incorrect.",
"Repeat Password": "Répéter le mot de passe",
"PasswordsDoNotMatch": "Les mots de passe ne correspondent pas.",
"Create": "Créer",
"signedInDisp": "Connecté en tant que {0}",
"signedInDispDisabled": "Authentification désactivée.",
"home": "Accueil",
"console": "Console",
"registry": "Registre",
"compose": "Compose",
"addFirstStackMsg": "Créez votre première pile !",
"stackName": "Nom de la pile",
"deployStack": "Déployer",
"deleteStack": "Supprimer",
"stopStack": "Arrêter",
"restartStack": "Redémarrer",
"updateStack": "Mettre à jour",
"startStack": "Démarrer",
"editStack": "Modifier",
"discardStack": "Ignorer",
"saveStackDraft": "Sauvegarder",
"notAvailableShort": "N/A",
"deleteStackMsg": "Êtes-vous sûr de vouloir supprimer cette pile ?",
"stackNotManagedByDockgeMsg": "Cette pile n'est pas gérée par Dockge.",
"primaryHostname": "Nom d'hôte principal",
"general": "Général",
"container": "Conteneur | Conteneurs",
"scanFolder": "Analyser le dossier des piles",
"dockerImage": "Image",
"restartPolicyUnlessStopped": "Sauf arrêt",
"restartPolicyAlways": "Toujours",
"restartPolicyOnFailure": "En cas d'échec",
"restartPolicyNo": "Non",
"environmentVariable": "Variable d'environnement | Variables d'environnement",
"restartPolicy": "Politique de redémarrage",
"containerName": "Nom du conteneur",
"port": "Port | Ports",
"volume": "Volume | Volumes",
"network": "Réseau | Réseaux",
"dependsOn": "Dépendance du conteneur | Dépendances du conteneur",
"addListItem": "Ajouter {0}",
"deleteContainer": "Supprimer",
"addContainer": "Ajouter un conteneur",
"addNetwork": "Ajouter un réseau",
"disableauth.message1": "Voulez-vous vraiment <strong>désactiver l'authentification</strong> ?",
"disableauth.message2": "Il est conçu pour les scénarios <strong>dans lesquels vous avez l'intention d'implémenter une authentification tierce</strong> devant Dockge, comme Cloudflare Access, Authelia ou d'autres mécanismes d'authentification.",
"passwordNotMatchMsg": "Le mot de passe de confirmation ne correspond pas.",
"autoGet": "Obtention automatique",
"add": "Ajouter",
"Edit": "Modifier",
"applyToYAML": "Appliquer au YAML",
"createExternalNetwork": "Créer",
"addInternalNetwork": "Ajouter",
"Save": "Enregistrer",
"Language": "Langue",
"Current User": "Utilisateur Actuel",
"Change Password": "Changer le Mot de Passe",
"Current Password": "Mot de passe actuel",
"New Password": "Nouveau Mot de Passe",
"Repeat New Password": "Répéter le Nouveau Mot de Passe",
"Update Password": "Mettre à Jour le Mot de Passe",
"Advanced": "Avancé",
"Please use this option carefully!": "Veuillez utiliser cette option avec précaution !",
"Enable Auth": "Activer l'Authentification",
"Disable Auth": "Désactiver l'Authentification",
"I understand, please disable": "Je comprends, veuillez désactiver",
"Leave": "Quitter",
"Frontend Version": "Version Frontend",
"Check Update On GitHub": "Vérifier la Mise à Jour sur GitHub",
"Show update if available": "Afficher la mise à jour si disponible",
"Also check beta release": "Vérifier également la version bêta",
"Remember me": "Se souvenir de moi",
"Login": "Connexion",
"Username": "Nom d'utilisateur",
"Password": "Mot de Passe",
"Settings": "Paramètres",
"Logout": "Déconnexion",
"Lowercase only": "Minuscules uniquement",
"Convert to Compose": "Convertir en Compose",
"Docker Run": "Exécution Docker",
"active": "actif",
"exited": "arrêté",
"inactive": "inactif",
"Appearance": "Apparence",
"Security": "Sécurité",
"About": "À propos",
"Allowed commands:": "Commandes autorisées :",
"Internal Networks": "Réseaux Internes",
"External Networks": "Réseaux Externes",
"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

@ -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": "시작",
"editStack": "수정",
"discardStack": "취소",
"saveStackDraft": "저장",
"notAvailableShort": "N/A",
"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": "이 기능은 Dockge 앞에 Cloudflare Access, Authelia 등과 같은 <strong>서드 파티 인증을 사용하려는 경우</strong>에 사용하기 위해서 만들어졌어요.",
"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 Run",
"active": "활성",
"exited": "종료됨",
"inactive": "비활성",
"Appearance": "디스플레이",
"Security": "보안",
"About": "정보",
"Allowed commands:": "허용된 명령어:",
"Internal Networks": "내부 네트워크",
"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": "Polski",
"Create your admin account": "Utwórz konto administratora",
"authIncorrectCreds": "Nieprawidłowa nazwa użytkownika lub hasło.",
"PasswordsDoNotMatch": "Hasła nie pasują do siebie.",
"Repeat Password": "Powtórz hasło",
"Create": "Utwórz",
"signedInDisp": "Zalogowany jako {0}",
"signedInDispDisabled": "Autoryzacja wyłączona.",
"home": "Strona główna",
"console": "Konsola",
"registry": "Rejestr",
"compose": "Stwórz",
"addFirstStackMsg": "Stwórz swój pierwszy stos!",
"stackName" : "Nazwa stosu",
"deployStack": "Wdroż",
"deleteStack": "Usuń",
"stopStack": "Zatrzymaj",
"restartStack": "Uruchom ponownie",
"updateStack": "Aktualizuj",
"startStack": "Uruchom",
"editStack": "Edytuj",
"discardStack": "Odrzuć",
"saveStackDraft": "Zapisz",
"notAvailableShort" : "N/A",
"deleteStackMsg": "Czy na pewno chcesz usunąć ten stos?",
"stackNotManagedByDockgeMsg": "Ten stos nie jest zarządzany przez Dockge.",
"primaryHostname": "Podstawowa nazwa hosta",
"general": "Ogólne",
"container": "Kontener | Kontenery",
"scanFolder": "Skanuj folder ze stosami",
"dockerImage": "Obraz",
"restartPolicyUnlessStopped": "Jeśli nie zatrzymano",
"restartPolicyAlways": "Zawsze",
"restartPolicyOnFailure": "Po awarii",
"restartPolicyNo": "Nie restartuj",
"environmentVariable": "Zmienna środowiskowa | Zmienne środowiskowe",
"restartPolicy": "Polityka restartu",
"containerName": "Nazwa kontenera",
"port": "Port | Porty",
"volume": "Wolumin | Woluminy",
"network": "Sieć | Sieci",
"dependsOn": "Zależność kontenera | Zależności kontenera",
"addListItem": "Dodaj {0}",
"deleteContainer": "Usuń kontener",
"addContainer": "Dodaj kontener",
"addNetwork": "Dodaj sieć",
"disableauth.message1": "Czy na pewno chcesz <strong>wyłączyć uwierzytelnianie</strong>?",
"disableauth.message2": "Przeznaczone dla sytuacji, <strong>w których zamierzasz zaimplementować zewnętrzne mechanizmy uwierzytelniania</strong> przed Dockge, takie jak Cloudflare Access, Authelia lub inne.",
"passwordNotMatchMsg": "Hasła się nie zgadzają.",
"autoGet": "Automatyczne pobieranie",
"add": "Dodaj",
"Edit": "Edytuj",
"applyToYAML": "Zastosuj do YAML",
"createExternalNetwork": "Utwórz",
"addInternalNetwork": "Dodaj",
"Save": "Zapisz",
"Language": "Język",
"Current User": "Aktualny użytkownik",
"Change Password": "Zmień hasło",
"Current Password": "Aktualne hasło",
"New Password": "Nowe hasło",
"Repeat New Password": "Powtórz nowe hasło",
"Update Password": "Aktualizuj hasło",
"Advanced": "Zaawansowane",
"Please use this option carefully!": "Proszę używać tej opcji ostrożnie!",
"Enable Auth": "Włącz autoryzację",
"Disable Auth": "Wyłącz autoryzację",
"I understand, please disable": "Rozumiem, proszę wyłączyć",
"Leave": "Wyjdź",
"Frontend Version": "Wersja interfejsu graficznego",
"Check Update On GitHub": "Sprawdź dostępność aktualizacji na GitHub",
"Show update if available": "Pokaż aktualizacje, jeśli są dostępne",
"Also check beta release": "Sprawdź także wersje beta",
"Remember me": "Zapamiętaj mnie",
"Login": "Zaloguj się",
"Username": "Nazwa użytkownika",
"Password": "Hasło",
"Settings": "Ustawienia",
"Logout": "Wyloguj się",
"Lowercase only": "Tylko małe litery",
"Convert to Compose": "Przekształć na składnię Compose",
"Docker Run": "Uruchom za pomocą Dockera",
"active": "aktywny",
"exited": "wyłączony",
"inactive": "nieaktywny",
"Appearance": "Wygląd",
"Security": "Bezpieczeństwo",
"About": "O programie",
"Allowed commands:": "Dozwolone polecenia:",
"Internal Networks": "Sieci wewnętrzne",
"External Networks": "Sieci zewnętrzne",
"No External Networks": "Brak sieci zewnętrznych"
}

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

94
frontend/src/lang/pt.json Normal file
View file

@ -0,0 +1,94 @@
{
"languageName": "Português",
"Create your admin account": "Crie sua conta de administrador",
"authIncorrectCreds": "Nome de usuário ou senha incorretos.",
"PasswordsDoNotMatch": "As senhas não coincidem.",
"Repeat Password": "Repetir Senha",
"Create": "Criar",
"signedInDisp": "Logado como {0}",
"signedInDispDisabled": "Autenticação desativada.",
"home": "Início",
"console": "Console",
"registry": "Registro",
"compose": "Compor",
"addFirstStackMsg": "Componha sua primeira pilha!",
"stackName" : "Nome da Pilha",
"deployStack": "Implantar",
"deleteStack": "Excluir",
"stopStack": "Parar",
"restartStack": "Reiniciar",
"updateStack": "Atualizar",
"startStack": "Iniciar",
"editStack": "Editar",
"discardStack": "Descartar",
"saveStackDraft": "Salvar",
"notAvailableShort" : "N/D",
"deleteStackMsg": "Tem certeza de que deseja excluir esta pilha?",
"stackNotManagedByDockgeMsg": "Esta pilha não é gerenciada pelo Dockge.",
"primaryHostname": "Nome do Host Primário",
"general": "Geral",
"container": "Contêiner | Contêineres",
"scanFolder": "Digitalizar Pasta de Pilhas",
"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 de que deseja <strong>desativar a autenticação</strong>?",
"disableauth.message2": "Isso é projetado para cenários <strong>onde você pretende implementar autenticação de terceiros</strong> no Dockge, como Cloudflare Access, Authelia ou outros mecanismos de autenticação.",
"passwordNotMatchMsg": "A senha repetida não coincide.",
"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 cuidado!",
"Enable Auth": "Habilitar Autenticação",
"Disable Auth": "Desabilitar Autenticação",
"I understand, please disable": "Entendo, 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."
}

94
frontend/src/lang/ru.json Normal file
View file

@ -0,0 +1,94 @@
{
"languageName": "Русский",
"Create your admin account": "Создайте учетку администратора",
"authIncorrectCreds": "Неверный логин или пароль.",
"PasswordsDoNotMatch": "Пароль не совпадает.",
"Repeat Password": "Повторите пароль",
"Create": "Создать",
"signedInDisp": "Авторизован как",
"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": "Auto Get",
"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": "Нет внешних сетей"
}

94
frontend/src/lang/sl.json Normal file
View file

@ -0,0 +1,94 @@
{
"languageName": "Slovenščina",
"Create your admin account": "Ustvarite svoj skrbniški račun",
"authIncorrectCreds": "Napačno uporabniško ime ali geslo.",
"PasswordsDoNotMatch": "Gesli se ne ujemata.",
"Repeat Password": "Ponovi geslo",
"Create": "Ustvari",
"signedInDisp": "Prijavljeni kot {0}",
"signedInDispDisabled": "Preverjanje pristnosti onemogočeno.",
"home": "Domov",
"console": "Konzola",
"registry": "Register",
"compose": "Compose",
"addFirstStackMsg": "Ustvarite svoj prvi Stack!",
"stackName": "Ime Stack-a",
"deployStack": "Razporedi",
"deleteStack": "Izbriši",
"stopStack": "Ustavi",
"restartStack": "Ponovni zagon",
"updateStack": "Posodobi",
"startStack": "Zaženi",
"editStack": "Uredi",
"discardStack": "Zavrzi",
"saveStackDraft": "Shrani",
"notAvailableShort": "Ni na voljo",
"deleteStackMsg": "Ste prepričani, da želite izbrisati ta Stack?",
"stackNotManagedByDockgeMsg": "Ta Stack ni upravljan s strani Dockge.",
"primaryHostname": "Osnovno gostiteljsko ime",
"general": "Splošno",
"container": "Kontejner | Kontejnerji",
"scanFolder": "Preglej Stack mapo",
"dockerImage": "Slika",
"restartPolicyUnlessStopped": "Razen ko je zaustavljeno",
"restartPolicyAlways": "Vedno",
"restartPolicyOnFailure": "Ob napaki",
"restartPolicyNo": "Ne",
"environmentVariable": "Okoljska spremenljivka | Okoljske spremenljivke",
"restartPolicy": "Politika ponovnega zagona",
"containerName": "Ime kontejnerja",
"port": "Vrata | Vrata",
"volume": "Zvezek | Zvezki",
"network": "Omrežje | Omrežja",
"dependsOn": "Odvisnost kontejnerja | Odvisnosti kontejnerjev",
"addListItem": "Dodaj {0}",
"deleteContainer": "Izbriši",
"addContainer": "Dodaj kontejner",
"addNetwork": "Dodaj omrežje",
"disableauth.message1": "Ste prepričani, da želite <strong>onemogočiti overjanje</strong>?",
"disableauth.message2": "Namerno je zasnovano za scenarije, <strong>kjer nameravate izvajati avtentikacijo tretjih oseb</strong> pred Dockge, kot so Cloudflare Access, Authelia ali druge avtentikacijske mehanizme.",
"passwordNotMatchMsg": "Ponovljeno geslo se ne ujema.",
"autoGet": "Samodejno pridobi",
"add": "Dodaj",
"Edit": "Uredi",
"applyToYAML": "Uporabi za YAML",
"createExternalNetwork": "Ustvari",
"addInternalNetwork": "Dodaj",
"Save": "Shrani",
"Language": "Jezik",
"Current User": "Trenutni uporabnik",
"Change Password": "Spremeni geslo",
"Current Password": "Trenutno geslo",
"New Password": "Novo geslo",
"Repeat New Password": "Ponovi novo geslo",
"Update Password": "Posodobi geslo",
"Advanced": "Napredno",
"Please use this option carefully!": "Prosimo, uporabite to možnost previdno!",
"Enable Auth": "Omogoči overjanje",
"Disable Auth": "Onemogoči overjanje",
"I understand, please disable": "Razumem, prosim onemogočite",
"Leave": "Zapusti",
"Frontend Version": "Različica vmesnika",
"Check Update On GitHub": "Preveri posodobitve na GitHubu",
"Show update if available": "Prikaži posodobitve, če so na voljo",
"Also check beta release": "Preveri tudi beta izdaje",
"Remember me": "Zapomni si me",
"Login": "Prijava",
"Username": "Uporabniško ime",
"Password": "Geslo",
"Settings": "Nastavitve",
"Logout": "Odjava",
"Lowercase only": "Samo male črke",
"Convert to Compose": "Pretvori v Compose",
"Docker Run": "Zagon Dockerja",
"active": "aktivno",
"exited": "izklopljeno",
"inactive": "neaktivno",
"Appearance": "Videz",
"Security": "Varnost",
"About": "O nas",
"Allowed commands:": "Dovoljeni ukazi:",
"Internal Networks": "Notranja omrežja",
"External Networks": "Zunanja omrežja",
"No External Networks": "Ni zunanjih omrežij"
}

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

102
frontend/src/lang/tr.json Normal file
View file

@ -0,0 +1,102 @@
{
"languageName": "Türkçe",
"Create your admin account": "Yönetici hesabınızı oluşturun",
"authIncorrectCreds": "Yanlış kullanıcı adı veya parola.",
"PasswordsDoNotMatch": "Parolalar eşleşmiyor.",
"Repeat Password": "Parolayı Tekrarla",
"Create": "Oluştur",
"signedInDisp": "{0} olarak oturum açıldı",
"signedInDispDisabled": "Yetkilendirme Devre Dışı.",
"home": "Anasayfa",
"console": "Konsol",
"registry": "Kayıt Defteri",
"compose": "Compose",
"addFirstStackMsg": "İlk yığınınızı oluşturun!",
"stackName": "Yığın Adı",
"deployStack": "Dağıtmak",
"deleteStack": "Sil",
"stopStack": "Dudur",
"restartStack": "Yeniden Başlat",
"updateStack": "Güncelle",
"startStack": "Başlat",
"editStack": "Düzenle",
"discardStack": ıkar",
"saveStackDraft": "Kaydet",
"notAvailableShort": "N/A",
"deleteStackMsg": "Bu yığını silmek istediğinizden emin misiniz?",
"stackNotManagedByDockgeMsg": "Bu yığın Dockge tarafından yönetilmemektedir.",
"primaryHostname": "Birincil Ana Bilgisayar Adı",
"general": "Genel",
"container": "Konteyner | Konteynerler",
"scanFolder": "Yığınlar Klasörünü Tara",
"dockerImage": "Görüntü",
"restartPolicyUnlessStopped": "Durdurulana Kadar",
"restartPolicyAlways": "Her zaman",
"restartPolicyOnFailure": "Başarısızlıkta",
"restartPolicyNo": "Hayır",
"environmentVariable": "Ortam Değişkeni | Ortam Değişkenleri",
"restartPolicy": "Yeniden Başlatma Politikası",
"containerName": "Konteyner Adı",
"port": "Port | Portlar",
"volume": "Disk Bölümü | Disk Bölümleri",
"network": "Ağ | Ağlar",
"dependsOn": "Konteyner Bağımlılığı | Konteyner Bağımlılıkları",
"addListItem": "{0} Ekle",
"deleteContainer": "Sil",
"addContainer": "Konteyner Ekle",
"addNetwork": "Ağ Ekle",
"disableauth.message1": "<strong>Kimlik doğrulamayı devre dışı</strong> bırakmak istediğinizden emin misiniz?",
"disableauth.message2": "Cloudflare Access, Authelia veya diğer kimlik doğrulama mekanizmaları gibi Uptime Kuma'nın önünde <strong>üçüncü taraf kimlik doğrulaması uygulamak</strong> istediğiniz senaryolar için tasarlanmıştır.",
"passwordNotMatchMsg": "Tekrarlanan parola eşleşmiyor.",
"autoGet": "Otomatik Al",
"add": "Ekle",
"Edit": "Düzenle",
"applyToYAML": "YAML'ye uygulayın",
"createExternalNetwork": "Oluştur",
"addInternalNetwork": "Ekle",
"Save": "Kaydet",
"Language": "Dil",
"Current User": "Mevcut Kullanıcı",
"Change Password": "Mevcut Parola",
"Current Password": "Mevcut Parola",
"New Password": "Yeni Parola",
"Repeat New Password": "Yeni Parolayı Tekrarla",
"Update Password": "Parolayı Güncelle",
"Advanced": "Gelişmiş",
"Please use this option carefully!": "Lütfen bu seçeneği dikkatli kullanın!",
"Enable Auth": "Kimlik Doğrulamayı Etkinleştir",
"Disable Auth": "Kimlik Doğrulamayı Devre Dışı Bırak",
"I understand, please disable": "Anlıyorum, lütfen devre dışı bırakın",
"Leave": "Ayrıl",
"Frontend Version": "Frontend Versiyon",
"Check Update On GitHub": "GitHub'da Güncellemeyi Kontrol Edin",
"Show update if available": "Varsa güncellemeyi göster",
"Also check beta release": "Ayrıca beta sürümünü kontrol edin",
"Remember me": "Beni Hatırla",
"Login": "Oturum Aç",
"Username": "Kullanıcı Adı",
"Password": "Parola",
"Settings": "Ayarlar",
"Logout": "Oturumu Kapat",
"Lowercase only": "Yalnızca küçük harf",
"Convert to Compose": "Compose'a Dönüştür",
"Docker Run": "Docker Run",
"active": "aktif",
"exited": ıkış yaptı",
"inactive": "aktif değil",
"Appearance": "Görünüm",
"Security": "Güvenlik",
"About": "Hakkında",
"Allowed commands:": "İzin verilen komutlar:",
"Internal Networks": "İç Ağlar",
"External Networks": "Dış Ağlar",
"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": "Додатково"
}

102
frontend/src/lang/ur.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": "شروع کریں",
"editStack": "ترمیم",
"discardStack": "رد کر دیں",
"saveStackDraft": "محفوظ کریں۔",
"notAvailableShort": "N / A",
"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": "گیتوب پر اپ ڈیٹ چیک کریں",
"Show update if available": "اگر دستیاب ہو تو اپ ڈیٹ دکھائیں",
"Also check beta release": "بیٹا ریلیز بھی چیک کریں",
"Remember me": "مجھے پہچانتے ہو",
"Login": "لاگ ان کریں",
"Username": "صارف نام",
"Password": "پاس ورڈ",
"Settings": "ترتیبات",
"Logout": "لاگ آوٹ",
"Lowercase only": "صرف لوئر کیس",
"Convert to Compose": "تحریر میں تبدیل کریں",
"Docker Run": "ڈاکر رن",
"active": "فعال",
"exited": "باہر نکلا",
"inactive": "غیر فعال",
"Appearance": "ظہور",
"Security": "سیکورٹی",
"About": "کے بارے میں",
"Allowed commands:": "اجازت شدہ احکامات:",
"Internal Networks": "اندرونی نیٹ ورکس",
"External Networks": "بیرونی نیٹ ورکس",
"No External Networks": "کوئی بیرونی نیٹ ورک نہیں",
"reverseProxyMsg1": "ایک ریورس پراکسی کا استعمال کرتے ہوئے؟",
"Cannot connect to the socket server.": "ساکٹ سرور سے منسلک نہیں ہو سکتا۔",
"reconnecting...": "دوبارہ منسلک ہو رہا ہے…",
"connecting...": "ساکٹ سرور سے منسلک ہو رہا ہے…",
"url": "یو آر ایل | یو آر ایل",
"extra": "اضافی",
"downStack": "اسٹاپ اینڈ ڈاؤن",
"reverseProxyMsg2": "اسے WebSocket کے لیے ترتیب دینے کا طریقہ چیک کریں"
}

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": "无外部网络",
"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 class="container-fluid">
{{ $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>
@ -82,6 +85,10 @@
</header>
<main>
<div v-if="$root.socketIO.connecting" class="container mt-5">
<h4>{{ $t("connecting...") }}</h4>
</div>
<router-view v-if="$root.loggedIn" />
<Login v-if="! $root.loggedIn && $root.allowLoginDialog" />
</main>

View file

@ -19,6 +19,7 @@ export default defineComponent({
initedSocketIO: false,
connectionErrorMsg: `${this.$t("Cannot connect to the socket server.")} ${this.$t("Reconnecting...")}`,
showReverseProxyGuide: true,
connecting: false,
},
info: {
@ -103,6 +104,10 @@ export default defineComponent({
url = location.protocol + "//" + location.host;
}
let connectingMsgTimeout = setTimeout(() => {
this.socketIO.connecting = true;
}, 1500);
socket = io(url, {
transports: [ "websocket", "polling" ]
});
@ -110,6 +115,9 @@ export default defineComponent({
socket.on("connect", () => {
console.log("Connected to the socket server");
clearTimeout(connectingMsgTimeout);
this.socketIO.connecting = false;
this.socketIO.connectCount++;
this.socketIO.connected = true;
this.socketIO.showReverseProxyGuide = false;
@ -143,10 +151,11 @@ export default defineComponent({
socket.on("connect_error", (err) => {
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.connected = false;
this.socketIO.firstConnect = false;
this.socketIO.connecting = false;
});
// Custom Events

View file

@ -40,6 +40,13 @@
<font-awesome-icon icon="stop" class="me-1" />
{{ $t("stopStack") }}
</button>
<BDropdown right text="" variant="normal">
<BDropdownItem @click="downStack">
<font-awesome-icon icon="stop" class="me-1" />
{{ $t("downStack") }}
</BDropdownItem>
</BDropdown>
</div>
<button v-if="isEditMode && !isAdd" class="btn btn-normal" :disabled="processing" @click="discardStack">{{ $t("discardStack") }}</button>
@ -49,6 +56,13 @@
</button>
</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 -->
<transition name="slide-fade" appear>
<Terminal
@ -68,9 +82,10 @@
<h4 class="mb-3">{{ $t("general") }}</h4>
<div class="shadow-box big-padding mb-3">
<!-- Stack Name -->
<div class="mb-3">
<div>
<label for="name" class="form-label">{{ $t("stackName") }}</label>
<input id="name" v-model="stack.name" type="text" class="form-control" required>
<input id="name" v-model="stack.name" type="text" class="form-control" required @blur="stackNameToLowercase">
<div class="form-text">{{ $t("Lowercase only") }}</div>
</div>
</div>
</div>
@ -103,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>
<!-- 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 -->
<div v-show="!isEditMode">
<h4 class="mb-3">Terminal</h4>
@ -117,7 +146,7 @@
</div>
</div>
<div class="col-lg-6">
<h4 class="mb-3">compose.yaml</h4>
<h4 class="mb-3">{{ stack.composeFileName }}</h4>
<!-- YAML editor -->
<div class="shadow-box mb-3 editor-box" :class="{'edit-mode' : isEditMode}">
@ -125,7 +154,7 @@
ref="editor"
v-model="stack.composeYAML"
class="yaml-editor"
:highlight="highlighter"
:highlight="highlighterYAML"
line-numbers :readonly="!isEditMode"
@input="yamlCodeChange"
@focus="editorFocus = true"
@ -136,6 +165,22 @@
{{ yamlError }}
</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">
<!-- Volumes -->
<div v-if="false">
@ -203,10 +248,16 @@ services:
ports:
- "8080:80"
`;
const envDefault = "# VARIABLE=value #comment";
let yamlErrorTimeout = null;
let serviceStatusTimeout = null;
let prismjsSymbolDefinition = {
"symbol": {
pattern: /(?<!\$)\$(\{[^{}]*\}|\w+)/,
}
};
export default {
components: {
@ -244,6 +295,34 @@ export default {
};
},
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() {
return this.$route.path === "/compose" && !this.submitted;
},
@ -311,6 +390,12 @@ export default {
},
deep: true,
},
$route(to, from) {
// Leave Combined Terminal
console.debug("leaveCombinedTerminal", from.params.stackName);
this.$root.getSocket().emit("leaveCombinedTerminal", this.stack.name, () => {});
}
},
mounted() {
if (this.isAdd) {
@ -318,19 +403,26 @@ export default {
this.isEditMode = true;
let composeYAML;
let composeENV;
if (this.$root.composeTemplate) {
composeYAML = this.$root.composeTemplate;
this.$root.composeTemplate = "";
} else {
composeYAML = template;
}
if (this.$root.envTemplate) {
composeENV = this.$root.envTemplate;
this.$root.envTemplate = "";
} else {
composeENV = envDefault;
}
// Default Values
this.stack = {
name: "",
composeYAML,
composeENV,
isManagedByDockge: true,
};
@ -353,7 +445,7 @@ export default {
clearTimeout(serviceStatusTimeout);
serviceStatusTimeout = setTimeout(async () => {
this.requestServiceStatus();
}, 2000);
}, 5000);
},
requestServiceStatus() {
@ -429,7 +521,7 @@ export default {
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.$root.toastRes(res);
@ -443,7 +535,7 @@ export default {
saveStack() {
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.$root.toastRes(res);
@ -472,6 +564,15 @@ export default {
});
},
downStack() {
this.processing = true;
this.$root.getSocket().emit("downStack", this.stack.name, (res) => {
this.processing = false;
this.$root.toastRes(res);
});
},
restartStack() {
this.processing = true;
@ -504,8 +605,44 @@ export default {
this.isEditMode = false;
},
highlighter(code) {
return highlight(code, languages.yaml);
highlighterYAML(code) {
if (!languages.yaml_with_symbols) {
languages.yaml_with_symbols = languages.insertBefore("yaml", "punctuation", {
"symbol": prismjsSymbolDefinition["symbol"]
});
}
return highlight(code, languages.yaml_with_symbols);
},
highlighterENV(code) {
if (!languages.docker_env) {
languages.docker_env = {
"comment": {
pattern: /(^#| #).*$/m,
greedy: true
},
"keyword": {
pattern: /^[^ :=]*(?=[:=])/m,
greedy: true
},
"value": {
pattern: /(?<=[:=]).*?((?= #)|$)/m,
greedy: true,
inside: {
"string": [
{
pattern: /^ *'.*?(?<!\\)'/m,
},
{
pattern: /^ *".*?(?<!\\)"|^.*$/m,
inside: prismjsSymbolDefinition
},
],
},
},
};
}
return highlight(code, languages.docker_env);
},
yamlCodeChange() {
@ -527,10 +664,6 @@ export default {
throw new Error("Services must be an object");
}
if (!config.version) {
config.version = "3.8";
}
this.yamlDoc = doc;
this.jsonConfig = config;
@ -582,6 +715,10 @@ export default {
});
},
stackNameToLowercase() {
this.stack.name = this.stack?.name?.toLowerCase();
},
}
};
</script>

View file

@ -5,7 +5,7 @@
<div>
<p>
Allowed commands:
{{ $t("Allowed commands:") }}
<template v-for="(command, index) in allowedCommandList" :key="command">
<code>{{ command }}</code>

View file

@ -22,12 +22,12 @@
</div>
</div>
<h2 class="mb-3">Docker Run</h2>
<h2 class="mb-3">{{ $t("Docker Run") }}</h2>
<div class="mb-3">
<textarea id="name" v-model="dockerRunCommand" type="text" class="form-control docker-run" required placeholder="docker run ..."></textarea>
</div>
<button class="btn-normal btn" @click="convertDockerRun">Convert to Compose</button>
<button class="btn-normal btn" @click="convertDockerRun">{{ $t("Convert to Compose") }}</button>
</div>
</transition>
<router-view ref="child" />

View file

@ -75,7 +75,7 @@ export default {
subMenus() {
return {
general: {
title: this.$t("General"),
title: this.$t("general"),
},
appearance: {
title: this.$t("Appearance"),

View file

@ -680,6 +680,10 @@ code {
}
}
.form-text {
color: $dark-font-color3;
}
// Vue Prism Editor bug - workaround
// https://github.com/koca/vue-prism-editor/issues/87
/*

View file

@ -10,7 +10,7 @@ import { POSITION } from "vue-toastification";
*
* Generated by Trelent
*/
function getTimezoneOffset(timeZone) {
function getTimezoneOffset(timeZone : string) {
const now = new Date();
const tzString = now.toLocaleString("en-US", {
timeZone,
@ -124,33 +124,6 @@ export function hostNameRegexPattern(mqtt = false) {
return `${ipRegexPattern}|${hostNameRegexPattern}`;
}
/**
* Get the tag color options
* Shared between components
* @param {any} self Component
* @returns {object[]} Colour options
*/
export function colorOptions(self) {
return [
{ name: self.$t("Gray"),
color: "#4B5563" },
{ name: self.$t("Red"),
color: "#DC2626" },
{ name: self.$t("Orange"),
color: "#D97706" },
{ name: self.$t("Green"),
color: "#059669" },
{ name: self.$t("Blue"),
color: "#2563EB" },
{ name: self.$t("Indigo"),
color: "#4F46E5" },
{ name: self.$t("Purple"),
color: "#7C3AED" },
{ name: self.$t("Pink"),
color: "#DB2777" },
];
}
/**
* Loads the toast timeout settings from storage.
* @returns {object} The toast plugin options object.

View file

@ -1,3 +1,4 @@
/* eslint-disable */
/// <reference types="vite/client" />
declare module "*.vue" {

View file

@ -1,32 +1,39 @@
{
"name": "dockge",
"version": "1.0.1",
"version": "1.3.2",
"type": "module",
"engines": {
"node": ">= 18.0.0 && <= 18.17.1"
},
"scripts": {
"fmt": "eslint \"**/*.{ts,vue}\" --fix",
"lint": "eslint \"**/*.{ts,vue}\"",
"check-ts": "tsc --noEmit",
"start": "tsx ./backend/index.ts",
"dev:backend": "cross-env NODE_ENV=development tsx watch ./backend/index.ts",
"dev:backend": "cross-env NODE_ENV=development tsx watch --inspect ./backend/index.ts",
"dev:frontend": "cross-env NODE_ENV=development vite --host --config ./frontend/vite.config.ts",
"release-final": "tsx ./extra/test-docker.ts && pnpm run build:frontend && tsx extra/update-version.ts && npm run build:docker",
"release-final": "tsx ./extra/test-docker.ts && tsx extra/update-version.ts && pnpm run build:frontend && npm run build:docker",
"build:frontend": "vite build --config ./frontend/vite.config.ts",
"build:docker-base": "docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/dockge:base -f ./docker/Base.Dockerfile . --push",
"build:docker": "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: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",
"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",
"reset-password": "tsx ./extra/reset-password.ts"
},
"dependencies": {
"@fontsource/jetbrains-mono": "^5.0.17",
"@homebridge/node-pty-prebuilt-multiarch": "~0.11.10",
"@homebridge/node-pty-prebuilt-multiarch": "~0.11.11",
"@louislam/sqlite3": "~15.1.6",
"bcryptjs": "~2.4.3",
"check-password-strength": "~2.0.7",
"command-exists": "~1.2.9",
"compare-versions": "~6.1.0",
"composerize": "~1.4.1",
"croner": "~7.0.4",
"croner": "~7.0.5",
"dayjs": "~1.11.10",
"dotenv": "~16.3.1",
"express": "~4.18.2",
"express-static-gzip": "~2.1.7",
"http-graceful-shutdown": "~3.1.13",
@ -34,8 +41,9 @@
"jwt-decode": "~3.1.2",
"knex": "~2.5.1",
"limiter-es6-compat": "~2.1.2",
"mysql2": "^3.6.3",
"redbean-node": "0.3.2",
"mysql2": "~3.6.3",
"promisify-child-process": "~4.1.2",
"redbean-node": "~0.3.3",
"socket.io": "~4.7.2",
"socket.io-client": "~4.7.2",
"timezones-list": "~3.0.2",
@ -45,17 +53,20 @@
"yaml": "~2.3.4"
},
"devDependencies": {
"@actions/github": "^6.0.0",
"@fontsource/jetbrains-mono": "^5.0.17",
"@fortawesome/fontawesome-svg-core": "6.4.2",
"@fortawesome/free-regular-svg-icons": "6.4.2",
"@fortawesome/free-solid-svg-icons": "6.4.2",
"@fortawesome/vue-fontawesome": "3.0.3",
"@types/bootstrap": "~5.2.8",
"@types/bcryptjs": "^2.4.6",
"@types/bootstrap": "~5.2.9",
"@types/command-exists": "~1.2.3",
"@types/express": "~4.17.21",
"@types/jsonwebtoken": "~9.0.4",
"@types/jsonwebtoken": "~9.0.5",
"@typescript-eslint/eslint-plugin": "~6.8.0",
"@typescript-eslint/parser": "~6.8.0",
"@vitejs/plugin-vue": "~4.3.4",
"@vitejs/plugin-vue": "~4.5.0",
"bootstrap": "5.3.2",
"bootstrap-vue-next": "~0.14.10",
"cross-env": "~7.0.3",
@ -66,7 +77,7 @@
"sass": "~1.68.0",
"typescript": "~5.2.2",
"unplugin-vue-components": "~0.25.2",
"vite": "~4.5.0",
"vite": "~5.0.0",
"vite-plugin-compression": "~0.5.1",
"vue": "~3.3.8",
"vue-eslint-parser": "~9.3.2",
@ -75,7 +86,7 @@
"vue-qrcode": "~2.2.0",
"vue-router": "~4.2.5",
"vue-toastification": "2.0.0-rc.5",
"xterm": "~5.4.0-beta.37",
"xterm": "5.4.0-beta.37",
"xterm-addon-web-links": "~0.9.0"
}
}

File diff suppressed because it is too large Load diff

View file

@ -3,6 +3,10 @@
"module": "ESNext",
"target": "ESNext",
"strict": true,
"moduleResolution": "bundler"
}
"moduleResolution": "bundler",
"skipLibCheck": true
},
"include": [
"backend/**/*"
]
}