Compare commits

...

26 Commits

Author SHA1 Message Date
89e1297b1c Update workflow 2023-11-18 13:57:54 +08:00
cd9f9d2427 Eslint 2023-11-18 13:54:27 +08:00
be7a296fc7 Update vite to 5.0.0 and other dependencies 2023-11-18 13:45:52 +08:00
5ce6b90546 Support compose.y[a]ml and docker-compose.y[a]ml (#55)
* Support compose.y[a]ml and docker-compose.y[a]ml

* using for-loop to iterate over supported compose filenames

* Fix lint

---------

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

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

* add healthy and unhealthy status to be displayed

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

* Add lint to workflow

* Fix lint

---------

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

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

* Update README.md

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

View File

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

57
.github/workflows/ci.yml vendored Normal file
View File

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

View File

@ -6,6 +6,8 @@
A fancy, easy-to-use and reactive self-hosted docker compose.yaml stack-oriented manager.
![GitHub Repo stars](https://img.shields.io/github/stars/louislam/dockge?logo=github) ![GitHub issues](https://img.shields.io/github/issues/louislam/dockge?logo=github) ![GitHub pull requests](https://img.shields.io/github/issues-pr/louislam/dockge?logo=github) ![Docker Pulls](https://img.shields.io/docker/pulls/louislam/dockge?logo=docker) ![Docker Image Version (latest semver)](https://img.shields.io/docker/v/louislam/dockge?logo=docker) ![GitHub last commit (branch)](https://img.shields.io/github/last-commit/louislam/dockge/master?logo=github) ![GitHub](https://img.shields.io/github/license/louislam/dockge?logo=github)
<img src="https://github.com/louislam/dockge/assets/1336778/26a583e1-ecb1-4a8d-aedf-76157d714ad7" width="900" alt="" />
View Video: https://youtu.be/AWAlOQeNpgU?t=48
@ -32,11 +34,12 @@ View Video: https://youtu.be/AWAlOQeNpgU?t=48
## 🔧 How to Install
Requirements:
- [Docker CE](https://docs.docker.com/engine/install/) 20+ is recommended
- [Docker Compose V2](https://docs.docker.com/compose/install/linux/)
- [Docker CE](https://docs.docker.com/engine/install/) 20+ is recommended / Podman
- (Docker only) [Docker Compose Plugin](https://docs.docker.com/compose/install/linux/)
- (Podman only) podman-docker (Debian: `apt install podman-docker`)
- OS:
- As long as you can run Docker CE, it should be fine, but:
- Debian/Raspbian Buster or lower is not supported, please upgrade to Bullseye
- As long as you can run Docker CE / Podman, it should be fine, but:
- Debian/Raspbian Buster or lower is not supported, please upgrade to Bullseye or higher
- Arch: armv7, arm64, amd64 (a.k.a x86_64)
### Basic
@ -55,7 +58,7 @@ curl https://raw.githubusercontent.com/louislam/dockge/master/compose.yaml --out
# Start Server
docker compose up -d
# If you are using docker-compose V1
# If you are using docker-compose V1 or Podman
# docker-compose up -d
```
@ -65,8 +68,6 @@ Dockge is now running on http://localhost:5001
If you want to store your stacks in another directory, you can change the `DOCKGE_STACKS_DIR` environment variable and volumes.
For example, if you want to store your stacks in `/my-stacks`:
```yaml
version: "3.8"
services:
@ -83,18 +84,20 @@ services:
# If you want to use private registries, you need to share the auth file with Dockge:
# - /root/.docker/:/root/.docker
# Your stacks directory in the host
# (The paths inside container must be the same as the host)
- /my-stacks:/my-stacks
# Your stacks directory in the host (The paths inside container must be the same as the host)
# ⚠️⚠️ If you did it wrong, your data could end up be written into a wrong path.
# ✔️✔️✔️✔️ CORRECT EXAMPLE: - /my-stacks:/my-stacks (Both paths match)
# ❌❌❌❌ WRONG EXAMPLE: - /docker:/my-stacks (Both paths do not match)
- /opt/stacks:/opt/stacks
environment:
# Tell Dockge where is your stacks directory
- DOCKGE_STACKS_DIR=/my-stacks
- DOCKGE_STACKS_DIR=/opt/stacks
```
## How to Update
```bash
cd /opt/stacks
cd /opt/dockge
docker compose pull
docker compose up -d
```
@ -119,20 +122,22 @@ docker compose up -d
If you love this project, please consider giving this project a ⭐.
## 🗣️ Discussion / Ask for Help
## 🗣️
Please go to https://github.com/louislam/dockge/discussions
### Bug Report
https://github.com/louislam/dockge/issues
### Ask for Help / Discussions
https://github.com/louislam/dockge/discussions
## FAQ
#### "Dockge"?
"Dockge" is a coinage word which is created by myself. I hope it sounds like `Badge` but replacing with `Dock` - `Dock-ge`.
"Dockge" is a coinage word which is created by myself. I hope it sounds like `Dodge`.
The naming idea was coming from Twitch emotes like `sadge`, `bedge` or `wokege`. They are all ending with `-ge`.
If you are not comfortable with the pronunciation, you can call it `Dockage`.
#### Can I manage a single container without `compose.yaml`?
The main objective of Dockge is that try to use docker `compose.yaml` for everything. If you want to manage a single container, you can just use Portainer or Docker CLI.

View File

@ -80,7 +80,7 @@ export class Database {
* @param {boolean} noLog Should logs not be output?
* @returns {Promise<void>}
*/
static async connect(autoloadModels = true, noLog = false) {
static async connect(autoloadModels = true) {
const acquireConnectionTimeout = 120 * 1000;
let dbConfig;
try {

View File

@ -291,7 +291,7 @@ export class DockgeServer {
}
// Run every 5 seconds
const job = Cron("*/2 * * * * *", {
Cron("*/2 * * * * *", {
protect: true, // Enabled over-run protection.
}, () => {
log.debug("server", "Cron job running");

View File

@ -1,5 +1,4 @@
import { SocketHandler } from "../socket-handler.js";
import { Socket } from "socket.io";
import { DockgeServer } from "../dockge-server";
import { log } from "../log";
import { R } from "redbean-node";

View File

@ -24,6 +24,7 @@ export class Stack {
protected _status: number = UNKNOWN;
protected _composeYAML?: string;
protected _configFilePath?: string;
protected _composeFileName: string = "compose.yaml";
protected server: DockgeServer;
protected combinedTerminal? : Terminal;
@ -34,6 +35,15 @@ export class Stack {
this.name = name;
this.server = server;
this._composeYAML = composeYAML;
// Check if compose file name is different from compose.yaml
const supportedFileNames = [ "compose.yaml", "compose.yml", "docker-compose.yml", "docker-compose.yaml" ];
for (const filename of supportedFileNames) {
if (fs.existsSync(path.join(this.path, filename))) {
this._composeFileName = filename;
break;
}
}
}
toJSON() : object {
@ -50,6 +60,7 @@ export class Stack {
status: this._status,
tags: [],
isManagedByDockge: this.isManagedByDockge,
composeFileName: this._composeFileName,
};
}
@ -72,9 +83,9 @@ export class Stack {
}
validate() {
// Check name, allows [a-z][A-Z][0-9] _ - only
if (!this.name.match(/^[a-zA-Z0-9_-]+$/)) {
throw new ValidationError("Stack name can only contain [a-z][A-Z][0-9] _ - only");
// Check name, allows [a-z][0-9] _ - only
if (!this.name.match(/^[a-z0-9_-]+$/)) {
throw new ValidationError("Stack name can only contain [a-z][0-9] _ - only");
}
// Check YAML format
@ -84,7 +95,7 @@ export class Stack {
get composeYAML() : string {
if (this._composeYAML === undefined) {
try {
this._composeYAML = fs.readFileSync(path.join(this.path, "compose.yaml"), "utf-8");
this._composeYAML = fs.readFileSync(path.join(this.path, this._composeFileName), "utf-8");
} catch (e) {
this._composeYAML = "";
}
@ -135,7 +146,7 @@ export class Stack {
}
// Write or overwrite the compose.yaml
fs.writeFileSync(path.join(dir, "compose.yaml"), this.composeYAML);
fs.writeFileSync(path.join(dir, this._composeFileName), this.composeYAML);
}
async deploy(socket? : DockgeSocket) : Promise<number> {
@ -149,7 +160,7 @@ export class Stack {
async delete(socket?: DockgeSocket) : Promise<number> {
const terminalName = getComposeTerminalName(this.name);
let exitCode = await Terminal.exec(this.server, socket, terminalName, "docker", [ "compose", "down", "--remove-orphans", "--rmi", "all" ], this.path);
let exitCode = await Terminal.exec(this.server, socket, terminalName, "docker", [ "compose", "down", "--remove-orphans" ], this.path);
if (exitCode !== 0) {
throw new Error("Failed to delete, please check the terminal output for more information.");
}
@ -177,6 +188,11 @@ export class Stack {
for (let filename of filenameList) {
try {
// Check if it is a directory
let stat = fs.statSync(path.join(stacksDir, filename));
if (!stat.isDirectory()) {
continue;
}
let stack = this.getStack(server, filename);
stack._status = CREATED_FILE;
stackList.set(filename, stack);
@ -346,7 +362,11 @@ export class Stack {
for (let line of lines) {
try {
let obj = JSON.parse(line);
if (obj.Health === "") {
statusList.set(obj.Service, obj.State);
} else {
statusList.set(obj.Service, obj.Health);
}
} catch (e) {
}
}

View File

@ -7,14 +7,16 @@ services:
# Host Port : Container Port
- 5001:5001
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- ./data:/app/data
# If you want to use private registries, you need to share the auth file with Dockge:
# - /root/.docker/:/root/.docker
# Docker Socket
- /var/run/docker.sock:/var/run/docker.sock
# Dockge Config
- ./data:/app/data
# Your stacks directory in the host (The paths inside container must be the same as the host)
# ⚠️⚠️ If you did it wrong, your data could end up be written into a wrong path.
# ✔️✔️✔️✔️ CORRECT: - /my-stacks:/my-stacks (Both paths match)
# ❌❌❌❌ WRONG: - /docker:/my-stacks (Both paths do not match)
- /opt/stacks:/opt/stacks
environment:
# Tell Dockge where is your stacks directory

View File

@ -5,7 +5,7 @@
<li v-for="(value, index) in array" :key="index" class="list-group-item">
<select v-model="array[index]" class="no-bg domain-input">
<option value="">Select a network...</option>
<option v-for="option in options" :value="option">{{ option }}</option>
<option v-for="option in options" :key="option" :value="option">{{ option }}</option>
</select>
<font-awesome-icon icon="times" class="action remove ms-2 me-3 text-danger" @click="remove(index)" />

View File

@ -9,7 +9,7 @@
<div v-if="!isEditMode">
<span class="badge me-1" :class="bgStyle">{{ status }}</span>
<a v-for="port in service.ports" :href="parsePort(port).url" target="_blank">
<a v-for="port in service.ports" :key="port" :href="parsePort(port).url" target="_blank">
<span class="badge me-1 bg-secondary">{{ parsePort(port).display }}</span>
</a>
</div>
@ -179,8 +179,10 @@ export default defineComponent({
},
bgStyle() {
if (this.status === "running") {
if (this.status === "running" || this.status === "healthy") {
return "bg-primary";
} else if (this.status === "unhealthy") {
return "bg-danger";
} else {
return "bg-secondary";
}

View File

@ -32,7 +32,7 @@
class="form-control"
@keyup.enter="createExternelNetwork"
/>
<button class="btn btn-normal btn-sm me-2" type="button" @click="">
<button class="btn btn-normal btn-sm me-2" type="button">
{{ $t("createExternalNetwork") }}
</button>
</div>

View File

@ -19,7 +19,6 @@ export default {
computed: {
uptime() {
return "0.00%";
return this.$t("notAvailableShort");
},

View File

@ -68,13 +68,13 @@
</template>
<script>
import HiddenInput from "../../components/HiddenInput.vue";
import dayjs from "dayjs";
import { timezoneList } from "../../util-frontend";
export default {
components: {
HiddenInput,
},
data() {

View File

@ -68,9 +68,10 @@
<h4 class="mb-3">{{ $t("general") }}</h4>
<div class="shadow-box big-padding mb-3">
<!-- Stack Name -->
<div class="mb-3">
<div>
<label for="name" class="form-label">{{ $t("stackName") }}</label>
<input id="name" v-model="stack.name" type="text" class="form-control" required>
<input id="name" v-model="stack.name" type="text" class="form-control" required @blur="stackNameToLowercase">
<div class="form-text">Lowercase only</div>
</div>
</div>
</div>
@ -117,7 +118,7 @@
</div>
</div>
<div class="col-lg-6">
<h4 class="mb-3">compose.yaml</h4>
<h4 class="mb-3">{{ stack.composeFileName }}</h4>
<!-- YAML editor -->
<div class="shadow-box mb-3 editor-box" :class="{'edit-mode' : isEditMode}">
@ -582,6 +583,10 @@ export default {
});
},
stackNameToLowercase() {
this.stack.name = this.stack?.name?.toLowerCase();
},
}
};
</script>

View File

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

View File

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

View File

@ -1,6 +1,6 @@
{
"name": "dockge",
"version": "1.0.1",
"version": "1.0.4",
"type": "module",
"scripts": {
"fmt": "eslint \"**/*.{ts,vue}\" --fix",
@ -8,7 +8,7 @@
"start": "tsx ./backend/index.ts",
"dev:backend": "cross-env NODE_ENV=development tsx watch ./backend/index.ts",
"dev:frontend": "cross-env NODE_ENV=development vite --host --config ./frontend/vite.config.ts",
"release-final": "tsx ./extra/test-docker.ts && pnpm run build:frontend && tsx extra/update-version.ts && npm run build:docker",
"release-final": "tsx ./extra/test-docker.ts && tsx extra/update-version.ts && pnpm run build:frontend && npm run build:docker",
"build:frontend": "vite build --config ./frontend/vite.config.ts",
"build:docker-base": "docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/dockge:base -f ./docker/Base.Dockerfile . --push",
"build:docker": "node ./extra/env2arg.js docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/dockge:latest -t louislam/dockge:1 -t louislam/dockge:$VERSION --target release -f ./docker/Dockerfile . --push",
@ -17,15 +17,14 @@
"mark-as-nightly": "tsx ./extra/mark-as-nightly.ts"
},
"dependencies": {
"@fontsource/jetbrains-mono": "^5.0.17",
"@homebridge/node-pty-prebuilt-multiarch": "~0.11.10",
"@homebridge/node-pty-prebuilt-multiarch": "~0.11.11",
"@louislam/sqlite3": "~15.1.6",
"bcryptjs": "~2.4.3",
"check-password-strength": "~2.0.7",
"command-exists": "~1.2.9",
"compare-versions": "~6.1.0",
"composerize": "~1.4.1",
"croner": "~7.0.4",
"croner": "~7.0.5",
"dayjs": "~1.11.10",
"express": "~4.18.2",
"express-static-gzip": "~2.1.7",
@ -45,17 +44,18 @@
"yaml": "~2.3.4"
},
"devDependencies": {
"@fontsource/jetbrains-mono": "^5.0.17",
"@fortawesome/fontawesome-svg-core": "6.4.2",
"@fortawesome/free-regular-svg-icons": "6.4.2",
"@fortawesome/free-solid-svg-icons": "6.4.2",
"@fortawesome/vue-fontawesome": "3.0.3",
"@types/bootstrap": "~5.2.8",
"@types/bootstrap": "~5.2.9",
"@types/command-exists": "~1.2.3",
"@types/express": "~4.17.21",
"@types/jsonwebtoken": "~9.0.4",
"@types/jsonwebtoken": "~9.0.5",
"@typescript-eslint/eslint-plugin": "~6.8.0",
"@typescript-eslint/parser": "~6.8.0",
"@vitejs/plugin-vue": "~4.3.4",
"@vitejs/plugin-vue": "~4.5.0",
"bootstrap": "5.3.2",
"bootstrap-vue-next": "~0.14.10",
"cross-env": "~7.0.3",
@ -66,7 +66,7 @@
"sass": "~1.68.0",
"typescript": "~5.2.2",
"unplugin-vue-components": "~0.25.2",
"vite": "~4.5.0",
"vite": "~5.0.0",
"vite-plugin-compression": "~0.5.1",
"vue": "~3.3.8",
"vue-eslint-parser": "~9.3.2",
@ -75,7 +75,7 @@
"vue-qrcode": "~2.2.0",
"vue-router": "~4.2.5",
"vue-toastification": "2.0.0-rc.5",
"xterm": "~5.4.0-beta.37",
"xterm": "5.4.0-beta.37",
"xterm-addon-web-links": "~0.9.0"
}
}

629
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff