Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

wip #1

Merged
merged 27 commits into from
Nov 11, 2023
Merged
Prev Previous commit
Next Next commit
wip
  • Loading branch information
louislam committed Nov 10, 2023
commit 9b9234434e1f81380698efc6b7ee00a0b3ca2d28
46 changes: 39 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

# Dockge

A fancy, easy-to-use and reactive docker `compose.yaml` stack manager.
A fancy, easy-to-use and reactive docker `compose.yaml` stack oriented manager.

<img src="https://github.com/louislam/dockge/assets/1336778/26a583e1-ecb1-4a8d-aedf-76157d714ad7" width="900" alt="" />

Expand All @@ -21,23 +21,51 @@ A fancy, easy-to-use and reactive docker `compose.yaml` stack manager.

## 🔧 How to Install

1. Create a directory `dockge`
2. Create or download [`compose.yaml`](https://raw.githubusercontent.com/louislam/dockge/master/compose.yaml) and put it inside `dockge`:
### Basic

Default stacks directory is `/opt/stacks`.

```
# Create a directory that stores your stacks
mkdir -p /opt/stacks

# Create a directory that stores dockge's compose.yaml
mkdir -p /opt/dockge
cd /opt/dockge

# Download the compose.yaml
wget https://raw.githubusercontent.com/louislam/dockge/master/compose.yaml

# Start Server
docker-compose up -d
```

### Advanced

If you want to store your stacks in another directory, you can change the `DOCKGE_STACKS_DIR` environment variable and volumes.

For exmaples, if you want to store your stacks in `/my-stacks`:

```yaml
version: "3.8"
services:
dockge:
image: louislam/dockge:nightly
image: louislam/dockge:1
restart: unless-stopped
ports:
- 5001:5001
volumes:
- ./data:/app/data
- /var/run/docker.sock:/var/run/docker.sock
- ./data:/app/data

# Your stacks directory in the host
# (The paths inside container must be the same as the host)
- /my-stacks:/my-stacks
environment:
# Tell Dockge where is your stacks directory
- DOCKGE_STACKS_DIR=/my-stacks
```
3. `docker-compose up -d`

Dockge is now running on http://localhost:5001

## Motivations

Expand All @@ -57,6 +85,10 @@ The naming idea was coming from Twitch emotes like `sadge`, `bedge` or `wokege`.

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.

## More Ideas?

- Stats
Expand Down
2 changes: 1 addition & 1 deletion backend/database.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ export class Database {
R.freeze(true);

if (autoloadModels) {
await R.autoloadModels("./server/model");
R.autoloadModels("./backend/models", "ts");
}

if (dbConfig.type === "sqlite") {
Expand Down
28 changes: 26 additions & 2 deletions backend/dockge-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import { TerminalSocketHandler } from "./socket-handlers/terminal-socket-handler
import { Stack } from "./stack";
import { Cron } from "croner";
import gracefulShutdown from "http-graceful-shutdown";
import User from "./models/user";

export class DockgeServer {
app : Express;
Expand Down Expand Up @@ -194,7 +195,7 @@ export class DockgeServer {
cors,
});

this.io.on("connection", (socket: Socket) => {
this.io.on("connection", async (socket: Socket) => {
log.info("server", "Socket connected!");

this.sendInfo(socket, true);
Expand All @@ -208,6 +209,20 @@ export class DockgeServer {
for (const socketHandler of this.socketHandlerList) {
socketHandler.create(socket as DockgeSocket, this);
}

// ***************************
// Better do anything after added all socket handlers here
// ***************************

log.debug("auth", "check auto login");
if (await Settings.get("disableAuth")) {
log.info("auth", "Disabled Auth: auto login to admin");
this.afterLogin(socket as DockgeSocket, await R.findOne("user"));
socket.emit("autoLogin");
} else {
log.debug("auth", "need auth");
}

});

this.io.on("disconnect", () => {
Expand All @@ -216,8 +231,17 @@ export class DockgeServer {

}

prepareServer() {
async afterLogin(socket : DockgeSocket, user : User) {
socket.userID = user.id;
socket.join(user.id.toString());

this.sendInfo(socket);

try {
this.sendStackList();
} catch (e) {
log.error("server", e);
}
}

/**
Expand Down
4 changes: 3 additions & 1 deletion backend/models/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export class User extends BeanModel {

/**
* Reset this users password
* @param {string} newPassword Users new password
* @param {string} newPassword
* @returns {Promise<void>}
*/
async resetPassword(newPassword : string) {
Expand All @@ -42,3 +42,5 @@ export class User extends BeanModel {
}

}

export default User;
20 changes: 20 additions & 0 deletions backend/socket-handlers/docker-socket-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,26 @@ export class DockerSocketHandler extends SocketHandler {
callbackError(e, callback);
}
});

// Services status
socket.on("serviceStatusList", async (stackName : unknown, callback) => {
try {
checkLogin(socket);

if (typeof(stackName) !== "string") {
throw new ValidationError("Stack name must be a string");
}

const stack = Stack.getStack(server, stackName);
const serviceStatusList = Object.fromEntries(await stack.getServiceStatusList());
callback({
ok: true,
serviceStatusList,
});
} catch (e) {
callbackError(e, callback);
}
});
}

saveStack(socket : DockgeSocket, server : DockgeServer, name : unknown, composeYAML : unknown, isAdd : unknown) : Stack {
Expand Down
50 changes: 34 additions & 16 deletions backend/socket-handlers/main-socket-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ export class MainSocketHandler extends SocketHandler {
}

log.debug("auth", "afterLogin");
await this.afterLogin(server, socket, user);
await server.afterLogin(socket, user);
log.debug("auth", "afterLogin ok");

log.info("auth", `Successfully logged in user ${decoded.username}. IP=${clientIP}`);
Expand Down Expand Up @@ -129,7 +129,7 @@ export class MainSocketHandler extends SocketHandler {

if (user) {
if (user.twofa_status === 0) {
this.afterLogin(server, socket, user);
server.afterLogin(socket, user);

log.info("auth", `Successfully logged in user ${data.username}. IP=${clientIP}`);

Expand All @@ -152,7 +152,7 @@ export class MainSocketHandler extends SocketHandler {
const verify = notp.totp.verify(data.token, user.twofa_secret, twoFAVerifyOptions);

if (user.twofa_last_token !== data.token && verify) {
this.afterLogin(server, socket, user);
server.afterLogin(socket, user);

await R.exec("UPDATE `user` SET twofa_last_token = ? WHERE id = ? ", [
data.token,
Expand Down Expand Up @@ -189,6 +189,35 @@ export class MainSocketHandler extends SocketHandler {

});

// Change Password
socket.on("changePassword", async (password, callback) => {
try {
checkLogin(socket);

if (! password.newPassword) {
throw new Error("Invalid new password");
}

if (passwordStrength(password.newPassword).value === "Too weak") {
throw new Error("Password is too weak. It should contain alphabetic and numeric characters. It must be at least 6 characters in length.");
}

let user = await doubleCheckPassword(socket, password.currentPassword);
await user.resetPassword(password.newPassword);

callback({
ok: true,
msg: "Password has been updated successfully.",
});

} catch (e) {
callback({
ok: false,
msg: e.message,
});
}
});

socket.on("getSettings", async (callback) => {
try {
checkLogin(socket);
Expand Down Expand Up @@ -221,6 +250,8 @@ export class MainSocketHandler extends SocketHandler {
await doubleCheckPassword(socket, currentPassword);
}

console.log(data);

await Settings.setSettings("general", data);

callback({
Expand All @@ -239,19 +270,6 @@ export class MainSocketHandler extends SocketHandler {
});
}

async afterLogin(server: DockgeServer, socket : DockgeSocket, user : User) {
socket.userID = user.id;
socket.join(user.id.toString());

server.sendInfo(socket);

try {
server.sendStackList(socket);
} catch (e) {
log.error("server", e);
}
}

async login(username : string, password : string) {
if (typeof username !== "string" || typeof password !== "string") {
return null;
Expand Down
22 changes: 22 additions & 0 deletions backend/stack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -333,4 +333,26 @@ export class Stack {
terminal.join(socket);
terminal.start();
}

async getServiceStatusList() {
let statusList = new Map<string, number>();

let res = childProcess.execSync("docker compose ps --format json", {
cwd: this.path,
});

let lines = res.toString().split("\n");

console.log(lines);

for (let line of lines) {
try {
let obj = JSON.parse(line);
statusList.set(obj.Service, obj.State);
} catch (e) {
}
}

return statusList;
}
}
8 changes: 7 additions & 1 deletion compose.yaml
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
version: "3.8"
services:
dockge:
image: louislam/dockge:nightly
image: louislam/dockge:1
restart: unless-stopped
ports:
# Host Port : Container Port
- 5001:5001
volumes:
# 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)
- /opt/stacks:/opt/stacks
environment:
# Tell Dockge where is your stacks directory
- DOCKGE_STACKS_DIR=/opt/stacks
14 changes: 13 additions & 1 deletion frontend/src/components/Container.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<span class="me-1">{{ imageName }}:</span><span class="tag">{{ imageTag }}</span>
</div>
<div v-if="!isEditMode">
<span class="badge bg-primary me-1">Running</span>
<span class="badge me-1" :class="bgStyle">{{ status }}</span>
</div>
</div>
<div class="col-5">
Expand Down Expand Up @@ -146,6 +146,10 @@ export default defineComponent({
type: Boolean,
default: false,
},
status: {
type: String,
default: "N/A",
}
},
emits: [
],
Expand All @@ -156,6 +160,14 @@ export default defineComponent({
},
computed: {

bgStyle() {
if (this.status === "running") {
return "bg-primary";
} else {
return "bg-secondary";
}
},

terminalRouteLink() {
return {
name: "containerTerminal",
Expand Down
Loading