Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
louislam committed Dec 25, 2023
1 parent ca3bb30 commit fa0a4f8
Show file tree
Hide file tree
Showing 9 changed files with 99 additions and 24 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ View Video: https://youtu.be/AWAlOQeNpgU?t=48
- Update Docker Images
- ⌨️ Interactive Editor for `compose.yaml`
- 🦦 Interactive Web Terminal
- 🕷️ (1.4.0 NEW!) Multiple agents support - You can manage multiple stacks from different Docker hosts in one single interface
- 🕷️ (1.4.0 🆕) Multiple agents support - You can manage multiple stacks from different Docker hosts in one single interface
- 🏪 Convert `docker run ...` commands into `compose.yaml`
- 📙 File based structure - Dockge won't kidnap your compose files, they are stored on your drive as usual. You can interact with them using normal `docker compose` commands
<img src="https://github.com/louislam/dockge/assets/1336778/cc071864-592e-4909-b73a-343a57494002" width=300 />
Expand Down
44 changes: 38 additions & 6 deletions backend/agent-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,30 @@ import { DockgeSocket } from "./util-server";
import { io, Socket as SocketClient } from "socket.io-client";
import { log } from "./log";
import { Agent } from "./models/agent";
import { isDev, LooseObject } from "../common/util-common";
import { isDev, LooseObject, sleep } from "../common/util-common";
import semver from "semver";
import { R } from "redbean-node";
import dayjs, { Dayjs } from "dayjs";

