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

Close terminal if there is no clients connecting #60

Merged
merged 5 commits into from
Nov 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions backend/dockge-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import { Cron } from "croner";
import gracefulShutdown from "http-graceful-shutdown";
import User from "./models/user";
import childProcess from "child_process";
import { Terminal } from "./terminal";

export class DockgeServer {
app : Express;
Expand Down Expand Up @@ -230,6 +231,11 @@ export class DockgeServer {

});

if (isDev) {
setInterval(() => {
log.debug("terminal", "Terminal count: " + Terminal.getTerminalCount());
}, 5000);
}
}

async afterLogin(socket : DockgeSocket, user : User) {
Expand Down Expand Up @@ -292,11 +298,11 @@ export class DockgeServer {
log.info("server", `Listening on ${this.config.port}`);
}

// Run every 5 seconds
Cron("*/2 * * * * *", {
// Run every 10 seconds
Cron("*/10 * * * * *", {
protect: true, // Enabled over-run protection.
}, () => {
log.debug("server", "Cron job running");
//log.debug("server", "Cron job running");
this.sendStackList(true);
});

Expand Down
4 changes: 3 additions & 1 deletion backend/socket-handlers/docker-socket-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,9 @@ export class DockerSocketHandler extends SocketHandler {

const stack = Stack.getStack(server, stackName);

stack.joinCombinedTerminal(socket);
if (stack.isManagedByDockge) {
stack.joinCombinedTerminal(socket);
}

callback({
ok: true,
Expand Down
21 changes: 19 additions & 2 deletions backend/socket-handlers/terminal-socket-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,9 +140,26 @@ export class TerminalSocketHandler extends SocketHandler {
}
});

// Close Terminal
socket.on("terminalClose", async (terminalName : unknown, callback : unknown) => {
// Leave Combined Terminal
socket.on("leaveCombinedTerminal", async (stackName : unknown, callback) => {
try {
checkLogin(socket);

log.debug("leaveCombinedTerminal", "Stack name: " + stackName);

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

const stack = Stack.getStack(server, stackName);
await stack.leaveCombinedTerminal(socket);

callback({
ok: true,
});
} catch (e) {
callbackError(e, callback);
}
});

// TODO: Resize Terminal
Expand Down
11 changes: 10 additions & 1 deletion backend/stack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -298,7 +298,7 @@ export class Stack {
}
}
} else {
log.debug("getStack", "Skip FS operations");
//log.debug("getStack", "Skip FS operations");
}

let stack : Stack;
Expand Down Expand Up @@ -374,12 +374,21 @@ export class Stack {
async joinCombinedTerminal(socket: DockgeSocket) {
const terminalName = getCombinedTerminalName(this.name);
const terminal = Terminal.getOrCreateTerminal(this.server, terminalName, "docker", [ "compose", "logs", "-f", "--tail", "100" ], this.path);
terminal.enableKeepAlive = true;
terminal.rows = COMBINED_TERMINAL_ROWS;
terminal.cols = COMBINED_TERMINAL_COLS;
terminal.join(socket);
terminal.start();
}

async leaveCombinedTerminal(socket: DockgeSocket) {
const terminalName = getCombinedTerminalName(this.name);
const terminal = Terminal.getTerminal(terminalName);
if (terminal) {
terminal.leave(socket);
}
}

async joinContainerTerminal(socket: DockgeSocket, serviceName: string, shell : string = "sh", index: number = 0) {
const terminalName = getContainerExecTerminalName(this.name, serviceName, index);
let terminal = Terminal.getTerminal(terminalName);
Expand Down
34 changes: 33 additions & 1 deletion backend/terminal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ export class Terminal {
protected _rows : number = TERMINAL_ROWS;
protected _cols : number = TERMINAL_COLS;

public enableKeepAlive : boolean = false;
protected keepAliveInterval? : NodeJS.Timeout;

constructor(server : DockgeServer, name : string, file : string, args : string | string[], cwd : string) {
this.server = server;
this._name = name;
Expand Down Expand Up @@ -80,6 +83,25 @@ export class Terminal {
return;
}

if (this.enableKeepAlive) {
log.debug("Terminal", "Keep alive enabled for terminal " + this.name);

// Close if there is no clients
this.keepAliveInterval = setInterval(() => {
const clients = this.server.io.sockets.adapter.rooms.get(this.name);
const numClients = clients ? clients.size : 0;

if (numClients === 0) {
log.debug("Terminal", "Terminal " + this.name + " has no client, closing...");
this.close();
} else {
log.debug("Terminal", "Terminal " + this.name + " has " + numClients + " client(s)");
}
}, 60 * 1000);
} else {
log.debug("Terminal", "Keep alive disabled for terminal " + this.name);
}

try {
this._ptyProcess = pty.spawn(this.file, this.args, {
name: this.name,
Expand All @@ -100,6 +122,8 @@ export class Terminal {
this._ptyProcess.onExit(this.exit);
} catch (error) {
if (error instanceof Error) {
clearInterval(this.keepAliveInterval);

log.error("Terminal", "Failed to start terminal: " + error.message);
const exitCode = Number(error.message.split(" ").pop());
this.exit({
Expand All @@ -122,6 +146,8 @@ export class Terminal {
Terminal.terminalMap.delete(this.name);
log.debug("Terminal", "Terminal " + this.name + " exited with code " + res.exitCode);

clearInterval(this.keepAliveInterval);

if (this.callback) {
this.callback(res.exitCode);
}
Expand Down Expand Up @@ -158,7 +184,9 @@ export class Terminal {
}

close() {
this._ptyProcess?.kill();
clearInterval(this.keepAliveInterval);
// Send Ctrl+C to the terminal
this.ptyProcess?.write("\x03");
}

/**
Expand Down Expand Up @@ -193,6 +221,10 @@ export class Terminal {
terminal.start();
});
}

public static getTerminalCount() {
return Terminal.terminalMap.size;
}
}

/**
Expand Down
8 changes: 7 additions & 1 deletion frontend/src/pages/Compose.vue
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,12 @@ export default {
},
deep: true,
},

$route(to, from) {
// Leave Combined Terminal
console.debug("leaveCombinedTerminal", from.params.stackName);
this.$root.getSocket().emit("leaveCombinedTerminal", this.stack.name, () => {});
}
},
mounted() {
if (this.isAdd) {
Expand Down Expand Up @@ -361,7 +367,7 @@ export default {
clearTimeout(serviceStatusTimeout);
serviceStatusTimeout = setTimeout(async () => {
this.requestServiceStatus();
}, 2000);
}, 5000);
},

requestServiceStatus() {
Expand Down