├── .dockerignore ├── .editorconfig ├── .eslintrc.cjs ├── .github ├── DISCUSSION_TEMPLATE │ ├── ask-for-help.yml │ └── feature-request.yml ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── ask-for-help.yaml │ ├── bug_report.yaml │ ├── feature_request.yaml │ └── security.md ├── PULL_REQUEST_TEMPLATE.md ├── config │ └── exclude.txt └── workflows │ ├── ci.yml │ ├── close-incorrect-issue.yml │ ├── json-yaml-validate.yml │ └── prevent-file-change.yml ├── .gitignore ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── SECURITY.md ├── backend ├── agent-manager.ts ├── agent-socket-handler.ts ├── agent-socket-handlers │ ├── docker-socket-handler.ts │ └── terminal-socket-handler.ts ├── check-version.ts ├── database.ts ├── dockge-server.ts ├── index.ts ├── log.ts ├── migrations │ ├── 2023-10-20-0829-setting-table.ts │ ├── 2023-10-20-0829-user-table.ts │ └── 2023-12-20-2117-agent-table.ts ├── models │ ├── agent.ts │ └── user.ts ├── password-hash.ts ├── rate-limiter.ts ├── router.ts ├── routers │ └── main-router.ts ├── settings.ts ├── socket-handler.ts ├── socket-handlers │ ├── agent-proxy-socket-handler.ts │ ├── main-socket-handler.ts │ └── manage-agent-socket-handler.ts ├── stack.ts ├── terminal.ts ├── util-server.ts └── utils │ └── limit-queue.ts ├── common ├── agent-socket.ts └── util-common.ts ├── compose.yaml ├── docker ├── Base.Dockerfile ├── BuildHealthCheck.Dockerfile └── Dockerfile ├── extra ├── close-incorrect-issue.js ├── env2arg.js ├── healthcheck.go ├── mark-as-nightly.ts ├── reformat-changelog.ts ├── reset-password.ts ├── templates │ ├── mariadb │ │ └── compose.yaml │ ├── nginx-proxy-manager │ │ └── compose.yaml │ └── uptime-kuma │ │ └── compose.yaml ├── test-docker.ts └── update-version.ts ├── frontend ├── components.d.ts ├── index.html ├── public │ ├── apple-touch-icon.png │ ├── favicon.ico │ ├── icon-192x192.png │ ├── icon-512x512.png │ ├── icon.svg │ └── manifest.json ├── src │ ├── App.vue │ ├── components │ │ ├── ArrayInput.vue │ │ ├── ArraySelect.vue │ │ ├── Confirm.vue │ │ ├── Container.vue │ │ ├── HiddenInput.vue │ │ ├── Login.vue │ │ ├── NetworkInput.vue │ │ ├── StackList.vue │ │ ├── StackListItem.vue │ │ ├── Terminal.vue │ │ ├── TwoFADialog.vue │ │ ├── Uptime.vue │ │ └── settings │ │ │ ├── About.vue │ │ │ ├── Appearance.vue │ │ │ ├── General.vue │ │ │ └── Security.vue │ ├── i18n.ts │ ├── icon.ts │ ├── lang │ │ ├── README.md │ │ ├── ar.json │ │ ├── be.json │ │ ├── bg-BG.json │ │ ├── ca.json │ │ ├── cs-CZ.json │ │ ├── da.json │ │ ├── de-CH.json │ │ ├── de.json │ │ ├── en.json │ │ ├── es.json │ │ ├── fr.json │ │ ├── ga.json │ │ ├── hu.json │ │ ├── id.json │ │ ├── it-IT.json │ │ ├── ja.json │ │ ├── ko-KR.json │ │ ├── nb_NO.json │ │ ├── nl.json │ │ ├── pl-PL.json │ │ ├── pt-BR.json │ │ ├── pt.json │ │ ├── ro.json │ │ ├── ru.json │ │ ├── sl.json │ │ ├── sv-SE.json │ │ ├── th.json │ │ ├── tr.json │ │ ├── uk-UA.json │ │ ├── ur.json │ │ ├── vi.json │ │ ├── zh-CN.json │ │ └── zh-TW.json │ ├── layouts │ │ ├── EmptyLayout.vue │ │ └── Layout.vue │ ├── main.ts │ ├── mixins │ │ ├── lang.ts │ │ ├── socket.ts │ │ └── theme.ts │ ├── pages │ │ ├── Compose.vue │ │ ├── Console.vue │ │ ├── ContainerTerminal.vue │ │ ├── Dashboard.vue │ │ ├── DashboardHome.vue │ │ ├── Settings.vue │ │ └── Setup.vue │ ├── router.ts │ ├── styles │ │ ├── localization.scss │ │ ├── main.scss │ │ └── vars.scss │ ├── util-frontend.ts │ └── vite-env.d.ts └── vite.config.ts ├── package-lock.json ├── package.json └── tsconfig.json /.dockerignore: -------------------------------------------------------------------------------- 1 | # Should be identical to .gitignore 2 | .env 3 | node_modules 4 | .idea 5 | data 6 | stacks 7 | tmp 8 | /private 9 | 10 | # Docker extra 11 | docker 12 | frontend 13 | .editorconfig 14 | .eslintrc.cjs 15 | .git 16 | .gitignore 17 | README.md 18 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 4 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | 14 | [*.yaml] 15 | indent_size = 2 16 | 17 | [*.yml] 18 | indent_size = 2 19 | 20 | [*.vue] 21 | trim_trailing_whitespace = false 22 | 23 | [*.go] 24 | indent_style = tab 25 | -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | browser: true, 5 | node: true, 6 | }, 7 | extends: [ 8 | "eslint:recommended", 9 | "plugin:@typescript-eslint/recommended", 10 | "plugin:vue/vue3-recommended", 11 | ], 12 | parser: "vue-eslint-parser", 13 | parserOptions: { 14 | "parser": "@typescript-eslint/parser", 15 | }, 16 | plugins: [ 17 | "@typescript-eslint", 18 | "jsdoc" 19 | ], 20 | rules: { 21 | "yoda": "error", 22 | "linebreak-style": [ "error", "unix" ], 23 | "camelcase": [ "warn", { 24 | "properties": "never", 25 | "ignoreImports": true 26 | }], 27 | "no-unused-vars": [ "warn", { 28 | "args": "none" 29 | }], 30 | indent: [ 31 | "error", 32 | 4, 33 | { 34 | ignoredNodes: [ "TemplateLiteral" ], 35 | SwitchCase: 1, 36 | }, 37 | ], 38 | quotes: [ "error", "double" ], 39 | semi: "error", 40 | "vue/html-indent": [ "error", 4 ], // default: 2 41 | "vue/max-attributes-per-line": "off", 42 | "vue/singleline-html-element-content-newline": "off", 43 | "vue/html-self-closing": "off", 44 | "vue/require-component-is": "off", // not allow is="style" https://github.com/vuejs/eslint-plugin-vue/issues/462#issuecomment-430234675 45 | "vue/attribute-hyphenation": "off", // This change noNL to "no-n-l" unexpectedly 46 | "vue/multi-word-component-names": "off", 47 | "no-multi-spaces": [ "error", { 48 | ignoreEOLComments: true, 49 | }], 50 | "array-bracket-spacing": [ "warn", "always", { 51 | "singleValue": true, 52 | "objectsInArrays": false, 53 | "arraysInArrays": false 54 | }], 55 | "space-before-function-paren": [ "error", { 56 | "anonymous": "always", 57 | "named": "never", 58 | "asyncArrow": "always" 59 | }], 60 | "curly": "error", 61 | "object-curly-spacing": [ "error", "always" ], 62 | "object-curly-newline": "off", 63 | "object-property-newline": "error", 64 | "comma-spacing": "error", 65 | "brace-style": "error", 66 | "no-var": "error", 67 | "key-spacing": "warn", 68 | "keyword-spacing": "warn", 69 | "space-infix-ops": "error", 70 | "arrow-spacing": "warn", 71 | "no-trailing-spaces": "error", 72 | "no-constant-condition": [ "error", { 73 | "checkLoops": false, 74 | }], 75 | "space-before-blocks": "warn", 76 | "no-extra-boolean-cast": "off", 77 | "no-multiple-empty-lines": [ "warn", { 78 | "max": 1, 79 | "maxBOF": 0, 80 | }], 81 | "lines-between-class-members": [ "warn", "always", { 82 | exceptAfterSingleLine: true, 83 | }], 84 | "no-unneeded-ternary": "error", 85 | "array-bracket-newline": [ "error", "consistent" ], 86 | "eol-last": [ "error", "always" ], 87 | "comma-dangle": [ "warn", "only-multiline" ], 88 | "no-empty": [ "error", { 89 | "allowEmptyCatch": true 90 | }], 91 | "no-control-regex": "off", 92 | "one-var": [ "error", "never" ], 93 | "max-statements-per-line": [ "error", { "max": 1 }], 94 | "@typescript-eslint/ban-ts-comment": "off", 95 | "@typescript-eslint/no-unused-vars": [ "warn", { 96 | "args": "none" 97 | }], 98 | "prefer-const" : "off", 99 | }, 100 | }; 101 | -------------------------------------------------------------------------------- /.github/DISCUSSION_TEMPLATE/ask-for-help.yml: -------------------------------------------------------------------------------- 1 | labels: [help] 2 | body: 3 | - type: checkboxes 4 | id: no-duplicate-issues 5 | attributes: 6 | label: "⚠️ Please verify that this bug has NOT been raised before." 7 | description: "Search in the issues sections by clicking [HERE](https://github.com/louislam/dockge/discussions/categories/ask-for-help)" 8 | options: 9 | - label: "I checked and didn't find similar issue" 10 | required: true 11 | - type: checkboxes 12 | attributes: 13 | label: "🛡️ Security Policy" 14 | description: Please review the security policy before reporting security related issues/bugs. 15 | options: 16 | - label: I agree to have read this project [Security Policy](https://github.com/louislam/dockge/security/policy) 17 | required: true 18 | - type: textarea 19 | id: steps-to-reproduce 20 | validations: 21 | required: true 22 | attributes: 23 | label: "📝 Describe your problem" 24 | description: "Please walk us through it step by step." 25 | placeholder: "Describe what are you asking for..." 26 | - type: textarea 27 | id: error-msg 28 | validations: 29 | required: false 30 | attributes: 31 | label: "📝 Error Message(s) or Log" 32 | - type: input 33 | id: dockge-version 34 | attributes: 35 | label: "🐻 Dockge Version" 36 | description: "Which version of Dockge are you running? Please do NOT provide the docker tag such as latest or 1" 37 | placeholder: "Ex. 1.10.0" 38 | validations: 39 | required: true 40 | - type: input 41 | id: operating-system 42 | attributes: 43 | label: "💻 Operating System and Arch" 44 | description: "Which OS is your server/device running on? (For Replit, please do not report this bug)" 45 | placeholder: "Ex. Ubuntu 20.04 x86" 46 | validations: 47 | required: true 48 | - type: input 49 | id: browser-vendor 50 | attributes: 51 | label: "🌐 Browser" 52 | description: "Which browser are you running on? (For Replit, please do not report this bug)" 53 | placeholder: "Ex. Google Chrome 95.0.4638.69" 54 | validations: 55 | required: true 56 | - type: input 57 | id: docker-version 58 | attributes: 59 | label: "🐋 Docker Version" 60 | description: "If running with Docker, which version are you running?" 61 | placeholder: "Ex. Docker 20.10.9 / K8S / Podman" 62 | validations: 63 | required: false 64 | - type: input 65 | id: nodejs-version 66 | attributes: 67 | label: "🟩 NodeJS Version" 68 | description: "If running with Node.js? which version are you running?" 69 | placeholder: "Ex. 14.18.0" 70 | validations: 71 | required: false 72 | -------------------------------------------------------------------------------- /.github/DISCUSSION_TEMPLATE/feature-request.yml: -------------------------------------------------------------------------------- 1 | labels: [feature-request] 2 | body: 3 | - type: checkboxes 4 | id: no-duplicate-issues 5 | attributes: 6 | label: "⚠️ Please verify that this feature request has NOT been suggested before." 7 | description: "Search in the issues sections by clicking [HERE](https://github.com/louislam/dockge/discussions/categories/feature-request)" 8 | options: 9 | - label: "I checked and didn't find similar feature request" 10 | required: true 11 | - type: dropdown 12 | id: feature-area 13 | attributes: 14 | label: "🏷️ Feature Request Type" 15 | description: "What kind of feature request is this?" 16 | multiple: true 17 | options: 18 | - API 19 | - UI Feature 20 | - Other 21 | validations: 22 | required: true 23 | - type: textarea 24 | id: feature-description 25 | validations: 26 | required: true 27 | attributes: 28 | label: "🔖 Feature description" 29 | description: "A clear and concise description of what the feature request is." 30 | placeholder: "You should add ..." 31 | - type: textarea 32 | id: solution 33 | validations: 34 | required: true 35 | attributes: 36 | label: "✔️ Solution" 37 | description: "A clear and concise description of what you want to happen." 38 | placeholder: "In my use-case, ..." 39 | - type: textarea 40 | id: alternatives 41 | validations: 42 | required: false 43 | attributes: 44 | label: "❓ Alternatives" 45 | description: "A clear and concise description of any alternative solutions or features you've considered." 46 | placeholder: "I have considered ..." 47 | - type: textarea 48 | id: additional-context 49 | validations: 50 | required: false 51 | attributes: 52 | label: "📝 Additional Context" 53 | description: "Add any other context or screenshots about the feature request here." 54 | placeholder: "..." 55 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: louislam # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | #patreon: # Replace with a single Patreon username 5 | open_collective: uptime-kuma # Replace with a single Open Collective username 6 | #ko_fi: # Replace with a single Ko-fi username 7 | #tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | #community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | #liberapay: # Replace with a single Liberapay username 10 | #issuehunt: # Replace with a single IssueHunt username 11 | #otechie: # Replace with a single Otechie username 12 | #custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/ask-for-help.yaml: -------------------------------------------------------------------------------- 1 | name: "⚠️ Ask for help (Please go to the \"Discussions\" tab to submit a Help Request)" 2 | description: "⚠️ Please go to the \"Discussions\" tab to submit a Help Request" 3 | body: 4 | - type: markdown 5 | attributes: 6 | value: | 7 | ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ Please go to https://github.com/louislam/dockge/discussions/new?category=ask-for-help 8 | - type: checkboxes 9 | id: no-duplicate-issues 10 | attributes: 11 | label: "Issues are for bug reports only, please go to the \"Discussions\" tab to submit a Feature Request" 12 | options: 13 | - label: "I understand" 14 | required: true 15 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yaml: -------------------------------------------------------------------------------- 1 | name: "🐛 Bug Report" 2 | description: "Submit a bug report to help us improve" 3 | #title: "[Bug] " 4 | labels: [bug] 5 | body: 6 | - type: checkboxes 7 | id: no-duplicate-issues 8 | attributes: 9 | label: "⚠️ Please verify that this bug has NOT been reported before." 10 | description: "Search in the issues sections by clicking [HERE](https://github.com/louislam/dockge/issues?q=)" 11 | options: 12 | - label: "I checked and didn't find similar issue" 13 | required: true 14 | - type: checkboxes 15 | attributes: 16 | label: "🛡️ Security Policy" 17 | description: Please review the security policy before reporting security related issues/bugs. 18 | options: 19 | - label: I agree to have read this project [Security Policy](https://github.com/louislam/dockge/security/policy) 20 | required: true 21 | - type: textarea 22 | id: description 23 | validations: 24 | required: false 25 | attributes: 26 | label: "Description" 27 | description: "You could also upload screenshots" 28 | - type: textarea 29 | id: steps-to-reproduce 30 | validations: 31 | required: true 32 | attributes: 33 | label: "👟 Reproduction steps" 34 | description: "How do you trigger this bug? Please walk us through it step by step." 35 | placeholder: "..." 36 | - type: textarea 37 | id: expected-behavior 38 | validations: 39 | required: true 40 | attributes: 41 | label: "👀 Expected behavior" 42 | description: "What did you think would happen?" 43 | placeholder: "..." 44 | - type: textarea 45 | id: actual-behavior 46 | validations: 47 | required: true 48 | attributes: 49 | label: "😓 Actual Behavior" 50 | description: "What actually happen?" 51 | placeholder: "..." 52 | - type: input 53 | id: dockge-version 54 | attributes: 55 | label: "Dockge Version" 56 | description: "Which version of Dockge are you running? Please do NOT provide the docker tag such as latest or 1" 57 | placeholder: "Ex. 1.1.1" 58 | validations: 59 | required: true 60 | - type: input 61 | id: operating-system 62 | attributes: 63 | label: "💻 Operating System and Arch" 64 | description: "Which OS is your server/device running on?" 65 | placeholder: "Ex. Ubuntu 20.04 x64 " 66 | validations: 67 | required: true 68 | - type: input 69 | id: browser-vendor 70 | attributes: 71 | label: "🌐 Browser" 72 | description: "Which browser are you running on?" 73 | placeholder: "Ex. Google Chrome 95.0.4638.69" 74 | validations: 75 | required: true 76 | - type: input 77 | id: docker-version 78 | attributes: 79 | label: "🐋 Docker Version" 80 | description: "If running with Docker, which version are you running?" 81 | placeholder: "Ex. Docker 20.10.9 / K8S / Podman" 82 | validations: 83 | required: false 84 | - type: input 85 | id: nodejs-version 86 | attributes: 87 | label: "🟩 NodeJS Version" 88 | description: "If running with Node.js? which version are you running?" 89 | placeholder: "Ex. 14.18.0" 90 | validations: 91 | required: false 92 | - type: textarea 93 | id: logs 94 | attributes: 95 | label: "📝 Relevant log output" 96 | description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks. 97 | render: shell 98 | validations: 99 | required: false 100 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yaml: -------------------------------------------------------------------------------- 1 | name: 🚀 Feature Request (Please go to the "Discussions" tab to submit a Feature Request) 2 | description: "⚠️ Please go to the \"Discussions\" tab to submit a Feature Request" 3 | body: 4 | - type: markdown 5 | attributes: 6 | value: | 7 | ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ Please go to https://github.com/louislam/dockge/discussions/new?category=ask-for-help 8 | - type: checkboxes 9 | id: no-duplicate-issues 10 | attributes: 11 | label: "Issues are for bug reports only, please go to the \"Discussions\" tab to submit a Feature Request" 12 | options: 13 | - label: "I understand" 14 | required: true 15 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/security.md: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | name: "Security Issue" 4 | about: "Just for alerting @louislam, do not provide any details here" 5 | title: "Security Issue" 6 | ref: "main" 7 | labels: 8 | 9 | - security 10 | 11 | --- 12 | 13 | DO NOT PROVIDE ANY DETAILS HERE. Please privately report to https://github.com/louislam/dockge/security/advisories/new. 14 | 15 | 16 | Why need this issue? It is because GitHub Advisory do not send a notification to @louislam, it is a workaround to do so. 17 | 18 | Your GitHub Advisory URL: 19 | 20 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ⚠️⚠️⚠️ 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: 2 | https://github.com/louislam/dockge/blob/master/CONTRIBUTING.md 3 | 4 | Tick the checkbox if you understand [x]: 5 | - [ ] I have read and understand the pull request rules. 6 | 7 | # Description 8 | 9 | Fixes #(issue) 10 | 11 | ## Type of change 12 | 13 | Please delete any options that are not relevant. 14 | 15 | - Bug fix (non-breaking change which fixes an issue) 16 | - User interface (UI) 17 | - New feature (non-breaking change which adds functionality) 18 | - Breaking change (fix or feature that would cause existing functionality to not work as expected) 19 | - Other 20 | - This change requires a documentation update 21 | 22 | ## Checklist 23 | 24 | - [ ] My code follows the style guidelines of this project 25 | - [ ] I ran ESLint and other linters for modified files 26 | - [ ] I have performed a self-review of my own code and tested it 27 | - [ ] I have commented my code, particularly in hard-to-understand areas 28 | (including JSDoc for methods) 29 | - [ ] My changes generate no new warnings 30 | - [ ] My code needed automated testing. I have added them (this is optional task) 31 | 32 | ## Screenshots (if any) 33 | 34 | 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. 35 | -------------------------------------------------------------------------------- /.github/config/exclude.txt: -------------------------------------------------------------------------------- 1 | # This is a .gitignore style file for 'GrantBirki/json-yaml-validate' Action workflow 2 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Node.js CI - Dockge 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | paths-ignore: 7 | - '*.md' 8 | pull_request: 9 | branches: [master] 10 | paths-ignore: 11 | - '*.md' 12 | 13 | jobs: 14 | ci: 15 | strategy: 16 | matrix: 17 | os: [ubuntu-latest, windows-latest, macos-latest, ARM, ARM64] 18 | node: [22] # Can be changed 19 | runs-on: ${{ matrix.os }} 20 | steps: 21 | - name: Checkout Code 22 | run: | # Mainly for Windows 23 | git config --global core.autocrlf false 24 | git config --global core.eol lf 25 | uses: actions/checkout@v4 26 | 27 | - name: Setup Node.js 28 | uses: actions/setup-node@v3 29 | with: 30 | node-version: ${{matrix.node}} 31 | 32 | - name: Install dependencies 33 | run: npm install 34 | 35 | - name: Lint 36 | run: npm run lint 37 | 38 | - name: Check Typescript 39 | run: npm run check-ts 40 | 41 | - name: Build 42 | run: npm run build:frontend 43 | # more things can be add later like tests etc.. 44 | 45 | -------------------------------------------------------------------------------- /.github/workflows/close-incorrect-issue.yml: -------------------------------------------------------------------------------- 1 | name: Close Incorrect Issue 2 | 3 | on: 4 | issues: 5 | types: [opened] 6 | 7 | jobs: 8 | close-incorrect-issue: 9 | runs-on: ${{ matrix.os }} 10 | 11 | strategy: 12 | matrix: 13 | os: [ubuntu-latest] 14 | node-version: [16] 15 | 16 | steps: 17 | - uses: actions/checkout@v3 18 | 19 | - name: Close Incorrect Issue 20 | run: node extra/close-incorrect-issue.js ${{ secrets.GITHUB_TOKEN }} ${{ github.event.issue.number }} ${{ github.event.issue.user.login }} 21 | -------------------------------------------------------------------------------- /.github/workflows/json-yaml-validate.yml: -------------------------------------------------------------------------------- 1 | name: json-yaml-validate 2 | on: 3 | push: 4 | branches: 5 | - master 6 | pull_request: 7 | branches: 8 | - master 9 | - 2.0.X 10 | workflow_dispatch: 11 | 12 | permissions: 13 | contents: read 14 | pull-requests: write # enable write permissions for pull request comments 15 | 16 | jobs: 17 | json-yaml-validate: 18 | runs-on: ubuntu-latest 19 | steps: 20 | - uses: actions/checkout@v4 21 | 22 | - name: json-yaml-validate 23 | id: json-yaml-validate 24 | uses: GrantBirki/json-yaml-validate@v2.6.1 25 | with: 26 | comment: "false" # enable comment mode 27 | exclude_file: ".github/config/exclude.txt" # gitignore style file for exclusions 28 | -------------------------------------------------------------------------------- /.github/workflows/prevent-file-change.yml: -------------------------------------------------------------------------------- 1 | name: Prevent File Change 2 | 3 | on: 4 | pull_request: 5 | 6 | jobs: 7 | check-file-changes: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Prevent file change 11 | uses: xalvarez/prevent-file-change-action@v1 12 | with: 13 | githubToken: ${{ secrets.GITHUB_TOKEN }} 14 | # Regex, /src/lang/*.json is not allowed to be changed, except for /src/lang/en.json 15 | pattern: '^(?!frontend/src/lang/en\.json$)frontend/src/lang/.*\.json$' 16 | trustedAuthors: UptimeKumaBot 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Should update .dockerignore as well 2 | .env 3 | node_modules 4 | .idea 5 | data 6 | stacks 7 | tmp 8 | /private 9 | 10 | # Git only 11 | frontend-dist 12 | 13 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Can I create a pull request for Dockge? 2 | 3 | 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. 4 | 5 | Here are some references: 6 | 7 | ### ✅ Usually accepted: 8 | - Bug fix 9 | - Security fix 10 | - Adding new language files (see [these instructions](https://github.com/louislam/dockge/blob/master/frontend/src/lang/README.md)) 11 | - Adding new language keys: `$t("...")` 12 | 13 | ### ⚠️ Discussion required: 14 | - Large pull requests 15 | - New features 16 | 17 | ### ❌ Won't be merged: 18 | - A dedicated PR for translating existing languages (see [these instructions](https://github.com/louislam/dockge/blob/master/frontend/src/lang/README.md)) 19 | - Do not pass the auto-test 20 | - Any breaking changes 21 | - Duplicated pull requests 22 | - Buggy 23 | - UI/UX is not close to Dockge 24 | - Modifications or deletions of existing logic without a valid reason. 25 | - Adding functions that is completely out of scope 26 | - Converting existing code into other programming languages 27 | - Unnecessarily large code changes that are hard to review and cause conflicts with other PRs. 28 | 29 | The above cases may not cover all possible situations. 30 | 31 | 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. 32 | 33 | I will assign your pull request to a [milestone](https://github.com/louislam/dockge/milestones), if I plan to review and merge it. 34 | 35 | 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. 36 | 37 | ## Project Styles 38 | 39 | I personally do not like something that requires so many configurations before you can finally start the app. 40 | 41 | - Settings should be configurable in the frontend. Environment variables are discouraged, unless it is related to startup such as `DOCKGE_STACKS_DIR` 42 | - Easy to use 43 | - The web UI styling should be consistent and nice 44 | - No native build dependency 45 | 46 | ## Coding Styles 47 | 48 | - 4 spaces indentation 49 | - Follow `.editorconfig` 50 | - Follow ESLint 51 | - Methods and functions should be documented with JSDoc 52 | 53 | ## Name Conventions 54 | 55 | - Javascript/Typescript: camelCaseType 56 | - SQLite: snake_case (Underscore) 57 | - CSS/SCSS: kebab-case (Dash) 58 | 59 | ## Tools 60 | 61 | - [`Node.js`](https://nodejs.org/) >= 22.14.0 62 | - [`git`](https://git-scm.com/) 63 | - IDE that supports [`ESLint`](https://eslint.org/) and EditorConfig (I am using [`IntelliJ IDEA`](https://www.jetbrains.com/idea/)) 64 | - A SQLite GUI tool (f.ex. [`SQLite Expert Personal`](https://www.sqliteexpert.com/download.html) or [`DBeaver Community`](https://dbeaver.io/download/)) 65 | 66 | ## Install Dependencies for Development 67 | 68 | ```bash 69 | npm install 70 | ``` 71 | 72 | ## Dev Server 73 | 74 | ``` 75 | npm run dev:frontend 76 | npm run dev:backend 77 | ``` 78 | 79 | ## Backend Dev Server 80 | 81 | It binds to `0.0.0.0:5001` by default. 82 | 83 | It is mainly a socket.io app + express.js. 84 | 85 | ## Frontend Dev Server 86 | 87 | It binds to `0.0.0.0:5000` by default. The frontend dev server is used for development only. 88 | 89 | For production, it is not used. It will be compiled to `frontend-dist` directory instead. 90 | 91 | You can use Vue.js devtools Chrome extension for debugging. 92 | 93 | ### Build the frontend 94 | 95 | ```bash 96 | npm run build 97 | ``` 98 | 99 | ## Database Migration 100 | 101 | TODO 102 | 103 | ## Dependencies 104 | 105 | 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: 106 | 107 | - Frontend dependencies = "devDependencies" 108 | - Examples: vue, chart.js 109 | - Backend dependencies = "dependencies" 110 | - Examples: socket.io, sqlite3 111 | - Development dependencies = "devDependencies" 112 | - Examples: eslint, sass 113 | 114 | ### Update Dependencies 115 | 116 | Should only be done by the maintainer. 117 | 118 | ```bash 119 | npm update 120 | ```` 121 | 122 | It should update the patch release version only. 123 | 124 | Patch release = the third digit ([Semantic Versioning](https://semver.org/)) 125 | 126 | If for security / bug / other reasons, a library must be updated, breaking changes need to be checked by the person proposing the change. 127 | 128 | ## Translations 129 | 130 | Please add **all** the strings which are translatable to `src/lang/en.json` (If translation keys are omitted, they can not be translated). 131 | 132 | **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`. 133 | The translations can then (after merging a PR into `master`) be translated by awesome people donating their language skills. 134 | 135 | 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). 136 | 137 | ## Spelling & Grammar 138 | 139 | Feel free to correct the grammar in the documentation or code. 140 | My mother language is not English and my grammar is not that great. 141 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Louis Lam 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Reporting a Vulnerability 4 | 5 | 1. Please report security issues to https://github.com/louislam/dockge/security/advisories/new. 6 | 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 7 | 8 | Do not use the public issue tracker or discuss it in public as it will cause more damage. 9 | 10 | ## Do you accept other 3rd-party bug bounty platforms? 11 | 12 | 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. 13 | -------------------------------------------------------------------------------- /backend/agent-socket-handler.ts: -------------------------------------------------------------------------------- 1 | import { DockgeServer } from "./dockge-server"; 2 | import { AgentSocket } from "../common/agent-socket"; 3 | import { DockgeSocket } from "./util-server"; 4 | 5 | export abstract class AgentSocketHandler { 6 | abstract create(socket : DockgeSocket, server : DockgeServer, agentSocket : AgentSocket): void; 7 | } 8 | -------------------------------------------------------------------------------- /backend/check-version.ts: -------------------------------------------------------------------------------- 1 | import { log } from "./log"; 2 | import compareVersions from "compare-versions"; 3 | import packageJSON from "../package.json"; 4 | import { Settings } from "./settings"; 5 | 6 | // How much time in ms to wait between update checks 7 | const UPDATE_CHECKER_INTERVAL_MS = 1000 * 60 * 60 * 48; 8 | const CHECK_URL = "https://dockge.kuma.pet/version"; 9 | 10 | class CheckVersion { 11 | version = packageJSON.version; 12 | latestVersion? : string; 13 | interval? : NodeJS.Timeout; 14 | 15 | async startInterval() { 16 | const check = async () => { 17 | if (await Settings.get("checkUpdate") === false) { 18 | return; 19 | } 20 | 21 | log.debug("update-checker", "Retrieving latest versions"); 22 | 23 | try { 24 | const res = await fetch(CHECK_URL); 25 | const data = await res.json(); 26 | 27 | // For debug 28 | if (process.env.TEST_CHECK_VERSION === "1") { 29 | data.slow = "1000.0.0"; 30 | } 31 | 32 | const checkBeta = await Settings.get("checkBeta"); 33 | 34 | if (checkBeta && data.beta) { 35 | if (compareVersions.compare(data.beta, data.slow, ">")) { 36 | this.latestVersion = data.beta; 37 | return; 38 | } 39 | } 40 | 41 | if (data.slow) { 42 | this.latestVersion = data.slow; 43 | } 44 | 45 | } catch (_) { 46 | log.info("update-checker", "Failed to check for new versions"); 47 | } 48 | 49 | }; 50 | 51 | await check(); 52 | this.interval = setInterval(check, UPDATE_CHECKER_INTERVAL_MS); 53 | } 54 | } 55 | 56 | const checkVersion = new CheckVersion(); 57 | export default checkVersion; 58 | -------------------------------------------------------------------------------- /backend/index.ts: -------------------------------------------------------------------------------- 1 | import { DockgeServer } from "./dockge-server"; 2 | import { log } from "./log"; 3 | 4 | log.info("server", "Welcome to dockge!"); 5 | const server = new DockgeServer(); 6 | await server.serve(); 7 | -------------------------------------------------------------------------------- /backend/migrations/2023-10-20-0829-setting-table.ts: -------------------------------------------------------------------------------- 1 | import { Knex } from "knex"; 2 | 3 | export async function up(knex: Knex): Promise { 4 | return knex.schema.createTable("setting", (table) => { 5 | table.increments("id"); 6 | table.string("key", 200).notNullable().unique().collate("utf8_general_ci"); 7 | table.text("value"); 8 | table.string("type", 20); 9 | }); 10 | } 11 | 12 | export async function down(knex: Knex): Promise { 13 | return knex.schema.dropTable("setting"); 14 | } 15 | -------------------------------------------------------------------------------- /backend/migrations/2023-10-20-0829-user-table.ts: -------------------------------------------------------------------------------- 1 | import { Knex } from "knex"; 2 | 3 | export async function up(knex: Knex): Promise { 4 | // Create the user table 5 | return knex.schema.createTable("user", (table) => { 6 | table.increments("id"); 7 | table.string("username", 255).notNullable().unique().collate("utf8_general_ci"); 8 | table.string("password", 255); 9 | table.boolean("active").notNullable().defaultTo(true); 10 | table.string("timezone", 150); 11 | table.string("twofa_secret", 64); 12 | table.boolean("twofa_status").notNullable().defaultTo(false); 13 | table.string("twofa_last_token", 6); 14 | }); 15 | } 16 | 17 | export async function down(knex: Knex): Promise { 18 | return knex.schema.dropTable("user"); 19 | } 20 | -------------------------------------------------------------------------------- /backend/migrations/2023-12-20-2117-agent-table.ts: -------------------------------------------------------------------------------- 1 | import { Knex } from "knex"; 2 | 3 | export async function up(knex: Knex): Promise { 4 | // Create the user table 5 | return knex.schema.createTable("agent", (table) => { 6 | table.increments("id"); 7 | table.string("url", 255).notNullable().unique(); 8 | table.string("username", 255).notNullable(); 9 | table.string("password", 255).notNullable(); 10 | table.boolean("active").notNullable().defaultTo(true); 11 | }); 12 | } 13 | 14 | export async function down(knex: Knex): Promise { 15 | return knex.schema.dropTable("agent"); 16 | } 17 | -------------------------------------------------------------------------------- /backend/models/agent.ts: -------------------------------------------------------------------------------- 1 | import { BeanModel } from "redbean-node/dist/bean-model"; 2 | import { R } from "redbean-node"; 3 | import { LooseObject } from "../../common/util-common"; 4 | 5 | export class Agent extends BeanModel { 6 | 7 | static async getAgentList() : Promise> { 8 | let list = await R.findAll("agent") as Agent[]; 9 | let result : Record = {}; 10 | for (let agent of list) { 11 | result[agent.endpoint] = agent; 12 | } 13 | return result; 14 | } 15 | 16 | get endpoint() : string { 17 | let obj = new URL(this.url); 18 | return obj.host; 19 | } 20 | 21 | toJSON() : LooseObject { 22 | return { 23 | url: this.url, 24 | username: this.username, 25 | endpoint: this.endpoint, 26 | }; 27 | } 28 | 29 | } 30 | 31 | export default Agent; 32 | -------------------------------------------------------------------------------- /backend/models/user.ts: -------------------------------------------------------------------------------- 1 | import jwt from "jsonwebtoken"; 2 | import { R } from "redbean-node"; 3 | import { BeanModel } from "redbean-node/dist/bean-model"; 4 | import { generatePasswordHash, shake256, SHAKE256_LENGTH } from "../password-hash"; 5 | 6 | export class User extends BeanModel { 7 | /** 8 | * Reset user password 9 | * Fix #1510, as in the context reset-password.js, there is no auto model mapping. Call this static function instead. 10 | * @param {number} userID ID of user to update 11 | * @param {string} newPassword Users new password 12 | * @returns {Promise} 13 | */ 14 | static async resetPassword(userID : number, newPassword : string) { 15 | await R.exec("UPDATE `user` SET password = ? WHERE id = ? ", [ 16 | generatePasswordHash(newPassword), 17 | userID 18 | ]); 19 | } 20 | 21 | /** 22 | * Reset this users password 23 | * @param {string} newPassword 24 | * @returns {Promise} 25 | */ 26 | async resetPassword(newPassword : string) { 27 | await User.resetPassword(this.id, newPassword); 28 | this.password = newPassword; 29 | } 30 | 31 | /** 32 | * Create a new JWT for a user 33 | * @param {User} user The User to create a JsonWebToken for 34 | * @param {string} jwtSecret The key used to sign the JsonWebToken 35 | * @returns {string} the JsonWebToken as a string 36 | */ 37 | static createJWT(user : User, jwtSecret : string) { 38 | return jwt.sign({ 39 | username: user.username, 40 | h: shake256(user.password, SHAKE256_LENGTH), 41 | }, jwtSecret); 42 | } 43 | 44 | } 45 | 46 | export default User; 47 | -------------------------------------------------------------------------------- /backend/password-hash.ts: -------------------------------------------------------------------------------- 1 | import bcrypt from "bcryptjs"; 2 | import crypto from "crypto"; 3 | const saltRounds = 10; 4 | 5 | /** 6 | * Hash a password 7 | * @param {string} password Password to hash 8 | * @returns {string} Hash 9 | */ 10 | export function generatePasswordHash(password : string) { 11 | return bcrypt.hashSync(password, saltRounds); 12 | } 13 | 14 | /** 15 | * Verify a password against a hash 16 | * @param {string} password Password to verify 17 | * @param {string} hash Hash to verify against 18 | * @returns {boolean} Does the password match the hash? 19 | */ 20 | export function verifyPassword(password : string, hash : string) { 21 | return bcrypt.compareSync(password, hash); 22 | } 23 | 24 | /** 25 | * Does the hash need to be rehashed? 26 | * @param {string} hash Hash to check 27 | * @returns {boolean} Needs to be rehashed? 28 | */ 29 | export function needRehashPassword(hash : string) : boolean { 30 | return false; 31 | } 32 | 33 | export const SHAKE256_LENGTH = 16; 34 | 35 | /** 36 | * @param {string} data The data to be hashed 37 | * @param {number} len Output length of the hash 38 | * @returns {string} The hashed data in hex format 39 | */ 40 | export function shake256(data : string, len : number) { 41 | if (!data) { 42 | return ""; 43 | } 44 | return crypto.createHash("shake256", { outputLength: len }) 45 | .update(data) 46 | .digest("hex"); 47 | } 48 | -------------------------------------------------------------------------------- /backend/rate-limiter.ts: -------------------------------------------------------------------------------- 1 | // "limit" is bugged in Typescript, use "limiter-es6-compat" instead 2 | // See https://github.com/jhurliman/node-rate-limiter/issues/80 3 | import { RateLimiter, RateLimiterOpts } from "limiter-es6-compat"; 4 | import { log } from "./log"; 5 | 6 | export interface KumaRateLimiterOpts extends RateLimiterOpts { 7 | errorMessage : string; 8 | } 9 | 10 | export type KumaRateLimiterCallback = (err : object) => void; 11 | 12 | class KumaRateLimiter { 13 | 14 | errorMessage : string; 15 | rateLimiter : RateLimiter; 16 | 17 | /** 18 | * @param {object} config Rate limiter configuration object 19 | */ 20 | constructor(config : KumaRateLimiterOpts) { 21 | this.errorMessage = config.errorMessage; 22 | this.rateLimiter = new RateLimiter(config); 23 | } 24 | 25 | /** 26 | * Callback for pass 27 | * @callback passCB 28 | * @param {object} err Too many requests 29 | */ 30 | 31 | /** 32 | * Should the request be passed through 33 | * @param callback Callback function to call with decision 34 | * @param {number} num Number of tokens to remove 35 | * @returns {Promise} Should the request be allowed? 36 | */ 37 | async pass(callback : KumaRateLimiterCallback, num = 1) { 38 | const remainingRequests = await this.removeTokens(num); 39 | log.info("rate-limit", "remaining requests: " + remainingRequests); 40 | if (remainingRequests < 0) { 41 | if (callback) { 42 | callback({ 43 | ok: false, 44 | msg: this.errorMessage, 45 | }); 46 | } 47 | return false; 48 | } 49 | return true; 50 | } 51 | 52 | /** 53 | * Remove a given number of tokens 54 | * @param {number} num Number of tokens to remove 55 | * @returns {Promise} Number of remaining tokens 56 | */ 57 | async removeTokens(num = 1) { 58 | return await this.rateLimiter.removeTokens(num); 59 | } 60 | } 61 | 62 | export const loginRateLimiter = new KumaRateLimiter({ 63 | tokensPerInterval: 20, 64 | interval: "minute", 65 | fireImmediately: true, 66 | errorMessage: "Too frequently, try again later." 67 | }); 68 | 69 | export const apiRateLimiter = new KumaRateLimiter({ 70 | tokensPerInterval: 60, 71 | interval: "minute", 72 | fireImmediately: true, 73 | errorMessage: "Too frequently, try again later." 74 | }); 75 | 76 | export const twoFaRateLimiter = new KumaRateLimiter({ 77 | tokensPerInterval: 30, 78 | interval: "minute", 79 | fireImmediately: true, 80 | errorMessage: "Too frequently, try again later." 81 | }); 82 | -------------------------------------------------------------------------------- /backend/router.ts: -------------------------------------------------------------------------------- 1 | import { DockgeServer } from "./dockge-server"; 2 | import { Express, Router as ExpressRouter } from "express"; 3 | 4 | export abstract class Router { 5 | abstract create(app : Express, server : DockgeServer): ExpressRouter; 6 | } 7 | -------------------------------------------------------------------------------- /backend/routers/main-router.ts: -------------------------------------------------------------------------------- 1 | import { DockgeServer } from "../dockge-server"; 2 | import { Router } from "../router"; 3 | import express, { Express, Router as ExpressRouter } from "express"; 4 | 5 | export class MainRouter extends Router { 6 | create(app: Express, server: DockgeServer): ExpressRouter { 7 | const router = express.Router(); 8 | 9 | router.get("/", (req, res) => { 10 | res.send(server.indexHTML); 11 | }); 12 | 13 | // Robots.txt 14 | router.get("/robots.txt", async (_request, response) => { 15 | let txt = "User-agent: *\nDisallow: /"; 16 | response.setHeader("Content-Type", "text/plain"); 17 | response.send(txt); 18 | }); 19 | 20 | return router; 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /backend/settings.ts: -------------------------------------------------------------------------------- 1 | import { R } from "redbean-node"; 2 | import { log } from "./log"; 3 | import { LooseObject } from "../common/util-common"; 4 | 5 | export class Settings { 6 | 7 | /** 8 | * Example: 9 | * { 10 | * key1: { 11 | * value: "value2", 12 | * timestamp: 12345678 13 | * }, 14 | * key2: { 15 | * value: 2, 16 | * timestamp: 12345678 17 | * }, 18 | * } 19 | */ 20 | static cacheList : LooseObject = { 21 | 22 | }; 23 | 24 | static cacheCleaner? : NodeJS.Timeout; 25 | 26 | /** 27 | * Retrieve value of setting based on key 28 | * @param key Key of setting to retrieve 29 | * @returns Value 30 | */ 31 | static async get(key : string) { 32 | 33 | // Start cache clear if not started yet 34 | if (!Settings.cacheCleaner) { 35 | Settings.cacheCleaner = setInterval(() => { 36 | log.debug("settings", "Cache Cleaner is just started."); 37 | for (key in Settings.cacheList) { 38 | if (Date.now() - Settings.cacheList[key].timestamp > 60 * 1000) { 39 | log.debug("settings", "Cache Cleaner deleted: " + key); 40 | delete Settings.cacheList[key]; 41 | } 42 | } 43 | 44 | }, 60 * 1000); 45 | } 46 | 47 | // Query from cache 48 | if (key in Settings.cacheList) { 49 | const v = Settings.cacheList[key].value; 50 | log.debug("settings", `Get Setting (cache): ${key}: ${v}`); 51 | return v; 52 | } 53 | 54 | const value = await R.getCell("SELECT `value` FROM setting WHERE `key` = ? ", [ 55 | key, 56 | ]); 57 | 58 | try { 59 | const v = JSON.parse(value); 60 | log.debug("settings", `Get Setting: ${key}: ${v}`); 61 | 62 | Settings.cacheList[key] = { 63 | value: v, 64 | timestamp: Date.now() 65 | }; 66 | 67 | return v; 68 | } catch (e) { 69 | return value; 70 | } 71 | } 72 | 73 | /** 74 | * Sets the specified setting to specified value 75 | * @param key Key of setting to set 76 | * @param value Value to set to 77 | * @param {?string} type Type of setting 78 | * @returns {Promise} 79 | */ 80 | static async set(key : string, value : object | string | number | boolean, type : string | null = null) { 81 | 82 | let bean = await R.findOne("setting", " `key` = ? ", [ 83 | key, 84 | ]); 85 | if (!bean) { 86 | bean = R.dispense("setting"); 87 | bean.key = key; 88 | } 89 | bean.type = type; 90 | bean.value = JSON.stringify(value); 91 | await R.store(bean); 92 | 93 | Settings.deleteCache([ key ]); 94 | } 95 | 96 | /** 97 | * Get settings based on type 98 | * @param type The type of setting 99 | * @returns Settings 100 | */ 101 | static async getSettings(type : string) { 102 | const list = await R.getAll("SELECT `key`, `value` FROM setting WHERE `type` = ? ", [ 103 | type, 104 | ]); 105 | 106 | const result : LooseObject = {}; 107 | 108 | for (const row of list) { 109 | try { 110 | result[row.key] = JSON.parse(row.value); 111 | } catch (e) { 112 | result[row.key] = row.value; 113 | } 114 | } 115 | 116 | return result; 117 | } 118 | 119 | /** 120 | * Set settings based on type 121 | * @param type Type of settings to set 122 | * @param data Values of settings 123 | * @returns {Promise} 124 | */ 125 | static async setSettings(type : string, data : LooseObject) { 126 | const keyList = Object.keys(data); 127 | 128 | const promiseList = []; 129 | 130 | for (const key of keyList) { 131 | let bean = await R.findOne("setting", " `key` = ? ", [ 132 | key 133 | ]); 134 | 135 | if (bean == null) { 136 | bean = R.dispense("setting"); 137 | bean.type = type; 138 | bean.key = key; 139 | } 140 | 141 | if (bean.type === type) { 142 | bean.value = JSON.stringify(data[key]); 143 | promiseList.push(R.store(bean)); 144 | } 145 | } 146 | 147 | await Promise.all(promiseList); 148 | 149 | Settings.deleteCache(keyList); 150 | } 151 | 152 | /** 153 | * Delete selected keys from settings cache 154 | * @param {string[]} keyList Keys to remove 155 | * @returns {void} 156 | */ 157 | static deleteCache(keyList : string[]) { 158 | for (const key of keyList) { 159 | delete Settings.cacheList[key]; 160 | } 161 | } 162 | 163 | /** 164 | * Stop the cache cleaner if running 165 | * @returns {void} 166 | */ 167 | static stopCacheCleaner() { 168 | if (Settings.cacheCleaner) { 169 | clearInterval(Settings.cacheCleaner); 170 | Settings.cacheCleaner = undefined; 171 | } 172 | } 173 | } 174 | 175 | -------------------------------------------------------------------------------- /backend/socket-handler.ts: -------------------------------------------------------------------------------- 1 | import { DockgeServer } from "./dockge-server"; 2 | import { DockgeSocket } from "./util-server"; 3 | 4 | export abstract class SocketHandler { 5 | abstract create(socket : DockgeSocket, server : DockgeServer): void; 6 | } 7 | -------------------------------------------------------------------------------- /backend/socket-handlers/agent-proxy-socket-handler.ts: -------------------------------------------------------------------------------- 1 | import { SocketHandler } from "../socket-handler.js"; 2 | import { DockgeServer } from "../dockge-server"; 3 | import { log } from "../log"; 4 | import { checkLogin, DockgeSocket } from "../util-server"; 5 | import { AgentSocket } from "../../common/agent-socket"; 6 | import { ALL_ENDPOINTS } from "../../common/util-common"; 7 | 8 | export class AgentProxySocketHandler extends SocketHandler { 9 | 10 | create2(socket : DockgeSocket, server : DockgeServer, agentSocket : AgentSocket) { 11 | // Agent - proxying requests if needed 12 | socket.on("agent", async (endpoint : unknown, eventName : unknown, ...args : unknown[]) => { 13 | try { 14 | checkLogin(socket); 15 | 16 | // Check Type 17 | if (typeof(endpoint) !== "string") { 18 | throw new Error("Endpoint must be a string: " + endpoint); 19 | } 20 | if (typeof(eventName) !== "string") { 21 | throw new Error("Event name must be a string"); 22 | } 23 | 24 | if (endpoint === ALL_ENDPOINTS) { // Send to all endpoints 25 | log.debug("agent", "Sending to all endpoints: " + eventName); 26 | socket.instanceManager.emitToAllEndpoints(eventName, ...args); 27 | 28 | } else if (!endpoint || endpoint === socket.endpoint) { // Direct connection or matching endpoint 29 | log.debug("agent", "Matched endpoint: " + eventName); 30 | agentSocket.call(eventName, ...args); 31 | 32 | } else { 33 | log.debug("agent", "Proxying request to " + endpoint + " for " + eventName); 34 | await socket.instanceManager.emitToEndpoint(endpoint, eventName, ...args); 35 | } 36 | } catch (e) { 37 | if (e instanceof Error) { 38 | log.warn("agent", e.message); 39 | } 40 | } 41 | }); 42 | } 43 | 44 | create(socket : DockgeSocket, server : DockgeServer) { 45 | throw new Error("Method not implemented. Please use create2 instead."); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /backend/socket-handlers/manage-agent-socket-handler.ts: -------------------------------------------------------------------------------- 1 | import { SocketHandler } from "../socket-handler.js"; 2 | import { DockgeServer } from "../dockge-server"; 3 | import { log } from "../log"; 4 | import { callbackError, callbackResult, checkLogin, DockgeSocket } from "../util-server"; 5 | import { LooseObject } from "../../common/util-common"; 6 | 7 | export class ManageAgentSocketHandler extends SocketHandler { 8 | 9 | create(socket : DockgeSocket, server : DockgeServer) { 10 | // addAgent 11 | socket.on("addAgent", async (requestData : unknown, callback : unknown) => { 12 | try { 13 | log.debug("manage-agent-socket-handler", "addAgent"); 14 | checkLogin(socket); 15 | 16 | if (typeof(requestData) !== "object") { 17 | throw new Error("Data must be an object"); 18 | } 19 | 20 | let data = requestData as LooseObject; 21 | let manager = socket.instanceManager; 22 | await manager.test(data.url, data.username, data.password); 23 | await manager.add(data.url, data.username, data.password); 24 | 25 | // connect to the agent 26 | manager.connect(data.url, data.username, data.password); 27 | 28 | // Refresh another sockets 29 | // It is a bit difficult to control another browser sessions to connect/disconnect agents, so force them to refresh the page will be easier. 30 | server.disconnectAllSocketClients(undefined, socket.id); 31 | manager.sendAgentList(); 32 | 33 | callbackResult({ 34 | ok: true, 35 | msg: "agentAddedSuccessfully", 36 | msgi18n: true, 37 | }, callback); 38 | 39 | } catch (e) { 40 | callbackError(e, callback); 41 | } 42 | }); 43 | 44 | // removeAgent 45 | socket.on("removeAgent", async (url : unknown, callback : unknown) => { 46 | try { 47 | log.debug("manage-agent-socket-handler", "removeAgent"); 48 | checkLogin(socket); 49 | 50 | if (typeof(url) !== "string") { 51 | throw new Error("URL must be a string"); 52 | } 53 | 54 | let manager = socket.instanceManager; 55 | await manager.remove(url); 56 | 57 | server.disconnectAllSocketClients(undefined, socket.id); 58 | manager.sendAgentList(); 59 | 60 | callbackResult({ 61 | ok: true, 62 | msg: "agentRemovedSuccessfully", 63 | msgi18n: true, 64 | }, callback); 65 | } catch (e) { 66 | callbackError(e, callback); 67 | } 68 | }); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /backend/util-server.ts: -------------------------------------------------------------------------------- 1 | import { Socket } from "socket.io"; 2 | import { Terminal } from "./terminal"; 3 | import { randomBytes } from "crypto"; 4 | import { log } from "./log"; 5 | import { ERROR_TYPE_VALIDATION } from "../common/util-common"; 6 | import { R } from "redbean-node"; 7 | import { verifyPassword } from "./password-hash"; 8 | import fs from "fs"; 9 | import { AgentManager } from "./agent-manager"; 10 | 11 | export interface JWTDecoded { 12 | username : string; 13 | h? : string; 14 | } 15 | 16 | export interface DockgeSocket extends Socket { 17 | userID: number; 18 | consoleTerminal? : Terminal; 19 | instanceManager : AgentManager; 20 | endpoint : string; 21 | emitAgent : (eventName : string, ...args : unknown[]) => void; 22 | } 23 | 24 | // For command line arguments, so they are nullable 25 | export interface Arguments { 26 | sslKey? : string; 27 | sslCert? : string; 28 | sslKeyPassphrase? : string; 29 | port? : number; 30 | hostname? : string; 31 | dataDir? : string; 32 | stacksDir? : string; 33 | enableConsole? : boolean; 34 | } 35 | 36 | // Some config values are required 37 | export interface Config extends Arguments { 38 | dataDir : string; 39 | stacksDir : string; 40 | } 41 | 42 | export function checkLogin(socket : DockgeSocket) { 43 | if (!socket.userID) { 44 | throw new Error("You are not logged in."); 45 | } 46 | } 47 | 48 | export class ValidationError extends Error { 49 | constructor(message : string) { 50 | super(message); 51 | } 52 | } 53 | 54 | export function callbackError(error : unknown, callback : unknown) { 55 | if (typeof(callback) !== "function") { 56 | log.error("console", "Callback is not a function"); 57 | return; 58 | } 59 | 60 | if (error instanceof Error) { 61 | callback({ 62 | ok: false, 63 | msg: error.message, 64 | msgi18n: true, 65 | }); 66 | } else if (error instanceof ValidationError) { 67 | callback({ 68 | ok: false, 69 | type: ERROR_TYPE_VALIDATION, 70 | msg: error.message, 71 | msgi18n: true, 72 | }); 73 | } else { 74 | log.debug("console", "Unknown error: " + error); 75 | } 76 | } 77 | 78 | export function callbackResult(result : unknown, callback : unknown) { 79 | if (typeof(callback) !== "function") { 80 | log.error("console", "Callback is not a function"); 81 | return; 82 | } 83 | callback(result); 84 | } 85 | 86 | export async function doubleCheckPassword(socket : DockgeSocket, currentPassword : unknown) { 87 | if (typeof currentPassword !== "string") { 88 | throw new Error("Wrong data type?"); 89 | } 90 | 91 | let user = await R.findOne("user", " id = ? AND active = 1 ", [ 92 | socket.userID, 93 | ]); 94 | 95 | if (!user || !verifyPassword(currentPassword, user.password)) { 96 | throw new Error("Incorrect current password"); 97 | } 98 | 99 | return user; 100 | } 101 | 102 | export function fileExists(file : string) { 103 | return fs.promises.access(file, fs.constants.F_OK) 104 | .then(() => true) 105 | .catch(() => false); 106 | } 107 | -------------------------------------------------------------------------------- /backend/utils/limit-queue.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Limit Queue 3 | * The first element will be removed when the length exceeds the limit 4 | */ 5 | export class LimitQueue extends Array { 6 | __limit; 7 | __onExceed? : (item : T | undefined) => void; 8 | 9 | constructor(limit: number) { 10 | super(); 11 | this.__limit = limit; 12 | } 13 | 14 | pushItem(value : T) { 15 | super.push(value); 16 | if (this.length > this.__limit) { 17 | const item = this.shift(); 18 | if (this.__onExceed) { 19 | this.__onExceed(item); 20 | } 21 | } 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /common/agent-socket.ts: -------------------------------------------------------------------------------- 1 | export class AgentSocket { 2 | 3 | eventList : Map void> = new Map(); 4 | 5 | on(event : string, callback : (...args : unknown[]) => void) { 6 | this.eventList.set(event, callback); 7 | } 8 | 9 | call(eventName : string, ...args : unknown[]) { 10 | const callback = this.eventList.get(eventName); 11 | if (callback) { 12 | callback(...args); 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /compose.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | dockge: 3 | image: louislam/dockge:1 4 | restart: unless-stopped 5 | ports: 6 | # Host Port : Container Port 7 | - 5001:5001 8 | volumes: 9 | - /var/run/docker.sock:/var/run/docker.sock 10 | - ./data:/app/data 11 | 12 | # If you want to use private registries, you need to share the auth file with Dockge: 13 | # - /root/.docker/:/root/.docker 14 | 15 | # Stacks Directory 16 | # ⚠️ READ IT CAREFULLY. If you did it wrong, your data could end up writing into a WRONG PATH. 17 | # ⚠️ 1. FULL path only. No relative path (MUST) 18 | # ⚠️ 2. Left Stacks Path === Right Stacks Path (MUST) 19 | - /opt/stacks:/opt/stacks 20 | environment: 21 | # Tell Dockge where is your stacks directory 22 | - DOCKGE_STACKS_DIR=/opt/stacks 23 | -------------------------------------------------------------------------------- /docker/Base.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:22-bookworm-slim 2 | RUN apt update && apt install --yes --no-install-recommends \ 3 | curl \ 4 | ca-certificates \ 5 | gnupg \ 6 | unzip \ 7 | dumb-init \ 8 | && install -m 0755 -d /etc/apt/keyrings \ 9 | && curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg \ 10 | && chmod a+r /etc/apt/keyrings/docker.gpg \ 11 | && echo \ 12 | "deb [arch="$(dpkg --print-architecture)" signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/debian \ 13 | "$(. /etc/os-release && echo "$VERSION_CODENAME")" stable" | \ 14 | tee /etc/apt/sources.list.d/docker.list > /dev/null \ 15 | && apt update \ 16 | && apt --yes --no-install-recommends install \ 17 | docker-ce-cli \ 18 | docker-compose-plugin \ 19 | && rm -rf /var/lib/apt/lists/* \ 20 | && npm install -g tsx 21 | -------------------------------------------------------------------------------- /docker/BuildHealthCheck.Dockerfile: -------------------------------------------------------------------------------- 1 | ############################################ 2 | # Build in Golang 3 | ############################################ 4 | FROM golang:1.21.4-bookworm 5 | WORKDIR /app 6 | ARG TARGETPLATFORM 7 | COPY ./extra/healthcheck.go ./extra/healthcheck.go 8 | 9 | # Compile healthcheck.go 10 | RUN go build -x -o ./extra/healthcheck ./extra/healthcheck.go 11 | -------------------------------------------------------------------------------- /docker/Dockerfile: -------------------------------------------------------------------------------- 1 | ############################################ 2 | # Healthcheck Binary 3 | ############################################ 4 | FROM louislam/dockge:build-healthcheck AS build_healthcheck 5 | 6 | ############################################ 7 | # Build 8 | ############################################ 9 | FROM louislam/dockge:base AS build 10 | WORKDIR /app 11 | COPY --chown=node:node ./package.json ./package.json 12 | COPY --chown=node:node ./package-lock.json ./package-lock.json 13 | RUN npm ci --omit=dev 14 | 15 | ############################################ 16 | # ⭐ Main Image 17 | ############################################ 18 | FROM louislam/dockge:base AS release 19 | WORKDIR /app 20 | COPY --chown=node:node --from=build_healthcheck /app/extra/healthcheck /app/extra/healthcheck 21 | COPY --from=build /app/node_modules /app/node_modules 22 | COPY --chown=node:node . . 23 | RUN mkdir ./data 24 | 25 | 26 | # It is just for safe, as by default, it is disabled in the latest Node.js now. 27 | # Read more: 28 | # - https://github.com/sagemathinc/cocalc/issues/6963 29 | # - https://github.com/microsoft/node-pty/issues/630#issuecomment-1987212447 30 | ENV UV_USE_IO_URING=0 31 | 32 | VOLUME /app/data 33 | EXPOSE 5001 34 | HEALTHCHECK --interval=60s --timeout=30s --start-period=60s --retries=5 CMD extra/healthcheck 35 | ENTRYPOINT ["/usr/bin/dumb-init", "--"] 36 | CMD ["tsx", "./backend/index.ts"] 37 | 38 | ############################################ 39 | # Mark as Nightly 40 | ############################################ 41 | FROM release AS nightly 42 | RUN npm run mark-as-nightly 43 | -------------------------------------------------------------------------------- /extra/close-incorrect-issue.js: -------------------------------------------------------------------------------- 1 | import github from "@actions/github"; 2 | 3 | (async () => { 4 | try { 5 | const token = process.argv[2]; 6 | const issueNumber = process.argv[3]; 7 | const username = process.argv[4]; 8 | 9 | const client = github.getOctokit(token).rest; 10 | 11 | const issue = { 12 | owner: "louislam", 13 | repo: "dockge", 14 | number: issueNumber, 15 | }; 16 | 17 | const labels = ( 18 | await client.issues.listLabelsOnIssue({ 19 | owner: issue.owner, 20 | repo: issue.repo, 21 | issue_number: issue.number 22 | }) 23 | ).data.map(({ name }) => name); 24 | 25 | if (labels.length === 0) { 26 | console.log("Bad format here"); 27 | 28 | await client.issues.addLabels({ 29 | owner: issue.owner, 30 | repo: issue.repo, 31 | issue_number: issue.number, 32 | labels: [ "invalid-format" ] 33 | }); 34 | 35 | // Add the issue closing comment 36 | await client.issues.createComment({ 37 | owner: issue.owner, 38 | repo: issue.repo, 39 | issue_number: issue.number, 40 | 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.` 41 | }); 42 | 43 | // Close the issue 44 | await client.issues.update({ 45 | owner: issue.owner, 46 | repo: issue.repo, 47 | issue_number: issue.number, 48 | state: "closed" 49 | }); 50 | } else { 51 | console.log("Pass!"); 52 | } 53 | } catch (e) { 54 | console.log(e); 55 | } 56 | 57 | })(); 58 | -------------------------------------------------------------------------------- /extra/env2arg.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import childProcess from "child_process"; 4 | 5 | let env = process.env; 6 | 7 | let cmd = process.argv[2]; 8 | let args = process.argv.slice(3); 9 | let replacedArgs = []; 10 | 11 | for (let arg of args) { 12 | for (let key in env) { 13 | arg = arg.replaceAll(`$${key}`, env[key]); 14 | } 15 | replacedArgs.push(arg); 16 | } 17 | 18 | let child = childProcess.spawn(cmd, replacedArgs); 19 | child.stdout.pipe(process.stdout); 20 | child.stderr.pipe(process.stderr); 21 | -------------------------------------------------------------------------------- /extra/healthcheck.go: -------------------------------------------------------------------------------- 1 | /* 2 | * If changed, have to run `npm run build-docker-builder-go`. 3 | * This script should be run after a period of time (180s), because the server may need some time to prepare. 4 | */ 5 | package main 6 | 7 | import ( 8 | "crypto/tls" 9 | "io/ioutil" 10 | "log" 11 | "net/http" 12 | "os" 13 | "strings" 14 | "time" 15 | ) 16 | 17 | func main() { 18 | // Is K8S + "dockge" as the container name 19 | // See https://github.com/louislam/uptime-kuma/pull/2083 20 | isK8s := strings.HasPrefix(os.Getenv("DOCKGE_PORT"), "tcp://") 21 | 22 | // process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0"; 23 | http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{ 24 | InsecureSkipVerify: true, 25 | } 26 | 27 | client := http.Client{ 28 | Timeout: 28 * time.Second, 29 | } 30 | 31 | sslKey := os.Getenv("DOCKGE_SSL_KEY") 32 | sslCert := os.Getenv("DOCKGE_SSL_CERT") 33 | 34 | hostname := os.Getenv("DOCKGE_HOST") 35 | if len(hostname) == 0 { 36 | hostname = "127.0.0.1" 37 | } 38 | 39 | port := "" 40 | // DOCKGE_PORT is override by K8S unexpectedly, 41 | if !isK8s { 42 | port = os.Getenv("DOCKGE_PORT") 43 | } 44 | if len(port) == 0 { 45 | port = "5001" 46 | } 47 | 48 | protocol := "" 49 | if len(sslKey) != 0 && len(sslCert) != 0 { 50 | protocol = "https" 51 | } else { 52 | protocol = "http" 53 | } 54 | 55 | url := protocol + "://" + hostname + ":" + port 56 | 57 | log.Println("Checking " + url) 58 | resp, err := client.Get(url) 59 | 60 | if err != nil { 61 | log.Fatalln(err) 62 | } 63 | 64 | defer resp.Body.Close() 65 | 66 | _, err = ioutil.ReadAll(resp.Body) 67 | 68 | if err != nil { 69 | log.Fatalln(err) 70 | } 71 | 72 | log.Printf("Health Check OK [Res Code: %d]\n", resp.StatusCode) 73 | 74 | } 75 | -------------------------------------------------------------------------------- /extra/mark-as-nightly.ts: -------------------------------------------------------------------------------- 1 | import pkg from "../package.json"; 2 | import fs from "fs"; 3 | import dayjs from "dayjs"; 4 | 5 | const oldVersion = pkg.version; 6 | const newVersion = oldVersion + "-nightly-" + dayjs().format("YYYYMMDDHHmmss"); 7 | 8 | console.log("Old Version: " + oldVersion); 9 | console.log("New Version: " + newVersion); 10 | 11 | if (newVersion) { 12 | // Process package.json 13 | pkg.version = newVersion; 14 | //pkg.scripts.setup = pkg.scripts.setup.replaceAll(oldVersion, newVersion); 15 | //pkg.scripts["build-docker"] = pkg.scripts["build-docker"].replaceAll(oldVersion, newVersion); 16 | fs.writeFileSync("package.json", JSON.stringify(pkg, null, 4) + "\n"); 17 | 18 | // Process README.md 19 | if (fs.existsSync("README.md")) { 20 | fs.writeFileSync("README.md", fs.readFileSync("README.md", "utf8").replaceAll(oldVersion, newVersion)); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /extra/reformat-changelog.ts: -------------------------------------------------------------------------------- 1 | // Generate on GitHub 2 | const input = ` 3 | * Fixed envsubst issue by @louislam in https://github.com/louislam/dockge/pull/301 4 | * Fix: Only adding folders to stack with a compose file. by @Ozy-Viking in https://github.com/louislam/dockge/pull/299 5 | * Terminal text cols adjusts to terminal container. by @Ozy-Viking in https://github.com/louislam/dockge/pull/285 6 | * Update Docker Dompose plugin to 2.23.3 by @louislam in https://github.com/louislam/dockge/pull/303 7 | * Translations update from Kuma Weblate by @UptimeKumaBot in https://github.com/louislam/dockge/pull/302 8 | `; 9 | 10 | const template = ` 11 | 12 | > [!WARNING] 13 | > 14 | 15 | ### 🆕 New Features 16 | - 17 | 18 | ### ⬆️ Improvements 19 | - 20 | 21 | ### 🐛 Bug Fixes 22 | - 23 | 24 | ### 🦎 Translation Contributions 25 | - 26 | 27 | ### ⬆️ Security Fixes 28 | - 29 | 30 | ### Others 31 | - Other small changes, code refactoring and comment/doc updates in this repo: 32 | - 33 | 34 | Please let me know if your username is missing, if your pull request has been merged in this version, or your commit has been included in one of the pull requests. 35 | `; 36 | 37 | const lines = input.split("\n").filter((line) => line.trim() !== ""); 38 | 39 | for (const line of lines) { 40 | // Split the last " by " 41 | const usernamePullRequesURL = line.split(" by ").pop(); 42 | 43 | if (!usernamePullRequesURL) { 44 | console.log("Unable to parse", line); 45 | continue; 46 | } 47 | 48 | const [ username, pullRequestURL ] = usernamePullRequesURL.split(" in "); 49 | const pullRequestID = "#" + pullRequestURL.split("/").pop(); 50 | let message = line.split(" by ").shift(); 51 | 52 | if (!message) { 53 | console.log("Unable to parse", line); 54 | continue; 55 | } 56 | 57 | message = message.split("* ").pop(); 58 | 59 | let thanks = ""; 60 | if (username != "@louislam") { 61 | thanks = `(Thanks ${username})`; 62 | } 63 | 64 | console.log(pullRequestID, message, thanks); 65 | } 66 | console.log(template); 67 | -------------------------------------------------------------------------------- /extra/reset-password.ts: -------------------------------------------------------------------------------- 1 | import { Database } from "../backend/database"; 2 | import { R } from "redbean-node"; 3 | import readline from "readline"; 4 | import { User } from "../backend/models/user"; 5 | import { DockgeServer } from "../backend/dockge-server"; 6 | import { log } from "../backend/log"; 7 | import { io } from "socket.io-client"; 8 | import { BaseRes } from "../common/util-common"; 9 | 10 | console.log("== Dockge Reset Password Tool =="); 11 | 12 | const rl = readline.createInterface({ 13 | input: process.stdin, 14 | output: process.stdout 15 | }); 16 | 17 | const server = new DockgeServer(); 18 | 19 | export const main = async () => { 20 | // Check if 21 | console.log("Connecting the database"); 22 | try { 23 | await Database.init(server); 24 | } catch (e) { 25 | if (e instanceof Error) { 26 | log.error("server", "Failed to connect to your database: " + e.message); 27 | } 28 | process.exit(1); 29 | } 30 | 31 | try { 32 | // No need to actually reset the password for testing, just make sure no connection problem. It is ok for now. 33 | if (!process.env.TEST_BACKEND) { 34 | const user = await R.findOne("user"); 35 | if (! user) { 36 | throw new Error("user not found, have you installed?"); 37 | } 38 | 39 | console.log("Found user: " + user.username); 40 | 41 | while (true) { 42 | let password = await question("New Password: "); 43 | let confirmPassword = await question("Confirm New Password: "); 44 | 45 | if (password === confirmPassword) { 46 | await User.resetPassword(user.id, password); 47 | 48 | // Reset all sessions by reset jwt secret 49 | await server.initJWTSecret(); 50 | 51 | console.log("Password reset successfully."); 52 | 53 | // Disconnect all other socket clients of the user 54 | await disconnectAllSocketClients(user.username, password); 55 | 56 | break; 57 | } else { 58 | console.log("Passwords do not match, please try again."); 59 | } 60 | } 61 | } 62 | } catch (e) { 63 | if (e instanceof Error) { 64 | console.error("Error: " + e.message); 65 | } 66 | } 67 | 68 | await Database.close(); 69 | rl.close(); 70 | 71 | console.log("Finished."); 72 | }; 73 | 74 | /** 75 | * Ask question of user 76 | * @param question Question to ask 77 | * @returns Users response 78 | */ 79 | function question(question : string) : Promise { 80 | return new Promise((resolve) => { 81 | rl.question(question, (answer) => { 82 | resolve(answer); 83 | }); 84 | }); 85 | } 86 | 87 | function disconnectAllSocketClients(username : string, password : string) : Promise { 88 | return new Promise((resolve) => { 89 | const url = server.getLocalWebSocketURL(); 90 | 91 | console.log("Connecting to " + url + " to disconnect all other socket clients"); 92 | 93 | // Disconnect all socket connections 94 | const socket = io(url, { 95 | reconnection: false, 96 | timeout: 5000, 97 | }); 98 | socket.on("connect", () => { 99 | socket.emit("login", { 100 | username, 101 | password, 102 | }, (res : BaseRes) => { 103 | if (res.ok) { 104 | console.log("Logged in."); 105 | socket.emit("disconnectOtherSocketClients"); 106 | } else { 107 | console.warn("Login failed."); 108 | console.warn("Please restart the server to disconnect all sessions."); 109 | } 110 | socket.close(); 111 | }); 112 | }); 113 | 114 | socket.on("connect_error", function () { 115 | // The localWebSocketURL is not guaranteed to be working for some complicated Uptime Kuma setup 116 | // Ask the user to restart the server manually 117 | console.warn("Failed to connect to " + url); 118 | console.warn("Please restart the server to disconnect all sessions manually."); 119 | resolve(); 120 | }); 121 | socket.on("disconnect", () => { 122 | resolve(); 123 | }); 124 | }); 125 | } 126 | 127 | if (!process.env.TEST_BACKEND) { 128 | main(); 129 | } 130 | -------------------------------------------------------------------------------- /extra/templates/mariadb/compose.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | mariadb: 3 | image: mariadb:latest 4 | restart: unless-stopped 5 | ports: 6 | - 3306:3306 7 | environment: 8 | - MARIADB_ROOT_PASSWORD=123456 9 | -------------------------------------------------------------------------------- /extra/templates/nginx-proxy-manager/compose.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | nginx-proxy-manager: 3 | image: 'jc21/nginx-proxy-manager:latest' 4 | restart: unless-stopped 5 | ports: 6 | - '80:80' 7 | - '81:81' 8 | - '443:443' 9 | volumes: 10 | - ./data:/data 11 | - ./letsencrypt:/etc/letsencrypt 12 | -------------------------------------------------------------------------------- /extra/templates/uptime-kuma/compose.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | uptime-kuma: 3 | image: louislam/uptime-kuma:1 4 | volumes: 5 | - ./data:/app/data 6 | ports: 7 | - "3001:3001" 8 | restart: always 9 | -------------------------------------------------------------------------------- /extra/test-docker.ts: -------------------------------------------------------------------------------- 1 | // Check if docker is running 2 | import { exec } from "child_process"; 3 | 4 | exec("docker ps", (err, stdout, stderr) => { 5 | if (err) { 6 | console.error("Docker is not running. Please start docker and try again."); 7 | process.exit(1); 8 | } 9 | }); 10 | -------------------------------------------------------------------------------- /extra/update-version.ts: -------------------------------------------------------------------------------- 1 | import pkg from "../package.json"; 2 | import childProcess from "child_process"; 3 | import fs from "fs"; 4 | 5 | const newVersion = process.env.VERSION; 6 | 7 | console.log("New Version: " + newVersion); 8 | 9 | if (! newVersion) { 10 | console.error("invalid version"); 11 | process.exit(1); 12 | } 13 | 14 | const exists = tagExists(newVersion); 15 | 16 | if (! exists) { 17 | // Process package.json 18 | pkg.version = newVersion; 19 | fs.writeFileSync("package.json", JSON.stringify(pkg, null, 4) + "\n"); 20 | commit(newVersion); 21 | tag(newVersion); 22 | } else { 23 | console.log("version exists"); 24 | } 25 | 26 | /** 27 | * Commit updated files 28 | * @param {string} version Version to update to 29 | */ 30 | function commit(version) { 31 | let msg = "Update to " + version; 32 | 33 | let res = childProcess.spawnSync("git", [ "commit", "-m", msg, "-a" ]); 34 | let stdout = res.stdout.toString().trim(); 35 | console.log(stdout); 36 | 37 | if (stdout.includes("no changes added to commit")) { 38 | throw new Error("commit error"); 39 | } 40 | } 41 | 42 | /** 43 | * Create a tag with the specified version 44 | * @param {string} version Tag to create 45 | */ 46 | function tag(version) { 47 | let res = childProcess.spawnSync("git", [ "tag", version ]); 48 | console.log(res.stdout.toString().trim()); 49 | } 50 | 51 | /** 52 | * Check if a tag exists for the specified version 53 | * @param {string} version Version to check 54 | * @returns {boolean} Does the tag already exist 55 | */ 56 | function tagExists(version) { 57 | if (! version) { 58 | throw new Error("invalid version"); 59 | } 60 | 61 | let res = childProcess.spawnSync("git", [ "tag", "-l", version ]); 62 | 63 | return res.stdout.toString().trim() === version; 64 | } 65 | -------------------------------------------------------------------------------- /frontend/components.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | /* prettier-ignore */ 3 | // @ts-nocheck 4 | // Generated by unplugin-vue-components 5 | // Read more: https://github.com/vuejs/core/pull/3399 6 | export {} 7 | 8 | declare module 'vue' { 9 | export interface GlobalComponents { 10 | About: typeof import('./src/components/settings/About.vue')['default'] 11 | Appearance: typeof import('./src/components/settings/Appearance.vue')['default'] 12 | ArrayInput: typeof import('./src/components/ArrayInput.vue')['default'] 13 | ArraySelect: typeof import('./src/components/ArraySelect.vue')['default'] 14 | BDropdown: typeof import('bootstrap-vue-next')['BDropdown'] 15 | BDropdownItem: typeof import('bootstrap-vue-next')['BDropdownItem'] 16 | BModal: typeof import('bootstrap-vue-next')['BModal'] 17 | Confirm: typeof import('./src/components/Confirm.vue')['default'] 18 | Container: typeof import('./src/components/Container.vue')['default'] 19 | General: typeof import('./src/components/settings/General.vue')['default'] 20 | HiddenInput: typeof import('./src/components/HiddenInput.vue')['default'] 21 | Login: typeof import('./src/components/Login.vue')['default'] 22 | NetworkInput: typeof import('./src/components/NetworkInput.vue')['default'] 23 | RouterLink: typeof import('vue-router')['RouterLink'] 24 | RouterView: typeof import('vue-router')['RouterView'] 25 | Security: typeof import('./src/components/settings/Security.vue')['default'] 26 | StackList: typeof import('./src/components/StackList.vue')['default'] 27 | StackListItem: typeof import('./src/components/StackListItem.vue')['default'] 28 | Terminal: typeof import('./src/components/Terminal.vue')['default'] 29 | TwoFADialog: typeof import('./src/components/TwoFADialog.vue')['default'] 30 | Uptime: typeof import('./src/components/Uptime.vue')['default'] 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /frontend/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | Dockge 12 | 21 | 22 | 23 | 30 |
31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /frontend/public/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/louislam/dockge/e31f76651684cf3252471ee48051088175951cfe/frontend/public/apple-touch-icon.png -------------------------------------------------------------------------------- /frontend/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/louislam/dockge/e31f76651684cf3252471ee48051088175951cfe/frontend/public/favicon.ico -------------------------------------------------------------------------------- /frontend/public/icon-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/louislam/dockge/e31f76651684cf3252471ee48051088175951cfe/frontend/public/icon-192x192.png -------------------------------------------------------------------------------- /frontend/public/icon-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/louislam/dockge/e31f76651684cf3252471ee48051088175951cfe/frontend/public/icon-512x512.png -------------------------------------------------------------------------------- /frontend/public/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Created with Fabric.js 5.3.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /frontend/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Dockge", 3 | "short_name": "Dockge", 4 | "start_url": "/", 5 | "background_color": "#fff", 6 | "display": "standalone", 7 | "icons": [ 8 | { 9 | "src": "icon-192x192.png", 10 | "sizes": "192x192", 11 | "type": "image/png" 12 | }, 13 | { 14 | "src": "icon-512x512.png", 15 | "sizes": "512x512", 16 | "type": "image/png" 17 | } 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /frontend/src/App.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 10 | -------------------------------------------------------------------------------- /frontend/src/components/ArrayInput.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 124 | 125 | 150 | -------------------------------------------------------------------------------- /frontend/src/components/ArraySelect.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 103 | 104 | 129 | -------------------------------------------------------------------------------- /frontend/src/components/Confirm.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 85 | -------------------------------------------------------------------------------- /frontend/src/components/HiddenInput.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 88 | -------------------------------------------------------------------------------- /frontend/src/components/Login.vue: -------------------------------------------------------------------------------- 1 | 44 | 45 | 88 | 89 | 115 | -------------------------------------------------------------------------------- /frontend/src/components/StackListItem.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 118 | 119 | 182 | -------------------------------------------------------------------------------- /frontend/src/components/Uptime.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 44 | 45 | 57 | -------------------------------------------------------------------------------- /frontend/src/components/settings/About.vue: -------------------------------------------------------------------------------- 1 | 27 | 28 | 47 | 48 | 67 | -------------------------------------------------------------------------------- /frontend/src/components/settings/Appearance.vue: -------------------------------------------------------------------------------- 1 | 68 | 69 | 74 | 75 | 95 | -------------------------------------------------------------------------------- /frontend/src/components/settings/General.vue: -------------------------------------------------------------------------------- 1 | 69 | 70 | 114 | 115 | -------------------------------------------------------------------------------- /frontend/src/i18n.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore Performance issue when using "vue-i18n", so we use "vue-i18n/dist/vue-i18n.esm-browser.prod.js", but typescript doesn't like that. 2 | import { createI18n } from "vue-i18n/dist/vue-i18n.esm-browser.prod.js"; 3 | import en from "./lang/en.json"; 4 | 5 | const languageList = { 6 | "bg-BG": "Български", 7 | "es": "Español", 8 | "de": "Deutsch", 9 | "fr": "Français", 10 | "pl-PL": "Polski", 11 | "pt": "Português", 12 | "pt-BR": "Português-Brasil", 13 | "sl": "Slovenščina", 14 | "tr": "Türkçe", 15 | "zh-CN": "简体中文", 16 | "zh-TW": "繁體中文(台灣)", 17 | "ur": "Urdu", 18 | "ko-KR": "한국어", 19 | "ru": "Русский", 20 | "cs-CZ": "Čeština", 21 | "ar": "العربية", 22 | "th": "ไทย", 23 | "it-IT": "Italiano", 24 | "sv-SE": "Svenska", 25 | "uk-UA": "Українська", 26 | "da": "Dansk", 27 | "ja": "日本語", 28 | "nl": "Nederlands", 29 | "ro": "Română", 30 | "id": "Bahasa Indonesia (Indonesian)", 31 | "vi": "Tiếng Việt", 32 | "hu": "Magyar", 33 | "ca": "Català", 34 | "ga": "Gaeilge", 35 | "de-CH": "Schwiizerdütsch", 36 | }; 37 | 38 | let messages = { 39 | en, 40 | }; 41 | 42 | for (let lang in languageList) { 43 | messages[lang] = { 44 | languageName: languageList[lang] 45 | }; 46 | } 47 | 48 | const rtlLangs = [ "fa", "ar-SY", "ur", "ar" ]; 49 | 50 | export const currentLocale = () => localStorage.locale 51 | || languageList[navigator.language] && navigator.language 52 | || languageList[navigator.language.substring(0, 2)] && navigator.language.substring(0, 2) 53 | || "en"; 54 | 55 | export const localeDirection = () => { 56 | return rtlLangs.includes(currentLocale()) ? "rtl" : "ltr"; 57 | }; 58 | 59 | export const i18n = createI18n({ 60 | locale: currentLocale(), 61 | fallbackLocale: "en", 62 | silentFallbackWarn: true, 63 | silentTranslationWarn: true, 64 | messages: messages, 65 | }); 66 | -------------------------------------------------------------------------------- /frontend/src/icon.ts: -------------------------------------------------------------------------------- 1 | import { library } from "@fortawesome/fontawesome-svg-core"; 2 | import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome"; 3 | 4 | // Add Free Font Awesome Icons 5 | // https://fontawesome.com/v6/icons?d=gallery&p=2&s=solid&m=free 6 | // In order to add an icon, you have to: 7 | // 1) add the icon name in the import statement below; 8 | // 2) add the icon name to the library.add() statement below. 9 | import { 10 | faArrowAltCircleUp, 11 | faCog, 12 | faEdit, 13 | faEye, 14 | faEyeSlash, 15 | faList, 16 | faPause, 17 | faStop, 18 | faPlay, 19 | faPlus, 20 | faSearch, 21 | faTachometerAlt, 22 | faTimes, 23 | faTimesCircle, 24 | faTrash, 25 | faCheckCircle, 26 | faStream, 27 | faSave, 28 | faExclamationCircle, 29 | faBullhorn, 30 | faArrowsAltV, 31 | faUnlink, 32 | faQuestionCircle, 33 | faImages, 34 | faUpload, 35 | faCopy, 36 | faCheck, 37 | faFile, 38 | faAward, 39 | faLink, 40 | faChevronDown, 41 | faSignOutAlt, 42 | faPen, 43 | faExternalLinkSquareAlt, 44 | faSpinner, 45 | faUndo, 46 | faPlusCircle, 47 | faAngleDown, 48 | faWrench, 49 | faHeartbeat, 50 | faFilter, 51 | faInfoCircle, 52 | faClone, 53 | faCertificate, 54 | faTerminal, faWarehouse, faHome, faRocket, 55 | faRotate, 56 | faCloudArrowDown, faArrowsRotate, 57 | } from "@fortawesome/free-solid-svg-icons"; 58 | 59 | library.add( 60 | faArrowAltCircleUp, 61 | faCog, 62 | faEdit, 63 | faEye, 64 | faEyeSlash, 65 | faList, 66 | faPause, 67 | faStop, 68 | faPlay, 69 | faPlus, 70 | faSearch, 71 | faTachometerAlt, 72 | faTimes, 73 | faTimesCircle, 74 | faTrash, 75 | faCheckCircle, 76 | faStream, 77 | faSave, 78 | faExclamationCircle, 79 | faBullhorn, 80 | faArrowsAltV, 81 | faUnlink, 82 | faQuestionCircle, 83 | faImages, 84 | faUpload, 85 | faCopy, 86 | faCheck, 87 | faFile, 88 | faAward, 89 | faLink, 90 | faChevronDown, 91 | faSignOutAlt, 92 | faPen, 93 | faExternalLinkSquareAlt, 94 | faSpinner, 95 | faUndo, 96 | faPlusCircle, 97 | faAngleDown, 98 | faLink, 99 | faWrench, 100 | faHeartbeat, 101 | faFilter, 102 | faInfoCircle, 103 | faClone, 104 | faCertificate, 105 | faTerminal, 106 | faWarehouse, 107 | faHome, 108 | faRocket, 109 | faRotate, 110 | faCloudArrowDown, 111 | faArrowsRotate, 112 | ); 113 | 114 | export { FontAwesomeIcon }; 115 | 116 | -------------------------------------------------------------------------------- /frontend/src/lang/README.md: -------------------------------------------------------------------------------- 1 | # Translations 2 | 3 | A simple guide on how to translate `Dockge` in your native language. 4 | 5 | ## How to Translate 6 | 7 | (11-26-2023) Updated 8 | 9 | 1. Go to 10 | 2. Register an account on Weblate 11 | 3. Make sure your GitHub email is matched with Weblate's account, so that it could show you as a contributor on GitHub 12 | 4. Choose your language on Weblate and start translating. 13 | 14 | ## How to add a new language in the dropdown 15 | 16 | 1. Add your Language at . 17 | 2. Find the language code (You can find it at the end of the URL) 18 | 3. Add your language at the end of `languageList` in `frontend/src/i18n.ts`, format: `"zh-TW": "繁體中文 (台灣)"`, 19 | 4. Commit to new branch and make a new Pull Request for me to approve. 20 | -------------------------------------------------------------------------------- /frontend/src/lang/ar.json: -------------------------------------------------------------------------------- 1 | { 2 | "languageName": "العربية", 3 | "Create your admin account": "إنشاء حساب المشرف", 4 | "authIncorrectCreds": "اسم المستخدم أو كلمة المرور غير صحيحة.", 5 | "PasswordsDoNotMatch": "كلمة المرور غير مطابقة.", 6 | "Repeat Password": "أعد كتابة كلمة السر", 7 | "Create": "إنشاء", 8 | "signedInDisp": "تم تسجيل الدخول باسم {0}", 9 | "signedInDispDisabled": "تم تعطيل المصادقة.", 10 | "home": "الرئيسية", 11 | "console": "سطر الأوامر", 12 | "registry": "السجل", 13 | "compose": "أنشاء كمبوز", 14 | "addFirstStackMsg": "أنشيء أول كمبوز!", 15 | "stackName": "اسم المكدسة", 16 | "deployStack": "نشر", 17 | "deleteStack": "حذف", 18 | "stopStack": "إيقاف", 19 | "restartStack": "إعادة تشغيل", 20 | "updateStack": "تحديث", 21 | "startStack": "تشغيل", 22 | "downStack": "أيقاف", 23 | "editStack": "تعديل", 24 | "discardStack": "إهمال", 25 | "saveStackDraft": "حفظ", 26 | "notAvailableShort": "غير متوفر", 27 | "deleteStackMsg": "هل أنت متأكد أنك تريد حذف هذه المكدسة؟", 28 | "stackNotManagedByDockgeMsg": "لا يتم إدارة هذه المكدس بواسطة Dockge.", 29 | "primaryHostname": "اسم المضيف الرئيسي", 30 | "general": "عام", 31 | "container": "حاوية | حاويات", 32 | "scanFolder": "مسح مجلد المكدسات", 33 | "dockerImage": "صورة", 34 | "restartPolicyUnlessStopped": "ما لم يوقف", 35 | "restartPolicyAlways": "دائماً", 36 | "restartPolicyOnFailure": "عند الفشل", 37 | "restartPolicyNo": "لا", 38 | "environmentVariable": "متغير | متغيرات", 39 | "restartPolicy": "سياسة إعادة التشغيل", 40 | "containerName": "اسم الحاوية", 41 | "port": "منفذ | منافذ", 42 | "volume": "مجلد | مجلدات", 43 | "network": "شبكة | شبكات", 44 | "dependsOn": "تبعية الحاوية | تبعية الحاويات", 45 | "addListItem": "إضافة {0}", 46 | "deleteContainer": "حذف", 47 | "addContainer": "أضافة حاوية", 48 | "addNetwork": "أضافة شبكة", 49 | "disableauth.message1": "هل أنت متأكد أنك تريد تعطيل المصادقة؟", 50 | "disableauth.message2": "إنه مصمم للحالات التي تنوي فيها مصادقة الطرف الثالث أمام Dockge مثل Cloudflare Access, Authelia أو أي من آليات المصادقة الأخرى.", 51 | "passwordNotMatchMsg": "كلمة المرور المكررة غير متطابقة.", 52 | "autoGet": "الجلب التلقائي", 53 | "add": "إضافة", 54 | "Edit": "تعديل", 55 | "applyToYAML": "تطبيق على YAML", 56 | "createExternalNetwork": "إنشاء", 57 | "addInternalNetwork": "إضافة", 58 | "Save": "حفظ", 59 | "Language": "اللغة", 60 | "Current User": "المستخدم الحالي", 61 | "Change Password": "تعديل كلمة المرور", 62 | "Current Password": "كلمة المرور الحالية", 63 | "New Password": "كلمة مرور جديدة", 64 | "Repeat New Password": "أعد تكرار كلمة المرور", 65 | "Update Password": "تحديث كلمة المرور", 66 | "Advanced": "متقدم", 67 | "Please use this option carefully!": "من فضلك استخدم هذا الخيار بعناية!", 68 | "Enable Auth": "تفعيل المصادقة", 69 | "Disable Auth": "تعطيل المصادقة", 70 | "I understand, please disable": "أتفهم, أرجو التعطيل", 71 | "Leave": "مغادرة", 72 | "Frontend Version": "لإصدار الواجهة الأمامية", 73 | "Check Update On GitHub": "تحق من التحديث على GitHub", 74 | "Show update if available": "اعرض التحديث إذا كان متاحًا", 75 | "Also check beta release": "تحقق أيضًا من إصدار النسخة التجريبية", 76 | "Remember me": "تذكرني", 77 | "Login": "تسجيل الدخول", 78 | "Username": "اسم المستخدم", 79 | "Password": "كلمة المرور", 80 | "Settings": "الاعدادات", 81 | "Logout": "تسجيل الخروج", 82 | "Lowercase only": "أحرف صغيرة فقط", 83 | "Convert to Compose": "تحويل إلى كومبوز", 84 | "Docker Run": "تشغيل Docker", 85 | "active": "نشيط", 86 | "exited": "تم الخروج", 87 | "inactive": "غير نشيط", 88 | "Appearance": "المظهر", 89 | "Security": "الأمان", 90 | "About": "حول", 91 | "Allowed commands:": "الأوامر المسموح بها:", 92 | "Internal Networks": "الشبكات الداخلية", 93 | "External Networks": "الشبكات الخارجية", 94 | "No External Networks": "لا توجد شبكات خارجية", 95 | "reverseProxyMsg2": "تحقق كيف يتم إعداده لمقبس ويب", 96 | "Cannot connect to the socket server.": "تعذر الاتصال بخادم المقبس.", 97 | "reconnecting...": "إعادة الاتصال…", 98 | "url": "رابط | روابط", 99 | "extra": "إضافات", 100 | "reverseProxyMsg1": "هل تستدخم خادم عكسي؟", 101 | "connecting...": "جاري الاتصال بخادم المقبس…", 102 | "newUpdate": "تحديث جديد", 103 | "currentEndpoint": "السياق: الوكيل الحالي", 104 | "dockgeURL": "رابط Dockge (مثلا http://127.0.0.1:5001)", 105 | "agentOnline": "متصل", 106 | "agentOffline": "غير متصل", 107 | "connecting": "جاري الإتصال", 108 | "connect": "ارتبط", 109 | "dockgeAgent": "سيرفر Dockge", 110 | "removeAgent": "حذف الوكيل", 111 | "removeAgentMsg": "هل انت متأكد من حذف هذا الوكيل؟", 112 | "LongSyntaxNotSupported": "كتابة النصوص المدعومة غير المدعومة هنا. الرجاء استخدام محرر YAML." 113 | } 114 | -------------------------------------------------------------------------------- /frontend/src/lang/be.json: -------------------------------------------------------------------------------- 1 | { 2 | "active": "акт.", 3 | "LongSyntaxNotSupported": "Доўгі сінтаксіс тут не падтрымліваецца. Выкарыстоўвайце рэдактар YAML.", 4 | "removeAgentMsg": "Вы ўпэўнены, што хочаце выдаліць гэтага агента?", 5 | "languageName": "Беларуская", 6 | "Create your admin account": "Стварыце ўліковы запіс адміністратара", 7 | "authIncorrectCreds": "Няправільны лагін ці пароль.", 8 | "PasswordsDoNotMatch": "Паролі не супадаюць.", 9 | "Repeat Password": "Паўтарыце пароль", 10 | "Create": "Стварыць", 11 | "signedInDisp": "Аўтарызаваны як {0}", 12 | "signedInDispDisabled": "Аўтарызацыя выключана.", 13 | "home": "Галоўная", 14 | "console": "Кансоль", 15 | "registry": "Рэестр (Registry)", 16 | "compose": "Compose", 17 | "addFirstStackMsg": "Стварыце свой першы стэк!", 18 | "stackName": "Назва стэка", 19 | "deployStack": "Разгарнуць", 20 | "deleteStack": "Выдаліць", 21 | "stopStack": "Спыніць", 22 | "restartStack": "Перазапусціць", 23 | "updateStack": "Абнавіць", 24 | "startStack": "Запусціць", 25 | "downStack": "Спыніць і дэактываваць", 26 | "editStack": "Рэдагаваць", 27 | "discardStack": "Скасаваць", 28 | "saveStackDraft": "Захаваць", 29 | "notAvailableShort": "Н/Д", 30 | "deleteStackMsg": "Вы ўпэўнены, што хочаце выдаліць гэты стэк?", 31 | "stackNotManagedByDockgeMsg": "Дадзены стэк не кіруецца Dockge.", 32 | "primaryHostname": "Імя хоста", 33 | "general": "Агульныя", 34 | "container": "Кантэйнер | Кантэйнеры", 35 | "scanFolder": "Сканаваць папку стэкаў", 36 | "dockerImage": "Вобраз", 37 | "restartPolicyUnlessStopped": "Пакуль не будзе спынены", 38 | "restartPolicyAlways": "Заўсёды", 39 | "restartPolicyOnFailure": "Пры падзенні", 40 | "restartPolicyNo": "Ніколі", 41 | "environmentVariable": "Зменная асяроддзя | Зменныя асяроддзя", 42 | "restartPolicy": "Палітыка рэстарту", 43 | "containerName": "Імя кантэйнера", 44 | "port": "Порт | Порты", 45 | "volume": "Сховішча | Сховішчы", 46 | "network": "Сетка | Сеткі", 47 | "dependsOn": "Залежнасць кантэйнера | Залежнасці кантэйнера", 48 | "addListItem": "Дадаць {0}", 49 | "deleteContainer": "Выдаліць", 50 | "addContainer": "Дадаць кантэйнер", 51 | "addNetwork": "Дадаць сетку", 52 | "disableauth.message1": "Вы ўпэўнены, што хочаце адключыць аўтэнтыфікацыю?", 53 | "Show update if available": "Паказаць абнаўленне, калі яно даступна", 54 | "Also check beta release": "Атрымліваць бэта-версіі", 55 | "disableauth.message2": "Гэта прызначана для сцэнарыяў, калі вы збіраецеся выкарыстоўваць староннюю аўтэнтыфікацыю перад Dockge, напрыклад, Cloudflare Access, Authelia або іншыя механізмы аўтэнтыфікацыі.", 56 | "passwordNotMatchMsg": "Паўторны пароль не супадае.", 57 | "autoGet": "Аўта", 58 | "add": "Дадаць", 59 | "Edit": "Змяніць", 60 | "applyToYAML": "Ужыць да YAML", 61 | "createExternalNetwork": "Стварыць", 62 | "addInternalNetwork": "Дадаць", 63 | "Save": "Захаваць", 64 | "Language": "Мова", 65 | "Current User": "Бягучы карыстальнік", 66 | "Change Password": "Змяніць пароль", 67 | "Current Password": "Бягучы пароль", 68 | "New Password": "Новы пароль", 69 | "Repeat New Password": "Паўтарыце новы пароль", 70 | "Update Password": "Абнавіць пароль", 71 | "Advanced": "Пашыраныя", 72 | "Please use this option carefully!": "Выкарыстоўвайце гэтую опцыю асцярожна!", 73 | "Enable Auth": "Уключыць аўтэнтыфікацыю", 74 | "Disable Auth": "Адключыць аўтэнтыфікацыю", 75 | "I understand, please disable": "Я разумею, адключыце", 76 | "Leave": "Пакінуць", 77 | "Frontend Version": "Версія знешняга інтэрфейсу", 78 | "Check Update On GitHub": "Праверыць абнаўленні на GitHub", 79 | "Remember me": "Запомніць мяне", 80 | "Login": "Лагін", 81 | "Username": "Імя карыстальніка", 82 | "Password": "Пароль", 83 | "Settings": "Налады", 84 | "Logout": "Выйсці", 85 | "Lowercase only": "Толькі ніжні рэгістр", 86 | "Convert to Compose": "Пераўтварыць у Compose", 87 | "Docker Run": "Docker Run", 88 | "exited": "спын.", 89 | "inactive": "неакт.", 90 | "Appearance": "Знешні выгляд", 91 | "Security": "Бяспека", 92 | "About": "Аб праграме", 93 | "Allowed commands:": "Дазволеныя каманды:", 94 | "Internal Networks": "Унутраныя сеткі", 95 | "External Networks": "Знешнія сеткі", 96 | "No External Networks": "Няма знешніх сетак", 97 | "reverseProxyMsg1": "Выкарыстоўваеце зваротны проксі?", 98 | "reverseProxyMsg2": "Праверце, як наладзіць яго для WebSocket", 99 | "Cannot connect to the socket server.": "Не ўдалося падключыцца да сокет-сервера.", 100 | "reconnecting...": "Перападключэнне…", 101 | "connecting...": "Падключэнне да сокет-сервера…", 102 | "url": "URL-адрас | URL-адрасы", 103 | "extra": "Дадаткова", 104 | "newUpdate": "Даступна абнаўленне", 105 | "dockgeAgent": "Агент Dockge | Агенты Dockge", 106 | "currentEndpoint": "Бягучы", 107 | "dockgeURL": "URL-адрас Dockge (напрыклад: http://127.0.0.1:5001)", 108 | "agentOnline": "У сетцы", 109 | "agentOffline": "Не ў сетцы", 110 | "connecting": "Падключэнне", 111 | "connect": "Падключыць", 112 | "addAgent": "Дадаць Агента", 113 | "agentAddedSuccessfully": "Агент паспяхова дададзены.", 114 | "agentRemovedSuccessfully": "Агент паспяхова выдалены.", 115 | "removeAgent": "Выдаліць агента" 116 | } 117 | -------------------------------------------------------------------------------- /frontend/src/lang/it-IT.json: -------------------------------------------------------------------------------- 1 | { 2 | "languageName": "Italiano", 3 | "Create your admin account": "Crea il tuo account amministratore", 4 | "authIncorrectCreds": "Username e/o password errati.", 5 | "PasswordsDoNotMatch": "Le password non corrispondono.", 6 | "Repeat Password": "Ripetere la password", 7 | "Create": "Crea", 8 | "signedInDisp": "Autenticato come {0}", 9 | "signedInDispDisabled": "Autenticazione disabilitata.", 10 | "home": "Home", 11 | "console": "Console", 12 | "registry": "Registro", 13 | "compose": "Componi", 14 | "addFirstStackMsg": "Componi il tuo primo stack!", 15 | "stackName": "Nome dello stack", 16 | "deployStack": "Rilascia", 17 | "deleteStack": "Cancella", 18 | "stopStack": "Stop", 19 | "restartStack": "Riavvia", 20 | "updateStack": "Aggiorna", 21 | "startStack": "Avvia", 22 | "downStack": "Stop & Inattivo", 23 | "editStack": "Modifica", 24 | "discardStack": "Annulla", 25 | "saveStackDraft": "Salva", 26 | "notAvailableShort": "N/D", 27 | "deleteStackMsg": "Sei sicuro di voler eliminare questo stack?", 28 | "stackNotManagedByDockgeMsg": "Questo stack non è gestito da Dockge.", 29 | "primaryHostname": "Hostname primario", 30 | "general": "Generale", 31 | "container": "Container | Container", 32 | "scanFolder": "Scansiona la cartella degli stack", 33 | "dockerImage": "Immagine", 34 | "restartPolicyUnlessStopped": "A meno che non venga fermato", 35 | "restartPolicyAlways": "Sempre", 36 | "restartPolicyOnFailure": "Quando fallisce", 37 | "restartPolicyNo": "No", 38 | "environmentVariable": "Variabile d'ambiente | Variabili d'ambiente", 39 | "restartPolicy": "Politica di riavvio", 40 | "containerName": "Nome del container", 41 | "port": "Porta | Porte", 42 | "volume": "Volume | Volumi", 43 | "network": "Rete | Reti", 44 | "dependsOn": "Dipendenza del container | Dipendenze del container", 45 | "addListItem": "Aggiungi {0}", 46 | "deleteContainer": "Elimina", 47 | "addContainer": "Aggiungi container", 48 | "addNetwork": "Aggiungi rete", 49 | "disableauth.message1": "Sei sicuro di voler disabilitare l'autenticazione?", 50 | "disableauth.message2": "È stato progettato per scenari in cui intendi implementare un'autenticazione di terze parti davanti a Dockge come ad esempio Cloudflare Access, Authelia o altri meccanismi di autenticazione.", 51 | "passwordNotMatchMsg": "La password ripetuta non corrisponde.", 52 | "autoGet": "Ottieni automaticamente", 53 | "add": "Aggiungi", 54 | "Edit": "Modifica", 55 | "applyToYAML": "Applica al file YAML", 56 | "createExternalNetwork": "Crea", 57 | "addInternalNetwork": "Aggiungi", 58 | "Save": "Salva", 59 | "Language": "Lingua", 60 | "Current User": "Utente corrente", 61 | "Change Password": "Cambia la password", 62 | "Current Password": "Password corrente", 63 | "New Password": "Nuova password", 64 | "Repeat New Password": "Ripeti la nuova password", 65 | "Update Password": "Aggiornamento password", 66 | "Advanced": "Avanzato", 67 | "Please use this option carefully!": "Per favore usa questa opzione con cautela!", 68 | "Enable Auth": "Abilita l'autenticazione", 69 | "Disable Auth": "Disabilita l'autenticazione", 70 | "I understand, please disable": "Lo capisco, disabilita", 71 | "Leave": "Lascia", 72 | "Frontend Version": "Versione del frontend", 73 | "Check Update On GitHub": "Controlla la presenza di aggiornamenti su GitHub", 74 | "Show update if available": "Mostra l'aggiornamento se è disponibile", 75 | "Also check beta release": "Controlla anche le release in beta", 76 | "Remember me": "Ricordami", 77 | "Login": "Login", 78 | "Username": "Nome Utente", 79 | "Password": "Password", 80 | "Settings": "Impostazioni", 81 | "Logout": "Logout", 82 | "Lowercase only": "Solo lettere minuscole", 83 | "Convert to Compose": "Converti a Compose", 84 | "Docker Run": "Docker Run", 85 | "active": "attivo", 86 | "exited": "uscito", 87 | "inactive": "inattivo", 88 | "Appearance": "Aspetto", 89 | "Security": "Sicurezza", 90 | "About": "Informazioni su", 91 | "Allowed commands:": "Comandi permessi:", 92 | "Internal Networks": "Reti interne", 93 | "External Networks": "Reti esterne", 94 | "No External Networks": "Nessuna rete esterna", 95 | "reverseProxyMsg1": "Stai usando Reverse Proxy?", 96 | "reverseProxyMsg2": "Verifica come configurarlo per il WebSocket", 97 | "Cannot connect to the socket server.": "impossibile collegarsi al socket server", 98 | "connecting...": "connettendosi al socket server…", 99 | "extra": "Extra", 100 | "reconnecting...": "Riconnessione…", 101 | "url": "URL | URLs", 102 | "newUpdate": "Nuovo aggiornamento", 103 | "dockgeAgent": "Agente Dockge | Agenti Dockge", 104 | "currentEndpoint": "Corrente", 105 | "agentOnline": "Online", 106 | "agentOffline": "Offline", 107 | "connecting": "In connessione", 108 | "connect": "Connetti", 109 | "dockgeURL": "Dockge URL (ad esempio http://127.0.0.1:5001)", 110 | "agentRemovedSuccessfully": "Agente rimosso con successo.", 111 | "removeAgent": "Rimuovi Agente", 112 | "removeAgentMsg": "Sei sicuro di voler rimuovere questo agente?", 113 | "addAgent": "Aggungi Agente", 114 | "agentAddedSuccessfully": "Agente aggiunto correttamente.", 115 | "LongSyntaxNotSupported": "La sintassi lunga non è supportata qui. Utilizzare l'editor YAML." 116 | } 117 | -------------------------------------------------------------------------------- /frontend/src/lang/ja.json: -------------------------------------------------------------------------------- 1 | { 2 | "authIncorrectCreds": "ユーザーネームまたはパスワードが正しくありません。", 3 | "PasswordsDoNotMatch": "パスワードが一致しません。", 4 | "Repeat Password": "パスワードを再度入力してください", 5 | "Create": "作成", 6 | "signedInDispDisabled": "認証が無効化されています。", 7 | "home": "ホーム", 8 | "console": "コンソール", 9 | "registry": "レジストリ", 10 | "stackName": "スタック名", 11 | "deployStack": "デプロイ", 12 | "deleteStack": "削除", 13 | "stopStack": "停止", 14 | "restartStack": "再起動", 15 | "updateStack": "更新", 16 | "startStack": "起動", 17 | "editStack": "編集", 18 | "discardStack": "破棄", 19 | "saveStackDraft": "保存", 20 | "stackNotManagedByDockgeMsg": "このスタックはDockgeによって管理されていません。", 21 | "general": "一般", 22 | "scanFolder": "スタックフォルダをスキャン", 23 | "dockerImage": "イメージ", 24 | "environmentVariable": "環境変数", 25 | "restartPolicy": "再起動ポリシー", 26 | "containerName": "コンテナ名", 27 | "port": "ポート", 28 | "volume": "ボリューム", 29 | "network": "ネットワーク", 30 | "addListItem": "{0} を追加", 31 | "addContainer": "コンテナを追加", 32 | "addNetwork": "ネットワークを追加", 33 | "compose": "Compose", 34 | "primaryHostname": "主ホスト名", 35 | "container": "コンテナ", 36 | "dependsOn": "コンテナ依存関係", 37 | "downStack": "停止して非アクティブ化", 38 | "notAvailableShort": "N/A", 39 | "restartPolicyUnlessStopped": "手動で停止されるまで", 40 | "restartPolicyAlways": "常時", 41 | "restartPolicyOnFailure": "失敗時", 42 | "restartPolicyNo": "しない", 43 | "passwordNotMatchMsg": "繰り返しのパスワードが一致しません。", 44 | "autoGet": "自動取得", 45 | "add": "追加", 46 | "Edit": "編集", 47 | "applyToYAML": "YAMLに適用", 48 | "createExternalNetwork": "作成", 49 | "addInternalNetwork": "追加", 50 | "Save": "保存", 51 | "Language": "言語", 52 | "Change Password": "パスワードを変更する", 53 | "Current Password": "現在のパスワード", 54 | "New Password": "新しいパスワード", 55 | "Update Password": "パスワードを更新", 56 | "Advanced": "高度", 57 | "Please use this option carefully!": "このオプションは注意して使用してください!", 58 | "Enable Auth": "認証を有効化", 59 | "Disable Auth": "認証を無効化", 60 | "Check Update On GitHub": "GitHubで更新を確認", 61 | "Show update if available": "アップデートがある場合表示", 62 | "Also check beta release": "ベータ版のリリースも確認する", 63 | "Login": "ログイン", 64 | "Username": "ユーザー名", 65 | "Password": "パスワード", 66 | "Settings": "設定", 67 | "Logout": "ログアウト", 68 | "Convert to Compose": "Composeに変換", 69 | "Appearance": "外観", 70 | "Security": "セキュリティ", 71 | "Allowed commands:": "許可されたコマンド:", 72 | "Internal Networks": "内部ネットワーク", 73 | "External Networks": "外部ネットワーク", 74 | "reverseProxyMsg2": "WebSocketの設定方法を確認", 75 | "Cannot connect to the socket server.": "ソケットサーバーに接続できません。", 76 | "reconnecting...": "再接続中…", 77 | "Leave": "やめる", 78 | "Frontend Version": "フロントエンドバージョン", 79 | "Remember me": "覚えておく", 80 | "No External Networks": "外部ネットワークなし", 81 | "exited": "終了済み", 82 | "inactive": "非アクティブ", 83 | "active": "アクティブ", 84 | "languageName": "日本語", 85 | "Create your admin account": "管理者アカウントを作成してください", 86 | "signedInDisp": "{0} としてログイン中", 87 | "addFirstStackMsg": "最初のスタックを組み立てましょう!", 88 | "deleteStackMsg": "本当にこのスタックを削除しますか?", 89 | "deleteContainer": "削除", 90 | "disableauth.message1": "本当に認証を無効化しますか?", 91 | "disableauth.message2": "これはCloudflare AccessやAutheliaなどの認証手段をDockgeの前段に置いてサードパーティー認証を実装することをあなたが意図している場合のために設計されています。", 92 | "Current User": "現在のユーザー", 93 | "Repeat New Password": "新しいパスワードを繰り返してください", 94 | "I understand, please disable": "理解しました。無効化してください", 95 | "Lowercase only": "小文字のみ", 96 | "reverseProxyMsg1": "リバースプロキシを使用していますか?", 97 | "connecting...": "ソケットサーバーに接続中…", 98 | "newUpdate": "新しいバージョン", 99 | "dockgeAgent": "Dockge エージェント", 100 | "dockgeURL": "DockgeのURL (例: http://127.0.0.1:5001)", 101 | "agentOnline": "オンライン", 102 | "agentOffline": "オフライン", 103 | "connecting": "接続中", 104 | "connect": "接続", 105 | "addAgent": "エージェントを追加", 106 | "agentAddedSuccessfully": "エージェントが正常に追加されました。", 107 | "agentRemovedSuccessfully": "エージェントは正常に削除されました。", 108 | "removeAgent": "エージェントを削除", 109 | "removeAgentMsg": "本当にこのエージェントを削除しますか?", 110 | "url": "URL | URL", 111 | "About": "Dockgeについて", 112 | "Docker Run": "Docker Run to Compose", 113 | "LongSyntaxNotSupported": "長い構文はここではサポートされていません。YAMLエディタを使用してください。", 114 | "Lost connection to the socket server. Reconnecting...": "ソケットサーバーとの接続が失われました。再接続中です...", 115 | "extra": "追加設定", 116 | "Saved": "保存済み", 117 | "Deployed": "デプロイ済み", 118 | "Deleted": "削除済み", 119 | "Updated": "アップデート済み", 120 | "Started": "開始済み", 121 | "Stopped": "停止済み", 122 | "Restarted": "再起動済み", 123 | "Switch to sh": "shに切り替え", 124 | "terminal": "ターミナル", 125 | "New Container Name...": "新しいコンテナ名...", 126 | "Network name...": "ネットワーク名...", 127 | "Select a network...": "ネットワークを選択...", 128 | "NoNetworksAvailable": "利用可能なネットワークがありません。まず右側の内部ネットワークを追加するか、外部ネットワークを有効にする必要があります。" 129 | } 130 | -------------------------------------------------------------------------------- /frontend/src/lang/ko-KR.json: -------------------------------------------------------------------------------- 1 | { 2 | "languageName": "한국어", 3 | "Create your admin account": "관리자 계정 만들기", 4 | "authIncorrectCreds": "사용자명 또는 비밀번호가 일치하지 않아요.", 5 | "PasswordsDoNotMatch": "비밀번호가 일치하지 않아요.", 6 | "Repeat Password": "비밀번호 재입력", 7 | "Create": "생성", 8 | "signedInDisp": "{0}(으)로 로그인됨", 9 | "signedInDispDisabled": "인증 비활성화됨.", 10 | "home": "홈", 11 | "console": "콘솔", 12 | "registry": "레지스트리", 13 | "compose": "생성", 14 | "addFirstStackMsg": "첫 번째 스택을 만들어 보세요!", 15 | "stackName": "스택 이름", 16 | "deployStack": "배포", 17 | "deleteStack": "삭제", 18 | "stopStack": "정지", 19 | "restartStack": "재시작", 20 | "updateStack": "업데이트", 21 | "startStack": "시작", 22 | "editStack": "수정", 23 | "discardStack": "취소", 24 | "saveStackDraft": "저장", 25 | "notAvailableShort": "N/A", 26 | "deleteStackMsg": "정말로 이 스택을 삭제하시겠습니까?", 27 | "stackNotManagedByDockgeMsg": "이 스택은 Dockge에 의해 관리되지 않아요.", 28 | "primaryHostname": "주 호스트명", 29 | "general": "일반", 30 | "container": "컨테이너", 31 | "scanFolder": "스택 폴더 스캔", 32 | "dockerImage": "이미지", 33 | "restartPolicyUnlessStopped": "종료되기 전까지", 34 | "restartPolicyAlways": "항상", 35 | "restartPolicyOnFailure": "오류 발생 시", 36 | "restartPolicyNo": "안 함", 37 | "environmentVariable": "환경 변수", 38 | "restartPolicy": "재시작 정책", 39 | "containerName": "컨테이너 이름", 40 | "port": "포트", 41 | "volume": "볼륨", 42 | "network": "네트워크", 43 | "dependsOn": "컨테이너 의존성", 44 | "addListItem": "{0} 추가", 45 | "deleteContainer": "삭제", 46 | "addContainer": "컨테이너 추가", 47 | "addNetwork": "네트워크 추가", 48 | "disableauth.message1": "정말로 인증을 비활성화하시겠습니까?", 49 | "disableauth.message2": "이 기능은 Dockge 앞에 Cloudflare Access, Authelia 등과 같은 서드 파티 인증을 사용하려는 경우에 사용하기 위해서 만들어졌어요.", 50 | "passwordNotMatchMsg": "비밀번호 재입력이 일치하지 않아요..", 51 | "autoGet": "자동으로 가져오기", 52 | "add": "추가", 53 | "Edit": "수정", 54 | "applyToYAML": "YAML에 적용", 55 | "createExternalNetwork": "생성", 56 | "addInternalNetwork": "추가", 57 | "Save": "저장", 58 | "Language": "언어", 59 | "Current User": "현재 사용자", 60 | "Change Password": "비밀번호 변경", 61 | "Current Password": "현재 비밀번호", 62 | "New Password": "새 비밀번호", 63 | "Repeat New Password": "새 비밀번호 재입력", 64 | "Update Password": "비밀번호 변경", 65 | "Advanced": "고급", 66 | "Please use this option carefully!": "이 설정은 신중히 사용하세요!", 67 | "Enable Auth": "인증 활성화", 68 | "Disable Auth": "인증 비활성화", 69 | "I understand, please disable": "이해하고 있습니다. 비활성화해 주세요", 70 | "Leave": "취소", 71 | "Frontend Version": "프론트엔드 버전", 72 | "Check Update On GitHub": "GitHub에서 업데이트 확인", 73 | "Show update if available": "업데이트가 있을 때 표시", 74 | "Also check beta release": "베타 버전도 확인", 75 | "Remember me": "기억하기", 76 | "Login": "로그인", 77 | "Username": "사용자명", 78 | "Password": "비밀번호", 79 | "Settings": "설정", 80 | "Logout": "로그아웃", 81 | "Lowercase only": "소문자만", 82 | "Convert to Compose": "Compose로 변환", 83 | "Docker Run": "Docker Run", 84 | "active": "활성", 85 | "exited": "종료됨", 86 | "inactive": "비활성", 87 | "Appearance": "디스플레이", 88 | "Security": "보안", 89 | "About": "정보", 90 | "Allowed commands:": "허용된 명령어:", 91 | "Internal Networks": "내부 네트워크", 92 | "External Networks": "외부 네트워크", 93 | "No External Networks": "외부 네트워크 없음", 94 | "reverseProxyMsg2": "여기서 WebSocket을 위한 설정을 확인해 보세요", 95 | "downStack": "정지 & 비활성화", 96 | "reverseProxyMsg1": "리버스 프록시를 사용하고 계신가요?", 97 | "Cannot connect to the socket server.": "소켓 서버에 연결하지 못했습니다.", 98 | "connecting...": "소켓 서버에 연결하는 중…", 99 | "extra": "기타", 100 | "url": "URL | URL", 101 | "reconnecting...": "재연결 중…", 102 | "newUpdate": "새 업데이트", 103 | "dockgeURL": "Dockge URL (예. http://127.0.0.1:5001)", 104 | "agentOnline": "온라인", 105 | "agentOffline": "오프라인", 106 | "connect": "연결", 107 | "addAgent": "에이전트 추가", 108 | "agentAddedSuccessfully": "에이전트를 성공적으로 추가했습니다.", 109 | "removeAgent": "에이전트 삭제", 110 | "removeAgentMsg": "정말로 이 에이전트를 삭제하시겠습니까?", 111 | "dockgeAgent": "Dockge 에이전트", 112 | "currentEndpoint": "현재", 113 | "connecting": "연결 중", 114 | "agentRemovedSuccessfully": "에이전트를 성공적으로 삭제했습니다.", 115 | "LongSyntaxNotSupported": "긴 문법은 여기서 지원되지 않습니다. YAML 에디터를 사용하세요." 116 | } 117 | -------------------------------------------------------------------------------- /frontend/src/lang/nb_NO.json: -------------------------------------------------------------------------------- 1 | { 2 | "Create your admin account": "Lag din administrator konto", 3 | "authIncorrectCreds": "Brukernavn eller passord stemmer ikke.", 4 | "PasswordsDoNotMatch": "Passord stemmer ikke.", 5 | "Repeat Password": "Gjenta passord", 6 | "Create": "Lag", 7 | "signedInDisp": "Logg in som {0}", 8 | "signedInDispDisabled": "Auth deaktivert.", 9 | "home": "Hjem", 10 | "console": "Konsoll", 11 | "registry": "Register", 12 | "compose": "Skriv", 13 | "addFirstStackMsg": "Lag din første stack!", 14 | "stackName": "Navn på stack", 15 | "deployStack": "Utplassere", 16 | "deleteStack": "Slett", 17 | "stopStack": "Stoppe", 18 | "restartStack": "Omstart", 19 | "updateStack": "Oppdater", 20 | "downStack": "Stop & Inaktiver", 21 | "editStack": "Rediger", 22 | "discardStack": "Kast", 23 | "saveStackDraft": "Lagre", 24 | "notAvailableShort": "N/A", 25 | "deleteStackMsg": "Er du sikker på at du vil slette denne stacken?", 26 | "stackNotManagedByDockgeMsg": "Denne stacken er ikke styrt av Dockge.", 27 | "primaryHostname": "Primært vertsnavn", 28 | "general": "Generell", 29 | "container": "Container | Containers", 30 | "scanFolder": "Skann Stacks mappe", 31 | "dockerImage": "Bilde", 32 | "languageName": "Engelsk", 33 | "startStack": "Start" 34 | } 35 | -------------------------------------------------------------------------------- /frontend/src/lang/nl.json: -------------------------------------------------------------------------------- 1 | { 2 | "languageName": "Nederlands", 3 | "authIncorrectCreds": "Onjuiste gebruikersnaam of wachtwoord.", 4 | "PasswordsDoNotMatch": "Wachtwoorden komen niet overeen.", 5 | "Repeat Password": "Herhaal wachtwoord", 6 | "Create": "Aanmaken", 7 | "signedInDisp": "Ingelogd als {0}", 8 | "home": "Home", 9 | "console": "Console", 10 | "registry": "Register", 11 | "compose": "Nieuwe stack", 12 | "stackName": "Stack naam", 13 | "deployStack": "Opzetten", 14 | "deleteStack": "Verwijder", 15 | "stopStack": "Stop", 16 | "restartStack": "Herstart", 17 | "updateStack": "Update", 18 | "startStack": "Start", 19 | "downStack": "Stop & Afsluiten", 20 | "editStack": "Bewerken", 21 | "discardStack": "Verwijderen", 22 | "saveStackDraft": "Opslaan", 23 | "notAvailableShort": "n.v.t.", 24 | "stackNotManagedByDockgeMsg": "Deze stack wordt niet beheerd door Dockge.", 25 | "primaryHostname": "Primaire hostnaam", 26 | "general": "Algemeen", 27 | "scanFolder": "Scan stacks folder", 28 | "dockerImage": "Image", 29 | "restartPolicyUnlessStopped": "Tenzij gestopt", 30 | "restartPolicyAlways": "Altijd", 31 | "restartPolicyOnFailure": "Bij fout", 32 | "restartPolicyNo": "Neen", 33 | "environmentVariable": "Omgevings variabele(n)", 34 | "restartPolicy": "Herstart policy", 35 | "containerName": "Containernaam", 36 | "port": "Poort(en)", 37 | "volume": "Volume(s)", 38 | "network": "Netwerk(en)", 39 | "addListItem": "Voeg {0} toe", 40 | "deleteContainer": "Verwijder", 41 | "addContainer": "Container toevoegen", 42 | "addNetwork": "Netwerk toevoegen", 43 | "signedInDispDisabled": "Aanmelden uitgeschakeld.", 44 | "container": "Container(s)", 45 | "autoGet": "Auto ophalen", 46 | "add": "Toevoegen", 47 | "Edit": "Bewerken", 48 | "applyToYAML": "Toevoegen aan YAML", 49 | "createExternalNetwork": "Aanmaken", 50 | "addInternalNetwork": "Toevoegen", 51 | "Save": "Opslaan", 52 | "Language": "Taal", 53 | "Change Password": "Verander wachtwoord", 54 | "Current Password": "Huidig wachtwoord", 55 | "New Password": "Nieuw wachtwoord", 56 | "Repeat New Password": "Herhaal nieuw wachtwoord", 57 | "Update Password": "Update wachtwoord", 58 | "Advanced": "Geavanceerd", 59 | "I understand, please disable": "Begrepen, dit uitschakelen", 60 | "Disable Auth": "Aanmelden uitschakelen", 61 | "Enable Auth": "Aanmelden inschakelen", 62 | "Leave": "Afmelden", 63 | "Frontend Version": "Frontend versie", 64 | "Check Update On GitHub": "Controleer via GitHub op updates", 65 | "Show update if available": "Toon update indien beschikbaar", 66 | "Remember me": "Onthoud mij", 67 | "Login": "Inloggen", 68 | "Username": "Gebruikersnaam", 69 | "Password": "Wachtwoord", 70 | "Settings": "Instellingen", 71 | "Logout": "Uitloggen", 72 | "Lowercase only": "Geen hoofdletters", 73 | "Docker Run": "Docker run", 74 | "active": "actief", 75 | "exited": "gestopt", 76 | "inactive": "inactief", 77 | "Appearance": "Uiterlijk", 78 | "Security": "Beveiliging", 79 | "About": "Over", 80 | "Allowed commands:": "Toegelaten commando's:", 81 | "Internal Networks": "Interne netwerken", 82 | "No External Networks": "Geen externe netwerken", 83 | "reverseProxyMsg1": "Reverse proxy in gebruik?", 84 | "reverseProxyMsg2": "Controleer hoe te configureren voor WebSocket", 85 | "Cannot connect to the socket server.": "Kan geen verbinding maken met de socket server.", 86 | "reconnecting...": "Herverbinden…", 87 | "connecting...": "Verbinden met de socket server…", 88 | "url": "Adres(sen)", 89 | "extra": "Extra", 90 | "Create your admin account": "Creëer je beheerders-account", 91 | "addFirstStackMsg": "Maak je eerste stack!", 92 | "deleteStackMsg": "Zeker dat je deze stack wilt verwijderen?", 93 | "dependsOn": "Container afhankelijkheid | afhankelijkheden", 94 | "disableauth.message1": "Zeker dat u aanmelden wilt uitschakelen?", 95 | "disableauth.message2": "Dit is enkel bedoeld om te gebruiken wanneer je third-party autorisatie wilt gebruiken voor Dockge, zoals Cloudflare Acces, Authelia, ...", 96 | "passwordNotMatchMsg": "De wachtwoorden komen niet overeen.", 97 | "Current User": "Huidige gebruiker", 98 | "Please use this option carefully!": "Wees voorzichtig met deze optie!", 99 | "Also check beta release": "Controleer ook op beta releases", 100 | "Convert to Compose": "Converteer naar compose", 101 | "External Networks": "Externe netwerken", 102 | "newUpdate": "Nieuwe update", 103 | "dockgeAgent": "Dockge Agent | Dockge Agenten", 104 | "currentEndpoint": "Huidige", 105 | "dockgeURL": "Dockge Adres (bijv. http://127.0.0.1:5001)", 106 | "agentOnline": "Online", 107 | "agentOffline": "Offline", 108 | "connecting": "Verbinden", 109 | "connect": "Verbind", 110 | "addAgent": "Agent toevoegen", 111 | "agentAddedSuccessfully": "Agent toegevoegd.", 112 | "agentRemovedSuccessfully": "Agent verwijderd.", 113 | "removeAgent": "Verwijder agent", 114 | "removeAgentMsg": "Weet je zeker dat je deze agent wilt verwijderen?", 115 | "LongSyntaxNotSupported": "Lange syntax wordt hier niet ondersteund. Gebruik de YAML editor." 116 | } 117 | -------------------------------------------------------------------------------- /frontend/src/lang/pt.json: -------------------------------------------------------------------------------- 1 | { 2 | "languageName": "Português", 3 | "Create your admin account": "Crie sua conta de administrador", 4 | "authIncorrectCreds": "Nome de usuário ou senha incorretos.", 5 | "PasswordsDoNotMatch": "As senhas não coincidem.", 6 | "Repeat Password": "Repetir Senha", 7 | "Create": "Criar", 8 | "signedInDisp": "Logado como {0}", 9 | "signedInDispDisabled": "Autenticação desativada.", 10 | "home": "Início", 11 | "console": "Console", 12 | "registry": "Registro", 13 | "compose": "Compor", 14 | "addFirstStackMsg": "Componha sua primeira pilha!", 15 | "stackName": "Nome da Pilha", 16 | "deployStack": "Implantar", 17 | "deleteStack": "Excluir", 18 | "stopStack": "Parar", 19 | "restartStack": "Reiniciar", 20 | "updateStack": "Atualizar", 21 | "startStack": "Iniciar", 22 | "editStack": "Editar", 23 | "discardStack": "Descartar", 24 | "saveStackDraft": "Salvar", 25 | "notAvailableShort": "N/D", 26 | "deleteStackMsg": "Tem certeza de que deseja excluir esta pilha?", 27 | "stackNotManagedByDockgeMsg": "Esta pilha não é gerenciada pelo Dockge.", 28 | "primaryHostname": "Nome do Host Primário", 29 | "general": "Geral", 30 | "container": "Contêiner | Contêineres", 31 | "scanFolder": "Digitalizar Pasta de Pilhas", 32 | "dockerImage": "Imagem", 33 | "restartPolicyUnlessStopped": "A menos que seja parado", 34 | "restartPolicyAlways": "Sempre", 35 | "restartPolicyOnFailure": "Em caso de falha", 36 | "restartPolicyNo": "Não", 37 | "environmentVariable": "Variável de Ambiente | Variáveis de Ambiente", 38 | "restartPolicy": "Política de Reinicialização", 39 | "containerName": "Nome do Contêiner", 40 | "port": "Porta | Portas", 41 | "volume": "Volume | Volumes", 42 | "network": "Rede | Redes", 43 | "dependsOn": "Dependência do Contêiner | Dependências do Contêiner", 44 | "addListItem": "Adicionar {0}", 45 | "deleteContainer": "Excluir", 46 | "addContainer": "Adicionar Contêiner", 47 | "addNetwork": "Adicionar Rede", 48 | "disableauth.message1": "Tem certeza de que deseja desativar a autenticação?", 49 | "disableauth.message2": "Isso é projetado para cenários onde você pretende implementar autenticação de terceiros no Dockge, como Cloudflare Access, Authelia ou outros mecanismos de autenticação.", 50 | "passwordNotMatchMsg": "A senha repetida não coincide.", 51 | "autoGet": "Obter Automaticamente", 52 | "add": "Adicionar", 53 | "Edit": "Editar", 54 | "applyToYAML": "Aplicar ao YAML", 55 | "createExternalNetwork": "Criar", 56 | "addInternalNetwork": "Adicionar", 57 | "Save": "Salvar", 58 | "Language": "Idioma", 59 | "Current User": "Usuário Atual", 60 | "Change Password": "Alterar Senha", 61 | "Current Password": "Senha Atual", 62 | "New Password": "Nova Senha", 63 | "Repeat New Password": "Repetir Nova Senha", 64 | "Update Password": "Atualizar Senha", 65 | "Advanced": "Avançado", 66 | "Please use this option carefully!": "Por favor, use esta opção com cuidado!", 67 | "Enable Auth": "Habilitar Autenticação", 68 | "Disable Auth": "Desabilitar Autenticação", 69 | "I understand, please disable": "Entendo, por favor desabilitar", 70 | "Leave": "Sair", 71 | "Frontend Version": "Versão da Interface", 72 | "Check Update On GitHub": "Verificar Atualização no GitHub", 73 | "Show update if available": "Mostrar atualização se disponível", 74 | "Also check beta release": "Também verificar versão beta", 75 | "Remember me": "Lembrar-me", 76 | "Login": "Entrar", 77 | "Username": "Nome de Usuário", 78 | "Password": "Senha", 79 | "Settings": "Configurações", 80 | "Logout": "Sair", 81 | "Lowercase only": "Somente minúsculas", 82 | "Convert to Compose": "Converter para Compose", 83 | "Docker Run": "Executar Docker", 84 | "active": "ativo", 85 | "exited": "encerrado", 86 | "inactive": "inativo", 87 | "Appearance": "Aparência", 88 | "Security": "Segurança", 89 | "About": "Sobre", 90 | "Allowed commands:": "Comandos permitidos:", 91 | "Internal Networks": "Redes Internas", 92 | "External Networks": "Redes Externas", 93 | "No External Networks": "Sem Redes Externas", 94 | "newUpdate": "Nova Atualização", 95 | "currentEndpoint": "Atual", 96 | "dockgeURL": "Dockge URL (e.g. http://127.0.0.1:5001)", 97 | "agentOnline": "Online", 98 | "agentOffline": "Offline", 99 | "connecting": "Conectando", 100 | "addAgent": "Adicionar Agente", 101 | "agentAddedSuccessfully": "Agente adicionado com sucesso.", 102 | "agentRemovedSuccessfully": "Agente removido com sucesso.", 103 | "removeAgent": "Remover Agente", 104 | "downStack": "Parar & Desativar", 105 | "dockgeAgent": "Dockge Agente | Dockge Agentes", 106 | "connect": "Conectar", 107 | "removeAgentMsg": "Tem certeza de que deseja remover este agente?", 108 | "reverseProxyMsg1": "Usando um Proxy Reverso?", 109 | "reverseProxyMsg2": "Verifique para configurá-lo como WebSocket", 110 | "Cannot connect to the socket server.": "Não é possível se conectar ao servidor socket.", 111 | "url": "URL | URL's", 112 | "extra": "Extra", 113 | "reconnecting...": "Reconectando…", 114 | "connecting...": "Conectando ao servidor de socket…", 115 | "LongSyntaxNotSupported": "Sintaxes longas não são suportadas aqui. Por favor, utilize um editor YAML." 116 | } 117 | -------------------------------------------------------------------------------- /frontend/src/lang/ru.json: -------------------------------------------------------------------------------- 1 | { 2 | "languageName": "Русский", 3 | "Create your admin account": "Создайте учетную запись администратора", 4 | "authIncorrectCreds": "Неверный логин или пароль.", 5 | "PasswordsDoNotMatch": "Пароли не совпадают.", 6 | "Repeat Password": "Повторите пароль", 7 | "Create": "Создать", 8 | "signedInDisp": "Авторизован как {0}", 9 | "signedInDispDisabled": "Авторизация выключена.", 10 | "home": "Главная", 11 | "console": "Консоль", 12 | "registry": "Реестр (Registry)", 13 | "compose": "Compose", 14 | "addFirstStackMsg": "Создайте свой первый стек!", 15 | "stackName": "Имя стека", 16 | "deployStack": "Развернуть", 17 | "deleteStack": "Удалить", 18 | "stopStack": "Остановить", 19 | "restartStack": "Перезапустить", 20 | "updateStack": "Обновить", 21 | "startStack": "Запустить", 22 | "editStack": "Изменить", 23 | "discardStack": "Отменить", 24 | "saveStackDraft": "Сохранить", 25 | "notAvailableShort": "Н/Д", 26 | "deleteStackMsg": "Вы уверены что хотите удалить этот стек?", 27 | "stackNotManagedByDockgeMsg": "Данный стек не управляется Dockge.", 28 | "primaryHostname": "Имя хоста", 29 | "general": "Основные", 30 | "container": "Контейнер | Контейнеры", 31 | "scanFolder": "Сканировать папку стеков", 32 | "dockerImage": "Образ", 33 | "restartPolicyUnlessStopped": "Пока не будет остановлен", 34 | "restartPolicyAlways": "Всегда", 35 | "restartPolicyOnFailure": "При падении", 36 | "restartPolicyNo": "Никогда", 37 | "environmentVariable": "Переменная окружения | Переменные окружения", 38 | "restartPolicy": "Политика рестарта", 39 | "containerName": "Имя контейнера", 40 | "port": "Порт | Порты", 41 | "volume": "Хранилище | Хранилища", 42 | "network": "Сеть | Сети", 43 | "dependsOn": "Зависимость контейнера | Зависимости контейнера", 44 | "addListItem": "Добавить {0}", 45 | "deleteContainer": "Удалить", 46 | "addContainer": "Добавить контейнер", 47 | "addNetwork": "Добавить сеть", 48 | "disableauth.message1": "Вы уверены что хотите отключить аутентификацию?", 49 | "disableauth.message2": "Это предназначено для сценариев, когда Вы собираетесь использовать стороннюю аутентификацию перед Dockge, например Cloudflare Access, Authelia или другие механизмы аутентификации.", 50 | "passwordNotMatchMsg": "Повторный пароль не совпадает.", 51 | "autoGet": "Авто", 52 | "add": "Добавить", 53 | "Edit": "Изменить", 54 | "applyToYAML": "Применить к YAML", 55 | "createExternalNetwork": "Создать", 56 | "addInternalNetwork": "Добавить", 57 | "Save": "Сохранить", 58 | "Language": "Язык", 59 | "Current User": "Текущий пользователь", 60 | "Change Password": "Изменить пароль", 61 | "Current Password": "Текущий пароль", 62 | "New Password": "Новый пароль", 63 | "Repeat New Password": "Повторите новый пароль", 64 | "Update Password": "Обновить пароль", 65 | "Advanced": "Расширенные", 66 | "Please use this option carefully!": "Пожалуйста, используйте эту опцию осторожно!", 67 | "Enable Auth": "Включить аутентификацию", 68 | "Disable Auth": "Отключить аутентификацию", 69 | "I understand, please disable": "Я понимаю, пожалуйста, отключите", 70 | "Leave": "Покинуть", 71 | "Frontend Version": "Версия внешнего интерфейса", 72 | "Check Update On GitHub": "Проверить обновления на GitHub", 73 | "Show update if available": "Показать обновление, если оно доступно", 74 | "Also check beta release": "Получать бета-версии", 75 | "Remember me": "Запомнить меня", 76 | "Login": "Логин", 77 | "Username": "Имя пользователя", 78 | "Password": "Пароль", 79 | "Settings": "Настройки", 80 | "Logout": "Выйти", 81 | "Lowercase only": "Только нижний регистр", 82 | "Convert to Compose": "Преобразовать в Compose", 83 | "Docker Run": "Docker Run", 84 | "active": "акт.", 85 | "exited": "ост.", 86 | "inactive": "неакт.", 87 | "Appearance": "Внешний вид", 88 | "Security": "Безопасность", 89 | "About": "О продукте", 90 | "Allowed commands:": "Разрешенные команды:", 91 | "Internal Networks": "Внутренние сети", 92 | "External Networks": "Внешние сети", 93 | "No External Networks": "Нет внешних сетей", 94 | "downStack": "Остановить и деактивировать", 95 | "reverseProxyMsg1": "Используете обратный прокси?", 96 | "reconnecting...": "Переподключение…", 97 | "Cannot connect to the socket server.": "Не удается подключиться к сокет-серверу.", 98 | "url": "URL-адрес | URL-адреса", 99 | "extra": "Дополнительно", 100 | "reverseProxyMsg2": "Проверьте, как настроить его для WebSocket", 101 | "connecting...": "Подключение к сокет-серверу…", 102 | "newUpdate": "Доступно обновление", 103 | "currentEndpoint": "Текущий", 104 | "agentOnline": "В сети", 105 | "agentOffline": "Не в сети", 106 | "connecting": "Подключение", 107 | "connect": "Подключить", 108 | "addAgent": "Добавить Агента", 109 | "agentAddedSuccessfully": "Агент успешно добавлен.", 110 | "removeAgent": "Удалить агента", 111 | "removeAgentMsg": "Вы уверены, что хотите удалить этого агента?", 112 | "dockgeAgent": "Агент Dockge | Агенты Dockge", 113 | "dockgeURL": "URL-адрес Dockge (например: http://127.0.0.1:5001)", 114 | "agentRemovedSuccessfully": "Агент успешно удален.", 115 | "LongSyntaxNotSupported": "Длинный синтаксис здесь не поддерживается. Пожалуйста, используйте редактор YAML." 116 | } 117 | -------------------------------------------------------------------------------- /frontend/src/lang/sl.json: -------------------------------------------------------------------------------- 1 | { 2 | "languageName": "Slovenščina", 3 | "Create your admin account": "Ustvarite svoj skrbniški račun", 4 | "authIncorrectCreds": "Napačno uporabniško ime ali geslo.", 5 | "PasswordsDoNotMatch": "Gesli se ne ujemata.", 6 | "Repeat Password": "Ponovi geslo", 7 | "Create": "Ustvari", 8 | "signedInDisp": "Prijavljeni kot {0}", 9 | "signedInDispDisabled": "Preverjanje pristnosti onemogočeno.", 10 | "home": "Domov", 11 | "console": "Konzola", 12 | "registry": "Register", 13 | "compose": "Compose", 14 | "addFirstStackMsg": "Ustvarite svoj prvi Stack!", 15 | "stackName": "Ime Stack-a", 16 | "deployStack": "Razporedi", 17 | "deleteStack": "Izbriši", 18 | "stopStack": "Ustavi", 19 | "restartStack": "Ponovni zagon", 20 | "updateStack": "Posodobi", 21 | "startStack": "Zaženi", 22 | "editStack": "Uredi", 23 | "discardStack": "Zavrzi", 24 | "saveStackDraft": "Shrani", 25 | "notAvailableShort": "Ni na voljo", 26 | "deleteStackMsg": "Ste prepričani, da želite izbrisati ta Stack?", 27 | "stackNotManagedByDockgeMsg": "Ta Stack ni upravljan s strani Dockge.", 28 | "primaryHostname": "Osnovno gostiteljsko ime", 29 | "general": "Splošno", 30 | "container": "Kontejner | Kontejnerji", 31 | "scanFolder": "Preglej Stack mapo", 32 | "dockerImage": "Slika", 33 | "restartPolicyUnlessStopped": "Razen ko je zaustavljeno", 34 | "restartPolicyAlways": "Vedno", 35 | "restartPolicyOnFailure": "Ob napaki", 36 | "restartPolicyNo": "Ne", 37 | "environmentVariable": "Okoljska spremenljivka | Okoljske spremenljivke", 38 | "restartPolicy": "Politika ponovnega zagona", 39 | "containerName": "Ime kontejnerja", 40 | "port": "Vrata | Vrata", 41 | "volume": "Zvezek | Zvezki", 42 | "network": "Omrežje | Omrežja", 43 | "dependsOn": "Odvisnost kontejnerja | Odvisnosti kontejnerjev", 44 | "addListItem": "Dodaj {0}", 45 | "deleteContainer": "Izbriši", 46 | "addContainer": "Dodaj kontejner", 47 | "addNetwork": "Dodaj omrežje", 48 | "disableauth.message1": "Ste prepričani, da želite onemogočiti overjanje?", 49 | "disableauth.message2": "Namerno je zasnovano za scenarije, kjer nameravate izvajati avtentikacijo tretjih oseb pred Dockge, kot so Cloudflare Access, Authelia ali druge avtentikacijske mehanizme.", 50 | "passwordNotMatchMsg": "Ponovljeno geslo se ne ujema.", 51 | "autoGet": "Samodejno pridobi", 52 | "add": "Dodaj", 53 | "Edit": "Uredi", 54 | "applyToYAML": "Uporabi za YAML", 55 | "createExternalNetwork": "Ustvari", 56 | "addInternalNetwork": "Dodaj", 57 | "Save": "Shrani", 58 | "Language": "Jezik", 59 | "Current User": "Trenutni uporabnik", 60 | "Change Password": "Spremeni geslo", 61 | "Current Password": "Trenutno geslo", 62 | "New Password": "Novo geslo", 63 | "Repeat New Password": "Ponovi novo geslo", 64 | "Update Password": "Posodobi geslo", 65 | "Advanced": "Napredno", 66 | "Please use this option carefully!": "Prosimo, uporabite to možnost previdno!", 67 | "Enable Auth": "Omogoči overjanje", 68 | "Disable Auth": "Onemogoči overjanje", 69 | "I understand, please disable": "Razumem, prosim onemogočite", 70 | "Leave": "Zapusti", 71 | "Frontend Version": "Različica vmesnika", 72 | "Check Update On GitHub": "Preveri posodobitve na GitHubu", 73 | "Show update if available": "Prikaži posodobitve, če so na voljo", 74 | "Also check beta release": "Preveri tudi beta izdaje", 75 | "Remember me": "Zapomni si me", 76 | "Login": "Prijava", 77 | "Username": "Uporabniško ime", 78 | "Password": "Geslo", 79 | "Settings": "Nastavitve", 80 | "Logout": "Odjava", 81 | "Lowercase only": "Samo male črke", 82 | "Convert to Compose": "Pretvori v Compose", 83 | "Docker Run": "Zagon Dockerja", 84 | "active": "aktivno", 85 | "exited": "izklopljeno", 86 | "inactive": "neaktivno", 87 | "Appearance": "Videz", 88 | "Security": "Varnost", 89 | "About": "O nas", 90 | "Allowed commands:": "Dovoljeni ukazi:", 91 | "Internal Networks": "Notranja omrežja", 92 | "External Networks": "Zunanja omrežja", 93 | "No External Networks": "Ni zunanjih omrežij", 94 | "downStack": "Ustavi & Deaktiviraj", 95 | "connecting...": "Povezovanje s strežnikom…", 96 | "reverseProxyMsg1": "Uporabljate obratni proxy?", 97 | "extra": "Dodatno", 98 | "reconnecting...": "Ponovna povezava …", 99 | "newUpdate": "Nova posodobitev", 100 | "reverseProxyMsg2": "Preverite, kako ga konfigurirati za WebSocket", 101 | "Cannot connect to the socket server.": "Ni mogoče vzpostaviti povezave s strežnikom vtičnic.", 102 | "url": "URL | URL-ji", 103 | "currentEndpoint": "Trenutni", 104 | "dockgeURL": "Dockge URL (npr. http://127.0.0.1:5001)", 105 | "agentOnline": "Aktivno", 106 | "agentOffline": "Neaktivno", 107 | "connecting": "Povezujem", 108 | "connect": "Poveži", 109 | "addAgent": "Dodaj agenta", 110 | "dockgeAgent": "Dockge agent | Dockge agenti", 111 | "agentAddedSuccessfully": "Agent dodan uspešno.", 112 | "agentRemovedSuccessfully": "Agent uspešno odstranjen.", 113 | "removeAgent": "Odstrani agent", 114 | "removeAgentMsg": "Ali ste prepričani, da želite odstraniti agenta?", 115 | "LongSyntaxNotSupported": "Long syntax-a ni podprta tukaj. Prosim uporabite YAML urejevalnik." 116 | } 117 | -------------------------------------------------------------------------------- /frontend/src/lang/th.json: -------------------------------------------------------------------------------- 1 | { 2 | "languageName": "อังกฤษ", 3 | "Create your admin account": "สร้างบัญชีผู้ดูแลระบบของคุณ", 4 | "authIncorrectCreds": "ชื่อผู้ใช้หรือรหัสผ่านไม่ถูกต้อง", 5 | "PasswordsDoNotMatch": "รหัสผ่านไม่ตรงกัน", 6 | "Repeat Password": "ยืนยันรหัสผ่าน", 7 | "Create": "สร้าง", 8 | "signedInDisp": "ลงชื่อเข้าใช้ในนาม {0}", 9 | "signedInDispDisabled": "ปิดใช้งาน Auth", 10 | "home": "หน้าหลัก", 11 | "console": "คอนโซล", 12 | "registry": "Registry", 13 | "compose": "Compose", 14 | "addFirstStackMsg": "Compose stack แรกของคุณ!", 15 | "stackName": "ชื่อ Stack", 16 | "deployStack": "ปรับใช้", 17 | "deleteStack": "ลบ", 18 | "stopStack": "หยุด", 19 | "restartStack": "เริ่มใหม่", 20 | "updateStack": "อัปเดต", 21 | "startStack": "เริ่มต้น", 22 | "downStack": "หยุดการทำงาน", 23 | "editStack": "แก้ไข", 24 | "discardStack": "ยกเลิก", 25 | "saveStackDraft": "บันทึก", 26 | "notAvailableShort": "N/A", 27 | "deleteStackMsg": "คุณแน่ใจหรือไม่ว่าต้องการลบ stack นี้", 28 | "stackNotManagedByDockgeMsg": "stack นี้ไม่ได้รับการจัดการโดย Dockge", 29 | "primaryHostname": "ชื่อโฮสต์หลัก", 30 | "general": "ทั่วไป", 31 | "container": "Container | Containers", 32 | "scanFolder": "สแกนโฟลเดอร์ Stacks", 33 | "dockerImage": "Image", 34 | "restartPolicyUnlessStopped": "Unless Stopped", 35 | "restartPolicyAlways": "Always", 36 | "restartPolicyOnFailure": "On Failure", 37 | "restartPolicyNo": "No", 38 | "environmentVariable": "Environment Variable | Environment Variables", 39 | "restartPolicy": "เริ่มต้น Policy ใหม่", 40 | "containerName": "ชื่อ Container", 41 | "port": "พอร์ต | พอร์ต", 42 | "volume": "ปริมาณ | ปริมาณ", 43 | "network": "เครือข่าย | เครือข่าย", 44 | "dependsOn": "Container Dependency | Container Dependencies", 45 | "addListItem": "เพิ่ม {0}", 46 | "deleteContainer": "ลบ", 47 | "addContainer": "เพิ่ม Container", 48 | "addNetwork": "เพิ่ม เครือข่าย", 49 | "disableauth.message1": "คุณแน่ใจหรือไม่ว่าต้องการ ปิดใช้งานการตรวจสอบสิทธิ์?", 50 | "disableauth.message2": "ได้รับการออกแบบมาสำหรับสถานการณ์ ที่คุณตั้งใจจะใช้การตรวจสอบสิทธิ์ของบุคคลที่สาม หน้า Dockge เช่น Cloudflare Access, Authelia หรือกลไกการตรวจสอบสิทธิ์อื่นๆ", 51 | "passwordNotMatchMsg": "รหัสผ่านซ้ำไม่ตรงกัน", 52 | "autoGet": "รับอัตโนมัติ", 53 | "add": "เพิ่ม", 54 | "Edit": "แก้ไข", 55 | "applyToYAML": "นำไปใช้เป็น YAML", 56 | "createExternalNetwork": "สร้าง", 57 | "addInternalNetwork": "เพิ่ม", 58 | "Save": "บันทึก", 59 | "Language": "ภาษา", 60 | "Current User": "ผู้ใช้งานปัจจุบัน", 61 | "Change Password": "เปลี่ยนรหัสผ่าน", 62 | "Current Password": "รหัสผ่านปัจจุบัน", 63 | "New Password": "รหัสผ่านใหม่", 64 | "Repeat New Password": "รหัสผ่านใหม่ซ้ำ", 65 | "Update Password": "อัปเดตรหัสผ่าน", 66 | "Advanced": "ขั้นสูง", 67 | "Please use this option carefully!": "โปรดใช้ตัวเลือกนี้อย่างระมัดระวัง!", 68 | "Enable Auth": "เปิดใช้งาน Auth", 69 | "Disable Auth": "ปิดใช้งาน Auth", 70 | "I understand, please disable": "ฉันเข้าใจ กรุณาปิดการใช้งาน", 71 | "Leave": "ออก", 72 | "Frontend Version": "เวอร์ชัน Frontend", 73 | "Check Update On GitHub": "ตรวจสอบการอัปเดตบน GitHub", 74 | "Show update if available": "แสดงการอัปเดตหากมี", 75 | "Also check beta release": "สามารถตรวจสอบรุ่นเบต้าได้", 76 | "Remember me": "จดจำฉัน", 77 | "Login": "เข้าสู่ระบบ", 78 | "Username": "ชื่อผู้ใช้", 79 | "Password": "รหัสผ่าน", 80 | "Settings": "การตั้งค่า", 81 | "Logout": "ออกจากระบบ", 82 | "Lowercase only": "ตัวเล็กทั้งหมด", 83 | "Convert to Compose": "แปลงเป็น Compose", 84 | "Docker Run": "เรียกใช้ Docker", 85 | "active": "ใช้งานอยู่", 86 | "exited": "ปิดลงแล้ว", 87 | "inactive": "ไม่ได้ใช้งาน", 88 | "Appearance": "รูปลักษณ์", 89 | "Security": "ความปลอดภัย", 90 | "About": "เกี่ยวกับ", 91 | "Allowed commands:": "คำสั่งที่อนุญาต:", 92 | "Internal Networks": "เครือข่ายภายใน", 93 | "External Networks": "เครือข่ายภายนอก", 94 | "No External Networks": "ไม่มีเครือข่ายภายนอก", 95 | "reverseProxyMsg2": "ตรวจสอบวิธีกำหนดค่าสำหรับ WebSocket", 96 | "Cannot connect to the socket server.": "ไม่สามารถเชื่อมต่อกับเซิร์ฟเวอร์ socket ได้", 97 | "reverseProxyMsg1": "ใช้ Reverse Proxy หรือไม่?", 98 | "connecting...": "กำลังเชื่อมต่อกับเซิร์ฟเวอร์ socket…", 99 | "url": "URL | URLs", 100 | "extra": "พิเศษ", 101 | "reconnecting...": "กำลังเชื่อมต่อใหม่…", 102 | "newUpdate": "อัปเดตใหม่", 103 | "dockgeAgent": "เอเย่นต์ Dockge | เอเย่นต์ Dockge", 104 | "currentEndpoint": "ปัจุบัน", 105 | "agentOnline": "ออนไลน์", 106 | "agentOffline": "ออฟไลน์", 107 | "connecting": "กำลังเชื่อมต่อ", 108 | "connect": "เชื่อมต่อ", 109 | "addAgent": "เพิ่มเอเย่นต์", 110 | "agentAddedSuccessfully": "เพิ่มเอเย่นต์สำเร็จ", 111 | "agentRemovedSuccessfully": "ลบเอเย่นต์สำเร็จ", 112 | "removeAgent": "ลบเอเย่นต์", 113 | "removeAgentMsg": "คุณแน่ใจหรือไม่ที่จะลบเอเย่นต์นี้?", 114 | "dockgeURL": "ลิ้งก์ Dockge (เช่น http://127.0.0.1:5001)", 115 | "LongSyntaxNotSupported": "Syntax แบบยาสไม่รองรับที่นี่ กรุณาใช้ตัวแก้ไข YAML" 116 | } 117 | -------------------------------------------------------------------------------- /frontend/src/lang/ur.json: -------------------------------------------------------------------------------- 1 | { 2 | "languageName": "اردو", 3 | "Create your admin account": "اپنا ایڈمن اکاؤنٹ بنائیں", 4 | "authIncorrectCreds": "غلط صارف نام یا پاس ورڈ.", 5 | "PasswordsDoNotMatch": "پاس ورڈز کوئی مماثل نہیں ہیں۔", 6 | "Repeat Password": "پاس ورڈ دوبارہ لکھیے", 7 | "Create": "بنانا", 8 | "signedInDisp": "بطور {0} سائن ان", 9 | "signedInDispDisabled": "توثیق غیر فعال۔", 10 | "home": "گھر", 11 | "console": "تسلی", 12 | "registry": "رجسٹری", 13 | "compose": "تحریر", 14 | "addFirstStackMsg": "اپنا پہلا اسٹیک کمپوز کریں!", 15 | "stackName": "اسٹیک کا نام", 16 | "deployStack": "تعینات", 17 | "deleteStack": "حذف کریں", 18 | "stopStack": "روکو", 19 | "restartStack": "دوبارہ شروع کریں", 20 | "updateStack": "اپ ڈیٹ", 21 | "startStack": "شروع کریں", 22 | "editStack": "ترمیم", 23 | "discardStack": "رد کر دیں", 24 | "saveStackDraft": "محفوظ کریں۔", 25 | "notAvailableShort": "N / A", 26 | "deleteStackMsg": "کیا آپ واقعی اس اسٹیک کو حذف کرنا چاہتے ہیں؟", 27 | "stackNotManagedByDockgeMsg": "یہ اسٹیک Dockge کے زیر انتظام نہیں ہے۔", 28 | "primaryHostname": "بنیادی میزبان نام", 29 | "general": "جنرل", 30 | "container": "کنٹینر | کنٹینرز", 31 | "scanFolder": "اسٹیک فولڈر کو اسکین کریں", 32 | "dockerImage": "تصویر", 33 | "restartPolicyUnlessStopped": "جب تک روکا نہیں جاتا", 34 | "restartPolicyAlways": "ہمیشہ", 35 | "restartPolicyOnFailure": "ناکامی پر", 36 | "restartPolicyNo": "نہیں", 37 | "environmentVariable": "ماحولیاتی متغیر | ماحولیاتی تغیرات", 38 | "restartPolicy": "پالیسی کو دوبارہ شروع کریں", 39 | "containerName": "کنٹینر کا نام", 40 | "port": "پورٹ | بندرگاہیں", 41 | "volume": "والیوم | جلدیں", 42 | "network": "نیٹ ورک | نیٹ ورکس", 43 | "dependsOn": "کنٹینر انحصار | کنٹینر انحصار", 44 | "addListItem": "شامل کریں {0}", 45 | "deleteContainer": "حذف کریں", 46 | "addContainer": "کنٹینر شامل کریں", 47 | "addNetwork": "نیٹ ورک شامل کریں", 48 | "disableauth.message1": "کیا آپ واقعی تصدیق کو غیر فعال کرنا چاہتے ہیں؟", 49 | "disableauth.message2": "یہ ان منظرناموں کے لیے ڈیزائن کیا گیا ہے جہاں آپ کا ارادہ ہے تیسرے فریق کی توثیق کو لاگو کرنے کا Dockge کے سامنے جیسے Cloudflare Access، Authelia یا دیگر تصدیقی طریقہ کار۔", 50 | "passwordNotMatchMsg": "دہرانے والا پاس ورڈ مماثل نہیں ہے۔", 51 | "autoGet": "آٹو حاصل کریں", 52 | "add": "شامل کریں", 53 | "Edit": "ترمیم", 54 | "applyToYAML": "YAML پر درخواست دیں", 55 | "createExternalNetwork": "بنانا", 56 | "addInternalNetwork": "شامل کریں", 57 | "Save": "محفوظ کریں", 58 | "Language": "زبان", 59 | "Current User": "موجودہ صارف", 60 | "Change Password": "پاس ورڈ تبدیل کریں", 61 | "Current Password": "موجودہ خفیہ لفظ", 62 | "New Password": "نیا پاس ورڈ", 63 | "Repeat New Password": "نیا پاس ورڈ دہرائیں", 64 | "Update Password": "پاس ورڈ اپ ڈیٹ کریں", 65 | "Advanced": "ترقی یافتہ", 66 | "Please use this option carefully!": "براہ کرم اس اختیار کو احتیاط سے استعمال کریں!", 67 | "Enable Auth": "تصدیق کو فعال کریں", 68 | "Disable Auth": "توثیق کو غیر فعال کریں", 69 | "I understand, please disable": "میں سمجھتا ہوں، براہ کرم غیر فعال کریں", 70 | "Leave": "چھوڑ دو", 71 | "Frontend Version": "فرنٹ اینڈ ورژن", 72 | "Check Update On GitHub": "گیتوب پر اپ ڈیٹ چیک کریں", 73 | "Show update if available": "اگر دستیاب ہو تو اپ ڈیٹ دکھائیں", 74 | "Also check beta release": "بیٹا ریلیز بھی چیک کریں", 75 | "Remember me": "مجھے پہچانتے ہو", 76 | "Login": "لاگ ان کریں", 77 | "Username": "صارف نام", 78 | "Password": "پاس ورڈ", 79 | "Settings": "ترتیبات", 80 | "Logout": "لاگ آوٹ", 81 | "Lowercase only": "صرف لوئر کیس", 82 | "Convert to Compose": "تحریر میں تبدیل کریں", 83 | "Docker Run": "ڈاکر رن", 84 | "active": "فعال", 85 | "exited": "باہر نکلا", 86 | "inactive": "غیر فعال", 87 | "Appearance": "ظہور", 88 | "Security": "سیکورٹی", 89 | "About": "کے بارے میں", 90 | "Allowed commands:": "اجازت شدہ احکامات:", 91 | "Internal Networks": "اندرونی نیٹ ورکس", 92 | "External Networks": "بیرونی نیٹ ورکس", 93 | "No External Networks": "کوئی بیرونی نیٹ ورک نہیں", 94 | "reverseProxyMsg1": "ایک ریورس پراکسی کا استعمال کرتے ہوئے؟", 95 | "Cannot connect to the socket server.": "ساکٹ سرور سے منسلک نہیں ہو سکتا۔", 96 | "reconnecting...": "دوبارہ منسلک ہو رہا ہے…", 97 | "connecting...": "ساکٹ سرور سے منسلک ہو رہا ہے…", 98 | "url": "یو آر ایل | یو آر ایل", 99 | "extra": "اضافی", 100 | "downStack": "روکیں اور غیر فعال", 101 | "reverseProxyMsg2": "اسے WebSocket کے لیے ترتیب دینے کا طریقہ چیک کریں", 102 | "newUpdate": "نئی تازہ کاری", 103 | "dockgeAgent": "ڈاکج ایجنٹ | ڈاکج ایجنٹس", 104 | "currentEndpoint": "کرنٹ", 105 | "dockgeURL": "Dockge URL (جیسے http://127.0.0.1:5001)", 106 | "agentOnline": "آن لائن", 107 | "agentOffline": "آف لائن", 108 | "connecting": "جڑ رہا ہے", 109 | "connect": "جڑیں", 110 | "addAgent": "ایجنٹ شامل کریں", 111 | "agentAddedSuccessfully": "ایجنٹ کامیابی کے ساتھ شامل ہو گیا۔", 112 | "agentRemovedSuccessfully": "ایجنٹ کو کامیابی سے ہٹا دیا گیا۔", 113 | "removeAgent": "ایجنٹ کو ہٹا دیں", 114 | "removeAgentMsg": "کیا آپ واقعی اس ایجنٹ کو ہٹانا چاہتے ہیں؟", 115 | "LongSyntaxNotSupported": "لمبا نحو یہاں تعاون یافتہ نہیں ہے۔ براہ کرم YAML ایڈیٹر استعمال کریں۔" 116 | } 117 | -------------------------------------------------------------------------------- /frontend/src/lang/vi.json: -------------------------------------------------------------------------------- 1 | { 2 | "authIncorrectCreds": "Sai tên người dùng hoặc mật khẩu.", 3 | "PasswordsDoNotMatch": "Mật khẩu không khớp.", 4 | "Repeat Password": "Lặp Lại Mật Khẩu", 5 | "Create": "Tạo", 6 | "signedInDisp": "Đã đăng nhập với tư cách {0}", 7 | "home": "Trang chủ", 8 | "console": "Console", 9 | "compose": "Compose", 10 | "registry": "Registry", 11 | "stackName": "Tên Stack", 12 | "deployStack": "Triển khai", 13 | "deleteStack": "Xoá", 14 | "stopStack": "Dừng", 15 | "restartStack": "Khởi động lại", 16 | "signedInDispDisabled": "Đã Tắt Xác Thực Đăng Nhập.", 17 | "startStack": "Bắt đầu", 18 | "downStack": "Dừng & Ngưng hoạt động", 19 | "editStack": "Chỉnh sửa", 20 | "saveStackDraft": "Lưu", 21 | "notAvailableShort": "N/A", 22 | "deleteStackMsg": "Bạn có chắc chắn muốn xoá stack này?", 23 | "primaryHostname": "Tên Host Chính", 24 | "scanFolder": "Quét Thư Mục Stack", 25 | "restartPolicyAlways": "Luôn Luôn", 26 | "restartPolicyOnFailure": "Khi Có Lỗi", 27 | "restartPolicyNo": "Không", 28 | "environmentVariable": "Biến Môi Trường | Các Biến Môi Trường", 29 | "restartPolicy": "Chính Sách Khởi Động Lại", 30 | "containerName": "Tên Container", 31 | "port": "Cổng | Cổng", 32 | "addListItem": "Thêm {0}", 33 | "deleteContainer": "Xoá", 34 | "addContainer": "Thêm Container", 35 | "addNetwork": "Thêm Mạng", 36 | "passwordNotMatchMsg": "Mật khẩu nhập lại không khớp.", 37 | "autoGet": "Tự Động Lấy", 38 | "add": "Thêm", 39 | "Edit": "Chỉnh sửa", 40 | "applyToYAML": "Áp dụng cho YAML", 41 | "createExternalNetwork": "Tạo", 42 | "addInternalNetwork": "Thêm", 43 | "Save": "Lưu", 44 | "Language": "Ngôn ngữ", 45 | "Current User": "Người Dùng Hiện Tại", 46 | "Change Password": "Đổi Mật Khẩu", 47 | "Current Password": "Mật Khẩu Hiện Tại", 48 | "New Password": "Mật Khẩu Mới", 49 | "Repeat New Password": "Nhập Lại Mật Khẩu Mới", 50 | "Update Password": "Cập Nhật Mật Khẩu", 51 | "Advanced": "Nâng cao", 52 | "Please use this option carefully!": "Vui lòng sử dụng tuỳ chọn này cẩn thận!", 53 | "Enable Auth": "Kích Hoạt Xác Thực Đăng Nhập", 54 | "Disable Auth": "Vô Hiệu Xác Thực Đăng Nhập", 55 | "I understand, please disable": "Tôi hiểu, vui lòng vô hiệu", 56 | "Leave": "Rời", 57 | "Frontend Version": "Phiên Bản Giao Diện Người Dùng", 58 | "Check Update On GitHub": "Kiểm Tra Cập Nhật Trên Github", 59 | "Also check beta release": "Kiểm tra cả bản phát hành beta", 60 | "Remember me": "Ghi nhớ tôi", 61 | "Login": "Đăng nhập", 62 | "Username": "Tên người dùng", 63 | "Password": "Mật khẩu", 64 | "Settings": "Cài đặt", 65 | "Logout": "Đăng xuất", 66 | "Lowercase only": "Chỉ viết thường", 67 | "Convert to Compose": "Chuyển đổi sang Compose", 68 | "Docker Run": "Chạy Docker", 69 | "active": "hoạt động", 70 | "exited": "đã thoát", 71 | "inactive": "không hoạt động", 72 | "Security": "Bảo Mật", 73 | "Appearance": "Giao Diện", 74 | "About": "Về", 75 | "Allowed commands:": "Các lệnh được cho phép:", 76 | "Internal Networks": "Mạng Nội Bộ", 77 | "External Networks": "Mạng Ngoại Vi", 78 | "No External Networks": "Không Có Mạng Ngoại Vi", 79 | "reverseProxyMsg1": "Đang sử dụng Reverse Proxy?", 80 | "reverseProxyMsg2": "Xem cách để cấu hình nó cho WebSocket", 81 | "Cannot connect to the socket server.": "Không thể kết nối tới máy chủ socket.", 82 | "reconnecting...": "Đang kết nối lại…", 83 | "connecting...": "Đang kết nối tới máy chủ socket…", 84 | "url": "URL", 85 | "extra": "Bổ sung", 86 | "newUpdate": "Cập Nhật Mới", 87 | "dockgeAgent": "Dockge Agent", 88 | "currentEndpoint": "Đang sử dụng", 89 | "dockgeURL": "URL của Dockge (v.d. http://127.0.0.1:5001)", 90 | "agentOnline": "Trực tuyến", 91 | "agentOffline": "Ngoại tuyến", 92 | "connecting": "Đang kết nối", 93 | "connect": "Kết nối", 94 | "addAgent": "Thêm Agent", 95 | "agentAddedSuccessfully": "Agent đã được thêm thành công.", 96 | "agentRemovedSuccessfully": "Agent đã được xoá thành công.", 97 | "removeAgent": "Xoá Agent", 98 | "removeAgentMsg": "Bạn có chắc chắn muốn xoá agent này?", 99 | "languageName": "Tiếng Việt", 100 | "Create your admin account": "Tạo tài khoản admin của bạn", 101 | "addFirstStackMsg": "Tạo stack đầu tiên của bạn!", 102 | "volume": "Volume | Volume", 103 | "updateStack": "Cập nhật", 104 | "network": "Mạng | Mạng", 105 | "discardStack": "Huỷ", 106 | "stackNotManagedByDockgeMsg": "Stack này không được quản lý bởi Dockge.", 107 | "dependsOn": "Container Phụ Thuộc | Các Container Phụ Thuộc", 108 | "general": "Tổng Quan", 109 | "disableauth.message1": "Bạn có chắc chắn muốn tắt xác thực đăng nhập?", 110 | "container": "Container", 111 | "disableauth.message2": "Nó được thiết kế trong hoàn cảnh mà bạn dự định triển khai xác thực đăng nhập bên thứ ba trước Dockge như là Cloudflare Access, Authelia hay các phương thức xác minh đăng nhập khác.", 112 | "dockerImage": "Image", 113 | "Show update if available": "Hiển thị cập nhật nếu có", 114 | "restartPolicyUnlessStopped": "Trừ Khi Dừng Lại" 115 | } 116 | -------------------------------------------------------------------------------- /frontend/src/lang/zh-CN.json: -------------------------------------------------------------------------------- 1 | { 2 | "languageName": "简体中文", 3 | "Create your admin account": "创建你的管理员账号", 4 | "authIncorrectCreds": "用户名或密码错误。", 5 | "PasswordsDoNotMatch": "两次输入的密码不一致。", 6 | "Repeat Password": "重复以确认密码", 7 | "Create": "创建", 8 | "signedInDisp": "当前用户: {0}", 9 | "signedInDispDisabled": "已禁用身份验证。", 10 | "home": "主页", 11 | "console": "终端", 12 | "registry": "镜像仓库", 13 | "compose": "Compose", 14 | "addFirstStackMsg": "组合你的第一个堆栈!", 15 | "stackName": "堆栈名称", 16 | "deployStack": "部署", 17 | "deleteStack": "删除", 18 | "stopStack": "停止", 19 | "restartStack": "重启", 20 | "updateStack": "更新", 21 | "startStack": "启动", 22 | "editStack": "编辑", 23 | "discardStack": "放弃", 24 | "saveStackDraft": "保存", 25 | "notAvailableShort": "不可用", 26 | "deleteStackMsg": "你确定要删除这个堆栈吗?", 27 | "stackNotManagedByDockgeMsg": "这个堆栈不由Dockge管理。", 28 | "primaryHostname": "主机名", 29 | "general": "常规", 30 | "container": "容器 | 容器组", 31 | "scanFolder": "扫描堆栈文件夹", 32 | "dockerImage": "镜像", 33 | "restartPolicyUnlessStopped": "除非手动停止", 34 | "restartPolicyAlways": "始终", 35 | "restartPolicyOnFailure": "在失败时", 36 | "restartPolicyNo": "不重启", 37 | "environmentVariable": "环境变量 | 环境变量组", 38 | "restartPolicy": "重启策略", 39 | "containerName": "容器名", 40 | "port": "端口 | 端口组", 41 | "volume": "数据卷 | 数据卷组", 42 | "network": "网络 | 网络组", 43 | "dependsOn": "容器依赖 | 容器依赖关系", 44 | "addListItem": "添加 {0}", 45 | "deleteContainer": "删除", 46 | "addContainer": "添加容器", 47 | "addNetwork": "添加网络", 48 | "disableauth.message1": "你确定要禁用身份验证吗?", 49 | "disableauth.message2": "该选项设计用于某些场景,例如在Dockge之上接入第三方认证,比如Cloudflare Access、Authelia或其他认证机制,如果你不清楚这个选项的作用,不要禁用验证!", 50 | "passwordNotMatchMsg": "两次输入的密码不一致。", 51 | "autoGet": "自动获取", 52 | "add": "添加", 53 | "Edit": "编辑", 54 | "applyToYAML": "应用到YAML", 55 | "createExternalNetwork": "创建", 56 | "addInternalNetwork": "添加", 57 | "Save": "保存", 58 | "Language": "语言", 59 | "Current User": "当前用户", 60 | "Change Password": "更换密码", 61 | "Current Password": "当前密码", 62 | "New Password": "新密码", 63 | "Repeat New Password": "重复以确认新密码", 64 | "Update Password": "更新密码", 65 | "Advanced": "进阶", 66 | "Please use this option carefully!": "请谨慎使用该选项!", 67 | "Enable Auth": "启用验证", 68 | "Disable Auth": "禁用验证", 69 | "I understand, please disable": "我已了解风险,确认禁用", 70 | "Leave": "离开", 71 | "Frontend Version": "前端版本", 72 | "Check Update On GitHub": "在GitHub上检查更新", 73 | "Show update if available": "有更新时提醒我", 74 | "Also check beta release": "同时检查Beta渠道更新", 75 | "Remember me": "记住我", 76 | "Login": "登录", 77 | "Username": "用户名", 78 | "Password": "密码", 79 | "Settings": "设置", 80 | "Logout": "登出", 81 | "Lowercase only": "仅小写字母", 82 | "Convert to Compose": "转换为Compose格式", 83 | "Docker Run": "Docker启动", 84 | "active": "已启动", 85 | "exited": "已退出", 86 | "inactive": "未启动", 87 | "Appearance": "外观", 88 | "Security": "安全", 89 | "About": "关于", 90 | "Allowed commands:": "允许使用的指令:", 91 | "Internal Networks": "内部网络", 92 | "External Networks": "外部网络", 93 | "No External Networks": "无外部网络", 94 | "reconnecting...": "重连中…", 95 | "reverseProxyMsg2": "检查如何配置WebSocket", 96 | "reverseProxyMsg1": "正在使用反向代理?", 97 | "connecting...": "正在连接到socket服务器…", 98 | "Cannot connect to the socket server.": "无法连接到socket服务器。", 99 | "url": "网址 | 网址", 100 | "extra": "额外", 101 | "downStack": "停止并置于非活动状态", 102 | "newUpdate": "新版本", 103 | "dockgeURL": "Dockge地址 (例如 http://127.0.0.1:5001)", 104 | "agentOnline": "在线", 105 | "agentOffline": "离线", 106 | "connecting": "连接中", 107 | "connect": "连接", 108 | "dockgeAgent": "Dockge代理", 109 | "currentEndpoint": "当前", 110 | "addAgent": "添加代理", 111 | "agentRemovedSuccessfully": "代理移除成功。", 112 | "removeAgent": "移除代理", 113 | "removeAgentMsg": "您确定要移除此代理?", 114 | "agentAddedSuccessfully": "代理添加成功。", 115 | "LongSyntaxNotSupported": "此处不支持Long syntax,请使用YAML编辑器。", 116 | "Lost connection to the socket server. Reconnecting...": "已断开socket服务器连接,重新连接中...", 117 | "Saved": "已保存", 118 | "Deployed": "已部署", 119 | "Deleted": "已删除", 120 | "Updated": "已更新", 121 | "Started": "已启动", 122 | "Stopped": "已停止", 123 | "Restarted": "已重启", 124 | "Switch to sh": "切换至sh", 125 | "terminal": "终端", 126 | "CurrentHostname": "未设置:沿用当前主机名", 127 | "New Container Name...": "新的容器名称...", 128 | "Network name...": "网络名称...", 129 | "Select a network...": "选择网络...", 130 | "NoNetworksAvailable": "网络不可用.你需要在正确的方向先添加内部网络或者启用外部网络.", 131 | "Downed": "已宕机" 132 | } 133 | -------------------------------------------------------------------------------- /frontend/src/lang/zh-TW.json: -------------------------------------------------------------------------------- 1 | { 2 | "languageName": "繁體中文 (台灣)", 3 | "Create your admin account": "建立您的管理員帳號", 4 | "authIncorrectCreds": "使用者名稱或密碼錯誤。", 5 | "PasswordsDoNotMatch": "兩次輸入的密碼不一致。", 6 | "Repeat Password": "重複以確認密碼", 7 | "Create": "建立", 8 | "signedInDisp": "目前使用者:{0}", 9 | "signedInDispDisabled": "已停用身份驗證。", 10 | "home": "首頁", 11 | "console": "主控台", 12 | "registry": "映像倉庫", 13 | "compose": "撰寫", 14 | "addFirstStackMsg": "組合您的第一個堆疊!", 15 | "stackName": "堆疊名稱", 16 | "deployStack": "部署", 17 | "deleteStack": "刪除", 18 | "stopStack": "停止", 19 | "restartStack": "重啟", 20 | "updateStack": "更新", 21 | "startStack": "啟動", 22 | "editStack": "編輯", 23 | "discardStack": "丟棄", 24 | "saveStackDraft": "儲存", 25 | "notAvailableShort": "不可用", 26 | "deleteStackMsg": "您確定要刪除這個堆疊嗎?", 27 | "stackNotManagedByDockgeMsg": "這個堆疊不由 Dockge 管理。", 28 | "primaryHostname": "主機名稱", 29 | "general": "一般", 30 | "container": "容器 | 容器群組", 31 | "scanFolder": "掃描堆疊資料夾", 32 | "dockerImage": "映像", 33 | "restartPolicyUnlessStopped": "除非手動停止", 34 | "restartPolicyAlways": "始終", 35 | "restartPolicyOnFailure": "在失敗時", 36 | "restartPolicyNo": "不重啟", 37 | "environmentVariable": "環境變數 | 環境變數群組", 38 | "restartPolicy": "重啟策略", 39 | "containerName": "容器名稱", 40 | "port": "連接埠 | 連接埠群組", 41 | "volume": "資料卷 | 資料卷群組", 42 | "network": "網路 | 網路群組", 43 | "dependsOn": "容器依賴 | 容器依賴關係", 44 | "addListItem": "新增 {0}", 45 | "deleteContainer": "刪除容器", 46 | "addContainer": "新增容器", 47 | "addNetwork": "新增網路", 48 | "disableauth.message1": "您確定要停用身份驗證嗎?", 49 | "disableauth.message2": "該選項設計用於某些場景,例如在 Dockge 之介接接第三方身份驗證,例如 Cloudflare Access、Authelia 或其他身份驗證機制。", 50 | "passwordNotMatchMsg": "兩次輸入的密碼不一致。", 51 | "autoGet": "自動取得", 52 | "add": "新增", 53 | "Edit": "編輯", 54 | "applyToYAML": "套用到 YAML", 55 | "createExternalNetwork": "建立", 56 | "addInternalNetwork": "新增", 57 | "Save": "儲存", 58 | "Language": "語言", 59 | "Current User": "目前使用者", 60 | "Change Password": "更換密碼", 61 | "Current Password": "目前密碼", 62 | "New Password": "新密碼", 63 | "Repeat New Password": "重複以確認新密碼", 64 | "Update Password": "更新密碼", 65 | "Advanced": "進階", 66 | "Please use this option carefully!": "請謹慎使用該選項!", 67 | "Enable Auth": "啟用驗證", 68 | "Disable Auth": "停用驗證", 69 | "I understand, please disable": "我已了解風險,確認停用", 70 | "Leave": "離開", 71 | "Frontend Version": "前端版本", 72 | "Check Update On GitHub": "在 GitHub 上檢查更新", 73 | "Show update if available": "有更新時提醒我", 74 | "Also check beta release": "同時檢查 Beta 版更新", 75 | "Remember me": "記住我", 76 | "Login": "登入", 77 | "Username": "使用者名稱", 78 | "Password": "密碼", 79 | "Settings": "設定", 80 | "Logout": "登出", 81 | "Lowercase only": "僅小寫字母", 82 | "Convert to Compose": "轉換為 Compose 格式", 83 | "Docker Run": "Docker 啟動", 84 | "active": "已啟動", 85 | "exited": "已退出", 86 | "inactive": "未啟動", 87 | "Appearance": "外觀", 88 | "Security": "安全", 89 | "About": "關於", 90 | "Allowed commands:": "允許使用的指令:", 91 | "Internal Networks": "內部網路", 92 | "External Networks": "外部網路", 93 | "No External Networks": "無外部網路", 94 | "downStack": "停止及未啟動化", 95 | "reverseProxyMsg1": "在使用反向代理嗎?", 96 | "reverseProxyMsg2": "點擊這裡了解如何為 WebSocket 配置反向代理", 97 | "Cannot connect to the socket server.": "無法連接到 Socket 伺服器。", 98 | "reconnecting...": "重新連線中…", 99 | "connecting...": "連線至 Socket 伺服器中…", 100 | "url": "網址 | 網址", 101 | "extra": "額外", 102 | "newUpdate": "新版本", 103 | "currentEndpoint": "目前", 104 | "dockgeURL": "Dockge URL(例如:http://127.0.0.1:5001)", 105 | "agentOnline": "線上", 106 | "connecting": "正在連線", 107 | "agentOffline": "離線", 108 | "Lost connection to the socket server. Reconnecting...": "與伺服器斷線。正在重新連線...", 109 | "dockgeAgent": "Dockge代理 | Dockge代理", 110 | "Saved": "已儲存", 111 | "Switch to sh": "切換到 sh", 112 | "NoNetworksAvailable": "沒有可以使用的網路。您需要先在右側新增內部網路或啟用外部網路。", 113 | "LongSyntaxNotSupported": "這裡不支援長語法。請使用 YAML 編輯器。", 114 | "connect": "連接", 115 | "addAgent": "新增代理", 116 | "agentAddedSuccessfully": "代理新增成功。", 117 | "agentRemovedSuccessfully": "代理刪除成功。", 118 | "Deployed": "已佈署", 119 | "Deleted": "已刪除", 120 | "Updated": "已更新", 121 | "Started": "開始", 122 | "Stopped": "已停止", 123 | "Restarted": "重新啟動", 124 | "Downed": "斷線", 125 | "terminal": "終端", 126 | "CurrentHostname": "(取消設定:依據目前主機名稱)", 127 | "New Container Name...": "新容器名稱...", 128 | "Network name...": "網路名稱...", 129 | "Select a network...": "選擇網路...", 130 | "removeAgent": "刪除代理", 131 | "removeAgentMsg": "您確定要刪除這個代理嗎?" 132 | } 133 | -------------------------------------------------------------------------------- /frontend/src/layouts/EmptyLayout.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 8 | 9 | -------------------------------------------------------------------------------- /frontend/src/main.ts: -------------------------------------------------------------------------------- 1 | // Dayjs init inside this, so it has to be the first import 2 | import "../../common/util-common"; 3 | 4 | import { createApp, defineComponent, h } from "vue"; 5 | import App from "./App.vue"; 6 | import { router } from "./router"; 7 | import { FontAwesomeIcon } from "./icon.js"; 8 | import { i18n } from "./i18n"; 9 | 10 | // Dependencies 11 | import "bootstrap"; 12 | import Toast, { POSITION, useToast } from "vue-toastification"; 13 | import "@xterm/xterm/lib/xterm.js"; 14 | 15 | // CSS 16 | import "@fontsource/jetbrains-mono"; 17 | import "vue-toastification/dist/index.css"; 18 | import "@xterm/xterm/css/xterm.css"; 19 | import "./styles/main.scss"; 20 | 21 | // Minxins 22 | import socket from "./mixins/socket"; 23 | import lang from "./mixins/lang"; 24 | import theme from "./mixins/theme"; 25 | 26 | // Set Title 27 | document.title = document.title + " - " + location.host; 28 | 29 | const app = createApp(rootApp()); 30 | 31 | app.use(Toast, { 32 | position: POSITION.BOTTOM_RIGHT, 33 | showCloseButtonOnHover: true, 34 | }); 35 | app.use(router); 36 | app.use(i18n); 37 | app.component("FontAwesomeIcon", FontAwesomeIcon); 38 | app.mount("#app"); 39 | 40 | /** 41 | * Root Vue component 42 | */ 43 | function rootApp() { 44 | const toast = useToast(); 45 | 46 | return defineComponent({ 47 | mixins: [ 48 | socket, 49 | lang, 50 | theme, 51 | ], 52 | data() { 53 | return { 54 | loggedIn: false, 55 | allowLoginDialog: false, 56 | username: null, 57 | }; 58 | }, 59 | computed: { 60 | 61 | }, 62 | methods: { 63 | 64 | /** 65 | * Show success or error toast dependant on response status code 66 | * @param {object} res Response object 67 | * @returns {void} 68 | */ 69 | toastRes(res) { 70 | let msg = res.msg; 71 | if (res.msgi18n) { 72 | if (msg != null && typeof msg === "object") { 73 | msg = this.$t(msg.key, msg.values); 74 | } else { 75 | msg = this.$t(msg); 76 | } 77 | } 78 | 79 | if (res.ok) { 80 | toast.success(msg); 81 | } else { 82 | toast.error(msg); 83 | } 84 | }, 85 | /** 86 | * Show a success toast 87 | * @param {string} msg Message to show 88 | * @returns {void} 89 | */ 90 | toastSuccess(msg : string) { 91 | toast.success(this.$t(msg)); 92 | }, 93 | 94 | /** 95 | * Show an error toast 96 | * @param {string} msg Message to show 97 | * @returns {void} 98 | */ 99 | toastError(msg : string) { 100 | toast.error(this.$t(msg)); 101 | }, 102 | }, 103 | render: () => h(App), 104 | }); 105 | } 106 | -------------------------------------------------------------------------------- /frontend/src/mixins/lang.ts: -------------------------------------------------------------------------------- 1 | import { currentLocale } from "../i18n"; 2 | import { setPageLocale } from "../util-frontend"; 3 | import { defineComponent } from "vue"; 4 | const langModules = import.meta.glob("../lang/*.json"); 5 | 6 | export default defineComponent({ 7 | data() { 8 | return { 9 | language: currentLocale(), 10 | }; 11 | }, 12 | 13 | watch: { 14 | async language(lang) { 15 | await this.changeLang(lang); 16 | }, 17 | }, 18 | 19 | async created() { 20 | if (this.language !== "en") { 21 | await this.changeLang(this.language); 22 | } 23 | }, 24 | 25 | methods: { 26 | /** 27 | * Change the application language 28 | * @param {string} lang Language code to switch to 29 | * @returns {Promise} 30 | */ 31 | async changeLang(lang : string) { 32 | const message = (await langModules["../lang/" + lang + ".json"]()).default; 33 | this.$i18n.setLocaleMessage(lang, message); 34 | this.$i18n.locale = lang; 35 | localStorage.locale = lang; 36 | setPageLocale(); 37 | } 38 | } 39 | }); 40 | -------------------------------------------------------------------------------- /frontend/src/mixins/theme.ts: -------------------------------------------------------------------------------- 1 | import { defineComponent } from "vue"; 2 | 3 | export default defineComponent({ 4 | data() { 5 | return { 6 | system: (window.matchMedia("(prefers-color-scheme: dark)").matches) ? "dark" : "light", 7 | userTheme: localStorage.theme, 8 | statusPageTheme: "light", 9 | forceStatusPageTheme: false, 10 | path: "", 11 | }; 12 | }, 13 | 14 | computed: { 15 | theme() { 16 | if (this.userTheme === "auto") { 17 | return this.system; 18 | } 19 | return this.userTheme; 20 | }, 21 | 22 | isDark() { 23 | return this.theme === "dark"; 24 | } 25 | }, 26 | 27 | watch: { 28 | "$route.fullPath"(path) { 29 | this.path = path; 30 | }, 31 | 32 | userTheme(to, from) { 33 | localStorage.theme = to; 34 | }, 35 | 36 | styleElapsedTime(to, from) { 37 | localStorage.styleElapsedTime = to; 38 | }, 39 | 40 | theme(to, from) { 41 | document.body.classList.remove(from); 42 | document.body.classList.add(this.theme); 43 | this.updateThemeColorMeta(); 44 | }, 45 | 46 | userHeartbeatBar(to, from) { 47 | localStorage.heartbeatBarTheme = to; 48 | }, 49 | 50 | heartbeatBarTheme(to, from) { 51 | document.body.classList.remove(from); 52 | document.body.classList.add(this.heartbeatBarTheme); 53 | } 54 | }, 55 | 56 | mounted() { 57 | // Default Dark 58 | if (! this.userTheme) { 59 | this.userTheme = "dark"; 60 | } 61 | 62 | document.body.classList.add(this.theme); 63 | this.updateThemeColorMeta(); 64 | }, 65 | 66 | methods: { 67 | /** 68 | * Update the theme color meta tag 69 | * @returns {void} 70 | */ 71 | updateThemeColorMeta() { 72 | if (this.theme === "dark") { 73 | document.querySelector("#theme-color").setAttribute("content", "#161B22"); 74 | } else { 75 | document.querySelector("#theme-color").setAttribute("content", "#5cdd8b"); 76 | } 77 | } 78 | } 79 | }); 80 | 81 | -------------------------------------------------------------------------------- /frontend/src/pages/Console.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 44 | 45 | 50 | -------------------------------------------------------------------------------- /frontend/src/pages/ContainerTerminal.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 70 | 71 | 76 | -------------------------------------------------------------------------------- /frontend/src/pages/Dashboard.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 37 | 38 | 43 | -------------------------------------------------------------------------------- /frontend/src/pages/Setup.vue: -------------------------------------------------------------------------------- 1 | 47 | 48 | 101 | 102 | 139 | -------------------------------------------------------------------------------- /frontend/src/router.ts: -------------------------------------------------------------------------------- 1 | import { createRouter, createWebHistory } from "vue-router"; 2 | 3 | import Layout from "./layouts/Layout.vue"; 4 | import Setup from "./pages/Setup.vue"; 5 | import Dashboard from "./pages/Dashboard.vue"; 6 | import DashboardHome from "./pages/DashboardHome.vue"; 7 | import Console from "./pages/Console.vue"; 8 | import Compose from "./pages/Compose.vue"; 9 | import ContainerTerminal from "./pages/ContainerTerminal.vue"; 10 | 11 | const Settings = () => import("./pages/Settings.vue"); 12 | 13 | // Settings - Sub Pages 14 | import Appearance from "./components/settings/Appearance.vue"; 15 | import General from "./components/settings/General.vue"; 16 | const Security = () => import("./components/settings/Security.vue"); 17 | import About from "./components/settings/About.vue"; 18 | 19 | const routes = [ 20 | { 21 | path: "/empty", 22 | component: Layout, 23 | children: [ 24 | { 25 | path: "", 26 | component: Dashboard, 27 | children: [ 28 | { 29 | name: "DashboardHome", 30 | path: "/", 31 | component: DashboardHome, 32 | children: [ 33 | { 34 | path: "/compose", 35 | component: Compose, 36 | }, 37 | { 38 | path: "/compose/:stackName/:endpoint", 39 | component: Compose, 40 | }, 41 | { 42 | path: "/compose/:stackName", 43 | component: Compose, 44 | }, 45 | { 46 | path: "/terminal/:stackName/:serviceName/:type", 47 | component: ContainerTerminal, 48 | name: "containerTerminal", 49 | }, 50 | { 51 | path: "/terminal/:stackName/:serviceName/:type/:endpoint", 52 | component: ContainerTerminal, 53 | name: "containerTerminalEndpoint", 54 | }, 55 | ] 56 | }, 57 | { 58 | path: "/console", 59 | component: Console, 60 | }, 61 | { 62 | path: "/console/:endpoint", 63 | component: Console, 64 | }, 65 | { 66 | path: "/settings", 67 | component: Settings, 68 | children: [ 69 | { 70 | path: "general", 71 | component: General, 72 | }, 73 | { 74 | path: "appearance", 75 | component: Appearance, 76 | }, 77 | { 78 | path: "security", 79 | component: Security, 80 | }, 81 | { 82 | path: "about", 83 | component: About, 84 | }, 85 | ] 86 | }, 87 | ] 88 | }, 89 | ] 90 | }, 91 | { 92 | path: "/setup", 93 | component: Setup, 94 | }, 95 | ]; 96 | 97 | export const router = createRouter({ 98 | linkActiveClass: "active", 99 | history: createWebHistory(), 100 | routes, 101 | }); 102 | -------------------------------------------------------------------------------- /frontend/src/styles/localization.scss: -------------------------------------------------------------------------------- 1 | html[lang='fa'] { 2 | #app { 3 | font-family: 'IRANSans', 'Iranian Sans','B Nazanin', 'Tahoma', ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, segoe ui, Roboto, helvetica neue, Arial, noto sans, sans-serif, apple color emoji, segoe ui emoji, segoe ui symbol, noto color emoji; 4 | } 5 | } 6 | 7 | ul.multiselect__content { 8 | padding-left: 0 !important; 9 | } 10 | -------------------------------------------------------------------------------- /frontend/src/styles/vars.scss: -------------------------------------------------------------------------------- 1 | $primary: #74c2ff; 2 | $danger: #dc3545; 3 | $warning: #f8a306; 4 | $maintenance: #1747f5; 5 | $link-color: #111; 6 | $border-radius: 50rem; 7 | 8 | $highlight: #9dd1ff; 9 | $highlight-white: #e7faec; 10 | 11 | $dark-font-color: #b1b8c0; 12 | $dark-font-color2: #020b05; 13 | $dark-font-color3: #575c62; 14 | $dark-bg: #0d1117; 15 | $dark-bg2: #070a10; 16 | $dark-border-color: #1d2634; 17 | $dark-header-bg: #161b22; 18 | 19 | $easing-in: cubic-bezier(0.54, 0.78, 0.55, 0.97); 20 | $easing-out: cubic-bezier(0.25, 0.46, 0.45, 0.94); 21 | $easing-in-out: cubic-bezier(0.79, 0.14, 0.15, 0.86); 22 | 23 | $dropdown-border-radius: 0.5rem; 24 | 25 | $primary-gradient: linear-gradient(135deg, #74c2ff 0%, #74c2ff 75%, #86e6a9); 26 | $primary-gradient-active: linear-gradient(135deg, #74c2ff 0%, #74c2ff 50%, #86e6a9); 27 | -------------------------------------------------------------------------------- /frontend/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | /// 3 | 4 | declare module "*.vue" { 5 | import type { DefineComponent } from "vue"; 6 | const component: DefineComponent<{}, {}, any>; 7 | export default component; 8 | } 9 | -------------------------------------------------------------------------------- /frontend/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vite"; 2 | import vue from "@vitejs/plugin-vue"; 3 | import Components from "unplugin-vue-components/vite"; 4 | import { BootstrapVueNextResolver } from "unplugin-vue-components/resolvers"; 5 | import viteCompression from "vite-plugin-compression"; 6 | import "vue"; 7 | 8 | const viteCompressionFilter = /\.(js|mjs|json|css|html|svg)$/i; 9 | 10 | // https://vitejs.dev/config/ 11 | export default defineConfig({ 12 | server: { 13 | port: 5000, 14 | }, 15 | define: { 16 | "FRONTEND_VERSION": JSON.stringify(process.env.npm_package_version), 17 | }, 18 | root: "./frontend", 19 | build: { 20 | outDir: "../frontend-dist", 21 | }, 22 | plugins: [ 23 | vue(), 24 | Components({ 25 | resolvers: [ BootstrapVueNextResolver() ], 26 | }), 27 | viteCompression({ 28 | algorithm: "gzip", 29 | filter: viteCompressionFilter, 30 | }), 31 | viteCompression({ 32 | algorithm: "brotliCompress", 33 | filter: viteCompressionFilter, 34 | }), 35 | ], 36 | }); 37 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dockge", 3 | "version": "1.5.0", 4 | "type": "module", 5 | "engines": { 6 | "node": ">= 22.14.0" 7 | }, 8 | "scripts": { 9 | "fmt": "eslint \"**/*.{ts,vue}\" --fix", 10 | "lint": "eslint \"**/*.{ts,vue}\"", 11 | "check-ts": "tsc --noEmit", 12 | "start": "tsx ./backend/index.ts", 13 | "dev": "concurrently -k -r \"wait-on tcp:5000 && npm run dev:backend \" \"npm run dev:frontend\"", 14 | "dev:backend": "cross-env NODE_ENV=development tsx watch --inspect ./backend/index.ts", 15 | "dev:frontend": "cross-env NODE_ENV=development vite --host --config ./frontend/vite.config.ts", 16 | "release-final": "tsx ./extra/test-docker.ts && tsx extra/update-version.ts && npm run build:frontend && npm run build:docker", 17 | "release-beta": "tsx ./extra/test-docker.ts && tsx extra/update-version.ts && npm run build:frontend && npm run build:docker-beta", 18 | "build:frontend": "vite build --config ./frontend/vite.config.ts", 19 | "build:docker-base": "docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/dockge:base -f ./docker/Base.Dockerfile . --push", 20 | "build:docker": "node ./extra/env2arg.js docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/dockge:latest -t louislam/dockge:1 -t louislam/dockge:$VERSION -t louislam/dockge:beta -t louislam/dockge:nightly --target release -f ./docker/Dockerfile . --push", 21 | "build:docker-beta": "node ./extra/env2arg.js docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/dockge:beta -t louislam/dockge:$VERSION --target release -f ./docker/Dockerfile . --push", 22 | "build:docker-nightly": "npm run build:frontend && docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/dockge:nightly --target nightly -f ./docker/Dockerfile . --push", 23 | "build:healthcheck": "docker buildx build -f docker/BuildHealthCheck.Dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/dockge:build-healthcheck . --push", 24 | "start-docker": "docker run --rm -p 5001:5001 --name dockge louislam/dockge:latest", 25 | "mark-as-nightly": "tsx ./extra/mark-as-nightly.ts", 26 | "reformat-changelog": "tsx ./extra/reformat-changelog.ts", 27 | "reset-password": "tsx ./extra/reset-password.ts" 28 | }, 29 | "dependencies": { 30 | "@homebridge/node-pty-prebuilt-multiarch": "0.11.14", 31 | "@inventage/envsubst": "^0.16.0", 32 | "@louislam/sqlite3": "~15.1.6", 33 | "bcryptjs": "~2.4.3", 34 | "check-password-strength": "~2.0.10", 35 | "command-exists": "~1.2.9", 36 | "compare-versions": "~6.1.1", 37 | "composerize": "~1.7.1", 38 | "croner": "~8.1.2", 39 | "dayjs": "~1.11.13", 40 | "dotenv": "~16.3.2", 41 | "express": "~4.21.2", 42 | "express-static-gzip": "~2.1.8", 43 | "http-graceful-shutdown": "~3.1.14", 44 | "jsonwebtoken": "~9.0.2", 45 | "jwt-decode": "~3.1.2", 46 | "knex": "~2.5.1", 47 | "limiter-es6-compat": "~2.1.2", 48 | "mysql2": "~3.12.0", 49 | "promisify-child-process": "~4.1.2", 50 | "redbean-node": "~0.3.3", 51 | "semver": "^7.7.1", 52 | "socket.io": "~4.8.1", 53 | "socket.io-client": "~4.8.1", 54 | "timezones-list": "~3.0.3", 55 | "ts-command-line-args": "~2.5.1", 56 | "tsx": "~4.19.3", 57 | "type-fest": "~4.3.3", 58 | "yaml": "~2.3.4" 59 | }, 60 | "devDependencies": { 61 | "@actions/github": "^6.0.0", 62 | "@fontsource/jetbrains-mono": "^5.2.5", 63 | "@fortawesome/fontawesome-svg-core": "6.4.2", 64 | "@fortawesome/free-regular-svg-icons": "6.4.2", 65 | "@fortawesome/free-solid-svg-icons": "6.4.2", 66 | "@fortawesome/vue-fontawesome": "3.0.3", 67 | "@types/bcryptjs": "^2.4.6", 68 | "@types/bootstrap": "~5.2.10", 69 | "@types/command-exists": "~1.2.3", 70 | "@types/express": "~4.17.21", 71 | "@types/jsonwebtoken": "~9.0.9", 72 | "@types/semver": "^7.7.0", 73 | "@typescript-eslint/eslint-plugin": "~6.8.0", 74 | "@typescript-eslint/parser": "~6.8.0", 75 | "@vitejs/plugin-vue": "~5.2.3", 76 | "@xterm/addon-fit": "beta", 77 | "@xterm/xterm": "beta", 78 | "bootstrap": "5.3.2", 79 | "bootstrap-vue-next": "~0.14.10", 80 | "concurrently": "^8.2.2", 81 | "cross-env": "~7.0.3", 82 | "eslint": "~8.50.0", 83 | "eslint-plugin-jsdoc": "~46.8.2", 84 | "eslint-plugin-vue": "~9.32.0", 85 | "prismjs": "~1.30.0", 86 | "sass": "~1.68.0", 87 | "typescript": "~5.2.2", 88 | "unplugin-vue-components": "~0.25.2", 89 | "vite": "~5.4.15", 90 | "vite-plugin-compression": "~0.5.1", 91 | "vue": "~3.5.13", 92 | "vue-eslint-parser": "~9.3.2", 93 | "vue-i18n": "~10.0.6", 94 | "vue-prism-editor": "2.0.0-alpha.2", 95 | "vue-qrcode": "~2.2.2", 96 | "vue-router": "~4.5.0", 97 | "vue-toastification": "2.0.0-rc.5", 98 | "wait-on": "^7.2.0", 99 | "xterm-addon-web-links": "~0.9.0" 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "ESNext", 4 | "target": "ESNext", 5 | "strict": true, 6 | "moduleResolution": "bundler", 7 | "skipLibCheck": true 8 | }, 9 | "include": [ 10 | "backend/**/*", 11 | "common/**/*" 12 | ] 13 | } 14 | --------------------------------------------------------------------------------