/**
* Dockge Instance Manager
* One AgentManager per Socket connection
*/
export class AgentManager {

protected socket : DockgeSocket;
protected agentSocketList : Record<string, SocketClient> = {};
protected agentLoggedInList : Record<string, boolean> = {};
protected _firstConnectTime : Dayjs = dayjs();

constructor(socket: DockgeSocket) {
this.socket = socket;
}

get firstConnectTime() : Dayjs {
return this._firstConnectTime;
}

test(url : string, username : string, password : string) : Promise<void> {
return new Promise((resolve, reject) => {
let obj = new URL(url);
Expand Down Expand Up @@ -131,12 +139,14 @@ export class AgentManager {
}, (res : LooseObject) => {
if (res.ok) {
log.info("agent-manager", "Logged in to the socket server: " + endpoint);
this.agentLoggedInList[endpoint] = true;
this.socket.emit("agentStatus", {
endpoint: endpoint,
status: "online",
});
} else {
log.error("agent-manager", "Failed to login to the socket server: " + endpoint);
this.agentLoggedInList[endpoint] = false;
this.socket.emit("agentStatus", {
endpoint: endpoint,
status: "offline",
Expand Down Expand Up @@ -188,6 +198,8 @@ export class AgentManager {
}

async connectAll() {
this._firstConnectTime = dayjs();

if (this.socket.endpoint) {
log.info("agent-manager", "This connection is connected as an agent, skip connectAll()");
return;
Expand All @@ -211,7 +223,7 @@ export class AgentManager {
}
}

emitToEndpoint(endpoint: string, eventName: string, ...args : unknown[]) {
async emitToEndpoint(endpoint: string, eventName: string, ...args : unknown[]) {
log.debug("agent-manager", "Emitting event to endpoint: " + endpoint);
let client = this.agentSocketList[endpoint];

Expand All @@ -220,9 +232,27 @@ export class AgentManager {
throw new Error("Socket client not found for endpoint: " + endpoint);
}

if (!client.connected) {
log.error("agent-manager", "Socket client not connected for endpoint: " + endpoint);
throw new Error("Socket client not connected for endpoint: " + endpoint);
if (!client.connected || !this.agentLoggedInList[endpoint]) {
// Maybe the request is too quick, the socket is not connected yet, check firstConnectTime
// If it is within 10 seconds, we should apply retry logic here
let diff = dayjs().diff(this.firstConnectTime, "second");
log.debug("agent-manager", endpoint + ": diff: " + diff);
let ok = false;
while (diff < 10) {
if (client.connected && this.agentLoggedInList[endpoint]) {
log.debug("agent-manager", `${endpoint}: Connected & Logged in`);
ok = true;
break;
}
log.debug("agent-manager", endpoint + ": not ready yet, retrying in 1 second...");
await sleep(1000);
diff = dayjs().diff(this.firstConnectTime, "second");
}

if (!ok) {
log.error("agent-manager", `${endpoint}: Socket client not connected`);
throw new Error("Socket client not connected for endpoint: " + endpoint);
}
}

client.emit("agent", endpoint, eventName, ...args);
Expand All @@ -231,7 +261,9 @@ export class AgentManager {
emitToAllEndpoints(eventName: string, ...args : unknown[]) {
log.debug("agent-manager", "Emitting event to all endpoints");
for (let endpoint in this.agentSocketList) {
this.emitToEndpoint(endpoint, eventName, ...args);
this.emitToEndpoint(endpoint, eventName, ...args).catch((e) => {
log.warn("agent-manager", e.message);
});
}
}

Expand Down
2 changes: 1 addition & 1 deletion backend/socket-handlers/agent-proxy-socket-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export class AgentProxySocketHandler extends SocketHandler {

} else {
log.debug("agent", "Proxying request to " + endpoint + " for " + eventName);
socket.instanceManager.emitToEndpoint(endpoint, eventName, ...args);
await socket.instanceManager.emitToEndpoint(endpoint, eventName, ...args);
}
} catch (e) {
if (e instanceof Error) {
Expand Down
2 changes: 0 additions & 2 deletions backend/socket-handlers/main-socket-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -271,8 +271,6 @@ export class MainSocketHandler extends SocketHandler {
await doubleCheckPassword(socket, currentPassword);
}

console.log(data);

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

callback({
Expand Down
12 changes: 12 additions & 0 deletions backend/terminal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export class Terminal {

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

protected socketList : Record<string, DockgeSocket> = {};

Expand Down Expand Up @@ -84,6 +85,16 @@ export class Terminal {
return;
}

this.kickDisconnectedClientsInterval = setInterval(() => {
for (const socketID in this.socketList) {
const socket = this.socketList[socketID];
if (!socket.connected) {
log.debug("Terminal", "Kicking disconnected client " + socket.id + " from terminal " + this.name);
this.leave(socket);
}
}
}, 60 * 1000);

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

Expand Down Expand Up @@ -152,6 +163,7 @@ export class Terminal {
log.debug("Terminal", "Terminal " + this.name + " exited with code " + res.exitCode);

clearInterval(this.keepAliveInterval);
clearInterval(this.kickDisconnectedClientsInterval);

if (this.callback) {
this.callback(res.exitCode);
Expand Down
2 changes: 1 addition & 1 deletion common/util-common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,7 @@ function copyYAMLCommentsItems(items : any, srcItems : any) {
* @param input
* @param hostname
*/
export function parseDockerPort(input : string, hostname) {
export function parseDockerPort(input : string, hostname : string) {
let port;
let display;

Expand Down
8 changes: 2 additions & 6 deletions frontend/src/components/StackListItem.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<Uptime :stack="stack" :fixed-width="true" class="me-2" />
<div class="title">
<span>{{ stackName }}</span>
<div v-if="Object.keys($root.agentList).length > 1" class="endpoint">{{ endpointDisplay }}</div>
<div v-if="$root.agentCount > 1" class="endpoint">{{ endpointDisplay }}</div>
</div>
</router-link>
</template>
Expand Down Expand Up @@ -54,11 +54,7 @@ export default {
},
computed: {
endpointDisplay() {
if (this.stack.endpoint) {
return this.stack.endpoint;
} else {
return this.$t("currentEndpoint");
}
return this.$root.endpointDisplayFunction(this.stack.endpoint);
},
url() {
if (this.stack.endpoint) {
Expand Down
13 changes: 13 additions & 0 deletions frontend/src/mixins/socket.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ export default defineComponent({
},
computed: {

agentCount() {
return Object.keys(this.agentList).length;
},

completeStackList() {
let list : Record<string, any> = {};

Check failure on line 57 in frontend/src/mixins/socket.ts

View workflow job for this annotation

GitHub Actions / ci (ubuntu-latest, 18.17.1)

Unexpected any. Specify a different type

Expand Down Expand Up @@ -125,6 +129,15 @@ export default defineComponent({

},
methods: {

endpointDisplayFunction(endpoint : string) {
if (endpoint) {
return endpoint;
} else {
return this.$t("currentEndpoint");
}
},

/**
* Initialize connection to socket server
* @param bypass Should the check for if we
Expand Down
38 changes: 31 additions & 7 deletions frontend/src/pages/Compose.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@
<transition name="slide-fade" appear>
<div>
<h1 v-if="isAdd" class="mb-3">Compose</h1>
<h1 v-else class="mb-3"><Uptime :stack="globalStack" :pill="true" /> {{ stack.name }}</h1>
<h1 v-else class="mb-3">
<Uptime :stack="globalStack" :pill="true" /> {{ stack.name }}
<span v-if="$root.agentCount > 1" class="agent-name">
({{ endpointDisplay }})
</span>
</h1>

<div v-if="stack.isManagedByDockge" class="mb-3">
<div class="btn-group me-2" role="group">
Expand Down Expand Up @@ -310,6 +315,10 @@ export default {
},
computed: {
endpointDisplay() {
return this.$root.endpointDisplayFunction(this.endpoint);
},
urls() {
if (!this.envsubstJSONConfig["x-dockge"] || !this.envsubstJSONConfig["x-dockge"].urls || !Array.isArray(this.envsubstJSONConfig["x-dockge"].urls)) {
return [];
Expand Down Expand Up @@ -428,9 +437,7 @@ export default {
},
$route(to, from) {
// Leave Combined Terminal
console.debug("leaveCombinedTerminal", from.params.stackName);
this.$root.emitAgent(this.endpoint, "leaveCombinedTerminal", this.stack.name, () => {});
}
},
mounted() {
Expand Down Expand Up @@ -473,11 +480,9 @@ export default {
this.requestServiceStatus();
},
unmounted() {
this.stopServiceStatusTimeout = true;
clearTimeout(serviceStatusTimeout);
},
methods: {
startServiceStatusTimeout() {
clearTimeout(serviceStatusTimeout);
serviceStatusTimeout = setTimeout(async () => {
Expand All @@ -499,15 +504,27 @@ export default {
exitConfirm(next) {
if (this.isEditMode) {
if (confirm("You are currently editing a stack. Are you sure you want to leave?")) {
this.exitAction();
next();
} else {
next(false);
}
} else {
this.exitAction();
next();
}
},
exitAction() {
console.log("exitAction");
this.stopServiceStatusTimeout = true;
clearTimeout(serviceStatusTimeout);
// Leave Combined Terminal
console.debug("leaveCombinedTerminal", this.endpoint, this.stack.name);
this.$root.emitAgent(this.endpoint, "leaveCombinedTerminal", this.stack.name, () => {});
},
bindTerminal() {
this.$refs.progressTerminal?.bind(this.endpoint, this.terminalName);
},
Expand Down Expand Up @@ -774,6 +791,8 @@ export default {
</script>
<style scoped lang="scss">
@import "../styles/vars.scss";
.terminal {
height: 200px;
}
Expand All @@ -785,4 +804,9 @@ export default {
background-color: #2c2f38 !important;
}
}
.agent-name {
font-size: 13px;
color: $dark-font-color3;
}
</style>

0 comments on commit fa0a4f8

Please sign in to comment.