WIP
This commit is contained in:
parent
08813cbc29
commit
43a0c22e41
13 changed files with 204 additions and 109 deletions
|
@ -1,25 +1,89 @@
|
|||
import { DockgeSocket } from "./util-server";
|
||||
import { io } from "socket.io-client";
|
||||
import { io, Socket as SocketClient } from "socket.io-client";
|
||||
import { log } from "./log";
|
||||
import { addEndpointToTerminalName, convertToRemoteStackID } from "./util-common";
|
||||
|
||||
/**
|
||||
* Dockge Instance Manager
|
||||
*/
|
||||
export class DockgeInstanceManager {
|
||||
protected static instance: DockgeInstanceManager;
|
||||
|
||||
protected constructor() {
|
||||
protected socket : DockgeSocket;
|
||||
protected instanceSocketList : Record<string, SocketClient> = {};
|
||||
|
||||
constructor(socket: DockgeSocket) {
|
||||
this.socket = socket;
|
||||
}
|
||||
|
||||
public static getInstance(): DockgeInstanceManager {
|
||||
if (!DockgeInstanceManager.instance) {
|
||||
DockgeInstanceManager.instance = new DockgeInstanceManager();
|
||||
connect(endpoint : string, tls : boolean, username : string, password : string) {
|
||||
if (this.instanceSocketList[endpoint]) {
|
||||
log.debug("INSTANCEMANAGER", "Already connected to the socket server: " + endpoint);
|
||||
return;
|
||||
}
|
||||
return DockgeInstanceManager.instance;
|
||||
|
||||
let url = ((tls) ? "wss://" : "ws://") + endpoint;
|
||||
|
||||
log.info("INSTANCEMANAGER", "Connecting to the socket server: " + endpoint);
|
||||
let client = io(url, {
|
||||
transports: [ "websocket", "polling" ],
|
||||
});
|
||||
|
||||
client.on("connect", () => {
|
||||
log.info("INSTANCEMANAGER", "Connected to the socket server: " + endpoint);
|
||||
|
||||
client.emit("login", {
|
||||
username: username,
|
||||
password: password,
|
||||
}, (res) => {
|
||||
if (res.ok) {
|
||||
log.info("INSTANCEMANAGER", "Logged in to the socket server: " + endpoint);
|
||||
} else {
|
||||
log.error("INSTANCEMANAGER", "Failed to login to the socket server: " + endpoint);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
client.on("error", (err) => {
|
||||
log.error("INSTANCEMANAGER", "Error from the socket server: " + endpoint);
|
||||
log.error("INSTANCEMANAGER", err);
|
||||
});
|
||||
|
||||
client.on("disconnect", () => {
|
||||
log.info("INSTANCEMANAGER", "Disconnected from the socket server: " + endpoint);
|
||||
});
|
||||
|
||||
client.on("stackList", (res) => {
|
||||
if (res.endpoint) {
|
||||
log.debug("INSTANCEMANAGER", "Received stackList from endpoint, ignore: " + res.endpoint);
|
||||
return;
|
||||
}
|
||||
|
||||
res.endpoint = endpoint;
|
||||
|
||||
let newStackList : Record<string, any> = {};
|
||||
|
||||
for (let stackName in res.stackList) {
|
||||
let stack = res.stackList[stackName];
|
||||
stack.endpoint = endpoint;
|
||||
stack.id = convertToRemoteStackID(stack.name, endpoint);
|
||||
newStackList[stack.name] = stack;
|
||||
}
|
||||
this.socket.emit("stackList", res);
|
||||
});
|
||||
|
||||
client.on("terminalWrite", (terminalName, data) => {
|
||||
this.socket.emit("terminalWrite", addEndpointToTerminalName(terminalName, endpoint), data);
|
||||
});
|
||||
|
||||
this.instanceSocketList[endpoint] = client;
|
||||
}
|
||||
|
||||
connect(socket: DockgeSocket) {
|
||||
disconnect(endpoint : string) {
|
||||
let client = this.instanceSocketList[endpoint];
|
||||
client?.disconnect();
|
||||
}
|
||||
|
||||
connectAll() {
|
||||
let list : Record<string, {tls : boolean, username : string, password : string}> = {
|
||||
"louis-twister-pi:5001": {
|
||||
tls: false,
|
||||
|
@ -34,67 +98,20 @@ export class DockgeInstanceManager {
|
|||
|
||||
for (let endpoint in list) {
|
||||
let item = list[endpoint];
|
||||
|
||||
let url = (item.tls) ? "wss://" : "ws://";
|
||||
url += endpoint;
|
||||
|
||||
log.info("INSTANCEMANAGER", "Connecting to the socket server: " + endpoint);
|
||||
let client = io(url, {
|
||||
transports: [ "websocket", "polling" ],
|
||||
});
|
||||
|
||||
client.on("connect", () => {
|
||||
log.info("INSTANCEMANAGER", "Connected to the socket server: " + endpoint);
|
||||
|
||||
client.emit("login", {
|
||||
username: item.username,
|
||||
password: item.password,
|
||||
}, (res) => {
|
||||
if (res.ok) {
|
||||
log.info("INSTANCEMANAGER", "Logged in to the socket server: " + endpoint);
|
||||
} else {
|
||||
log.error("INSTANCEMANAGER", "Failed to login to the socket server: " + endpoint);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
client.on("error", (err) => {
|
||||
log.error("INSTANCEMANAGER", "Error from the socket server: " + endpoint);
|
||||
log.error("INSTANCEMANAGER", err);
|
||||
});
|
||||
|
||||
client.on("disconnect", () => {
|
||||
log.info("INSTANCEMANAGER", "Disconnected from the socket server: " + endpoint);
|
||||
});
|
||||
|
||||
// Catch all events
|
||||
client.onAny((eventName, ...args) => {
|
||||
log.debug("INSTANCEMANAGER", "Received event: " + eventName);
|
||||
|
||||
let proxyEventList = [
|
||||
"stackList",
|
||||
];
|
||||
|
||||
if (proxyEventList.includes(eventName) &&
|
||||
args.length >= 1 &&
|
||||
typeof(args[0]) === "object" &&
|
||||
args[0].endpoint === undefined // Only proxy the event from the endpoint, any upstream event will be ignored
|
||||
) {
|
||||
args[0].endpoint = endpoint;
|
||||
socket.emit(eventName, ...args);
|
||||
} else {
|
||||
log.debug("INSTANCEMANAGER", "Event not in the proxy list or cannot set endpoint to the res: " + eventName);
|
||||
}
|
||||
});
|
||||
|
||||
socket.instanceSocketList[url] = client;
|
||||
this.connect(endpoint, item.tls, item.username, item.password);
|
||||
}
|
||||
}
|
||||
|
||||
disconnect(socket: DockgeSocket) {
|
||||
for (let url in socket.instanceSocketList) {
|
||||
let client = socket.instanceSocketList[url];
|
||||
client.disconnect();
|
||||
disconnectAll() {
|
||||
for (let endpoint in this.instanceSocketList) {
|
||||
this.disconnect(endpoint);
|
||||
}
|
||||
}
|
||||
|
||||
emitToEndpoint(endpoint: string, eventName: string, ...args : unknown[]) {
|
||||
log.debug("INSTANCEMANAGER", "Emitting event to endpoint: " + endpoint);
|
||||
let client = this.instanceSocketList[endpoint];
|
||||
client?.emit(eventName, ...args);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -68,8 +68,6 @@ export class DockgeServer {
|
|||
|
||||
stacksDir : string = "";
|
||||
|
||||
dockgeInstanceManager : DockgeInstanceManager;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
|
@ -187,8 +185,6 @@ export class DockgeServer {
|
|||
response.send(this.indexHTML);
|
||||
});
|
||||
|
||||
this.dockgeInstanceManager = DockgeInstanceManager.getInstance();
|
||||
|
||||
// Allow all CORS origins in development
|
||||
let cors = undefined;
|
||||
if (isDev) {
|
||||
|
@ -206,13 +202,14 @@ export class DockgeServer {
|
|||
log.info("server", "Socket connected!");
|
||||
|
||||
let dockgeSocket = socket as DockgeSocket;
|
||||
dockgeSocket.instanceSocketList = {};
|
||||
dockgeSocket.isAgentMode = false;
|
||||
dockgeSocket.instanceManager = new DockgeInstanceManager(dockgeSocket);
|
||||
|
||||
this.sendInfo(socket, true);
|
||||
this.sendInfo(dockgeSocket, true);
|
||||
|
||||
if (this.needSetup) {
|
||||
log.info("server", "Redirect to setup page");
|
||||
socket.emit("setup");
|
||||
dockgeSocket.emit("setup");
|
||||
}
|
||||
|
||||
// Create socket handlers
|
||||
|
@ -228,15 +225,15 @@ export class DockgeServer {
|
|||
if (await Settings.get("disableAuth")) {
|
||||
log.info("auth", "Disabled Auth: auto login to admin");
|
||||
this.afterLogin(dockgeSocket, await R.findOne("user") as User);
|
||||
socket.emit("autoLogin");
|
||||
dockgeSocket.emit("autoLogin");
|
||||
} else {
|
||||
log.debug("auth", "need auth");
|
||||
}
|
||||
|
||||
// Socket disconnect
|
||||
socket.on("disconnect", () => {
|
||||
dockgeSocket.on("disconnect", () => {
|
||||
log.info("server", "Socket disconnected!");
|
||||
this.dockgeInstanceManager.disconnect(dockgeSocket);
|
||||
dockgeSocket.instanceManager.disconnectAll();
|
||||
});
|
||||
|
||||
});
|
||||
|
@ -265,7 +262,7 @@ export class DockgeServer {
|
|||
}
|
||||
|
||||
// Also connect to other dockge instances
|
||||
this.dockgeInstanceManager.connect(socket);
|
||||
socket.instanceManager.connectAll();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -525,7 +522,6 @@ export class DockgeServer {
|
|||
this.io.to(room).emit("stackList", {
|
||||
ok: true,
|
||||
stackList: Object.fromEntries(map),
|
||||
endpoint: undefined,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,23 @@
|
|||
import { DockgeServer } from "./dockge-server";
|
||||
import { DockgeSocket } from "./util-server";
|
||||
import { log } from "./log";
|
||||
|
||||
export abstract class SocketHandler {
|
||||
abstract create(socket : DockgeSocket, server : DockgeServer): void;
|
||||
|
||||
event(eventName : string, socket : DockgeSocket, callback: (...args: any[]) => void) {
|
||||
|
||||
socket.on(eventName, (...args) => {
|
||||
log.debug("SOCKET", "Received event: " + eventName);
|
||||
|
||||
let req = args[0];
|
||||
let endpoint = req.endpoint;
|
||||
|
||||
if (endpoint) {
|
||||
socket.instanceManager.emitToEndpoint(endpoint, eventName, ...args);
|
||||
} else {
|
||||
callback(...args);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ import { Stack } from "../stack";
|
|||
|
||||
// @ts-ignore
|
||||
import composerize from "composerize";
|
||||
import { convertToLocalStackName, convertToRemoteStackID, isRemoteStackName, LooseObject } from "../util-common";
|
||||
|
||||
export class DockerSocketHandler extends SocketHandler {
|
||||
create(socket : DockgeSocket, server : DockgeServer) {
|
||||
|
@ -65,14 +66,15 @@ export class DockerSocketHandler extends SocketHandler {
|
|||
}
|
||||
});
|
||||
|
||||
socket.on("getStack", (stackName : unknown, callback) => {
|
||||
this.event("getStack", socket, (req : LooseObject, callback) => {
|
||||
try {
|
||||
checkLogin(socket);
|
||||
|
||||
if (typeof(stackName) !== "string") {
|
||||
throw new ValidationError("Stack name must be a string");
|
||||
if (typeof(req) !== "object") {
|
||||
throw new ValidationError("Request must be an object");
|
||||
}
|
||||
|
||||
let stackName = req.stackName;
|
||||
const stack = Stack.getStack(server, stackName);
|
||||
|
||||
if (stack.isManagedByDockge) {
|
||||
|
|
|
@ -260,8 +260,6 @@ export class MainSocketHandler extends SocketHandler {
|
|||
await doubleCheckPassword(socket, currentPassword);
|
||||
}
|
||||
|
||||
console.log(data);
|
||||
|
||||
await Settings.setSettings("general", data);
|
||||
|
||||
callback({
|
||||
|
|
|
@ -21,6 +21,7 @@ import childProcess from "child_process";
|
|||
export class Stack {
|
||||
|
||||
name: string;
|
||||
|
||||
protected _status: number = UNKNOWN;
|
||||
protected _composeYAML?: string;
|
||||
protected _configFilePath?: string;
|
||||
|
@ -59,6 +60,8 @@ export class Stack {
|
|||
toSimpleJSON() : object {
|
||||
return {
|
||||
name: this.name,
|
||||
id: this.name,
|
||||
endpoint: undefined,
|
||||
status: this._status,
|
||||
tags: [],
|
||||
isManagedByDockge: this.isManagedByDockge,
|
||||
|
|
|
@ -190,20 +190,34 @@ export function getCryptoRandomInt(min: number, max: number):number {
|
|||
}
|
||||
}
|
||||
|
||||
export function getComposeTerminalName(stack : string) {
|
||||
return "compose-" + stack;
|
||||
export function getComposeTerminalName(stackID : string) {
|
||||
return "compose-" + stackID;
|
||||
}
|
||||
|
||||
export function getCombinedTerminalName(stack : string) {
|
||||
return "combined-" + stack;
|
||||
export function getCombinedTerminalName(stackID : string) {
|
||||
return "combined-" + stackID;
|
||||
}
|
||||
|
||||
export function getContainerTerminalName(container : string) {
|
||||
return "container-" + container;
|
||||
}
|
||||
|
||||
export function getContainerExecTerminalName(stackName : string, container : string, index : number) {
|
||||
return "container-exec-" + stackName + "-" + container + "-" + index;
|
||||
export function getContainerExecTerminalName(stackID : string, container : string, index : number) {
|
||||
return "containerExec-" + stackID + "-" + container + "-" + index;
|
||||
}
|
||||
|
||||
export function addEndpointToTerminalName(terminalName : string, endpoint : string) {
|
||||
if (
|
||||
terminalName.startsWith("compose-") ||
|
||||
terminalName.startsWith("combined-") ||
|
||||
terminalName.startsWith("containerExec-")
|
||||
) {
|
||||
let arr = terminalName.split("-");
|
||||
arr[1] = convertToRemoteStackID(arr[1], endpoint);
|
||||
return arr.join("-");
|
||||
} else {
|
||||
return terminalName;
|
||||
}
|
||||
}
|
||||
|
||||
export function copyYAMLComments(doc : Document, src : Document) {
|
||||
|
@ -340,3 +354,40 @@ export function parseDockerPort(input : string, defaultHostname : string = "loca
|
|||
display: display,
|
||||
};
|
||||
}
|
||||
|
||||
const splitChar : string = "::";
|
||||
|
||||
export function convertToRemoteStackID(stackName? : string, endpoint? : string) {
|
||||
if (!stackName || !endpoint) {
|
||||
return stackName;
|
||||
}
|
||||
|
||||
if (stackName.startsWith("remote" + splitChar)) {
|
||||
return stackName;
|
||||
}
|
||||
return `remote${splitChar}${endpoint}${splitChar}${stackName}`;
|
||||
}
|
||||
|
||||
export function convertToLocalStackName(stackName? : string) {
|
||||
if (!stackName) {
|
||||
return {
|
||||
endpoint: undefined,
|
||||
stackName: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
if (!stackName.startsWith("remote" + splitChar)) {
|
||||
return {
|
||||
endpoint: undefined,
|
||||
stackName,
|
||||
};
|
||||
}
|
||||
return {
|
||||
endpoint: stackName.split(splitChar)[1],
|
||||
stackName: stackName.split(splitChar).splice(2).join(splitChar)
|
||||
};
|
||||
}
|
||||
|
||||
export function isRemoteStackName(stackName : string) {
|
||||
return stackName.startsWith("remote" + splitChar);
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ import { log } from "./log";
|
|||
import { ERROR_TYPE_VALIDATION } from "./util-common";
|
||||
import { R } from "redbean-node";
|
||||
import { verifyPassword } from "./password-hash";
|
||||
import { DockgeInstanceManager } from "./dockge-instance-manager";
|
||||
|
||||
export interface JWTDecoded {
|
||||
username : string;
|
||||
|
@ -14,7 +15,8 @@ export interface JWTDecoded {
|
|||
export interface DockgeSocket extends Socket {
|
||||
userID: number;
|
||||
consoleTerminal? : Terminal;
|
||||
instanceSocketList: Record<string, SocketClient>;
|
||||
instanceManager : DockgeInstanceManager;
|
||||
isAgentMode : boolean;
|
||||
}
|
||||
|
||||
// For command line arguments, so they are nullable
|
||||
|
|
|
@ -152,6 +152,15 @@ export default {
|
|||
});
|
||||
|
||||
result.sort((m1, m2) => {
|
||||
|
||||
// sort by managed by dockge
|
||||
if (m1.isManagedByDockge && !m2.isManagedByDockge) {
|
||||
return -1;
|
||||
} else if (!m1.isManagedByDockge && m2.isManagedByDockge) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// sort by status
|
||||
if (m1.status !== m2.status) {
|
||||
if (m2.status === RUNNING) {
|
||||
return 1;
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
<script>
|
||||
|
||||
import Uptime from "./Uptime.vue";
|
||||
import { convertToLocalStackName } from "../../../backend/util-common";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
|
@ -55,10 +56,7 @@ export default {
|
|||
},
|
||||
computed: {
|
||||
url() {
|
||||
if (!this.stack.endpoint) {
|
||||
return `/compose/${this.stack.name}`;
|
||||
}
|
||||
return `/compose/${this.stack.name}/${this.stack.endpoint}`;
|
||||
return `/compose/${this.stack.id}`;
|
||||
},
|
||||
depthMargin() {
|
||||
return {
|
||||
|
@ -66,7 +64,7 @@ export default {
|
|||
};
|
||||
},
|
||||
stackName() {
|
||||
return this.stack.name;
|
||||
return convertToLocalStackName(this.stack.name).stackName;
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
|
|
|
@ -199,7 +199,6 @@ export default defineComponent({
|
|||
});
|
||||
|
||||
socket.on("stackList", (res) => {
|
||||
console.log(res);
|
||||
if (res.ok) {
|
||||
if (!res.endpoint) {
|
||||
this.stackList = res.stackList;
|
||||
|
|
|
@ -214,7 +214,7 @@ import "vue-prism-editor/dist/prismeditor.min.css";
|
|||
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
|
||||
import {
|
||||
COMBINED_TERMINAL_COLS,
|
||||
COMBINED_TERMINAL_ROWS,
|
||||
COMBINED_TERMINAL_ROWS, convertToLocalStackName,
|
||||
copyYAMLComments,
|
||||
getCombinedTerminalName,
|
||||
getComposeTerminalName,
|
||||
|
@ -322,17 +322,17 @@ export default {
|
|||
},
|
||||
|
||||
terminalName() {
|
||||
if (!this.stack.name) {
|
||||
if (!this.stack.id) {
|
||||
return "";
|
||||
}
|
||||
return getComposeTerminalName(this.stack.name);
|
||||
return getComposeTerminalName(this.stack.id);
|
||||
},
|
||||
|
||||
combinedTerminalName() {
|
||||
if (!this.stack.name) {
|
||||
if (!this.stack.id) {
|
||||
return "";
|
||||
}
|
||||
return getCombinedTerminalName(this.stack.name);
|
||||
return getCombinedTerminalName(this.stack.id);
|
||||
},
|
||||
|
||||
networks() {
|
||||
|
@ -371,7 +371,7 @@ export default {
|
|||
|
||||
$route(to, from) {
|
||||
// Leave Combined Terminal
|
||||
console.debug("leaveCombinedTerminal", from.params.stackName);
|
||||
console.debug("leaveCombinedTerminal", from.params.stackID);
|
||||
this.$root.getSocket().emit("leaveCombinedTerminal", this.stack.name, () => {});
|
||||
}
|
||||
},
|
||||
|
@ -400,7 +400,10 @@ export default {
|
|||
this.yamlCodeChange();
|
||||
|
||||
} else {
|
||||
this.stack.name = this.$route.params.stackName;
|
||||
this.stack.id = this.$route.params.stackID;
|
||||
let { endpoint, stackName } = convertToLocalStackName(this.stack.id);
|
||||
this.stack.name = stackName;
|
||||
this.stack.endpoint = endpoint;
|
||||
this.loadStack();
|
||||
}
|
||||
|
||||
|
@ -448,7 +451,11 @@ export default {
|
|||
|
||||
loadStack() {
|
||||
this.processing = true;
|
||||
this.$root.getSocket().emit("getStack", this.stack.name, (res) => {
|
||||
|
||||
this.$root.getSocket().emit("getStack", {
|
||||
stackName: this.stack.name,
|
||||
endpoint: this.stack.endpoint,
|
||||
}, (res) => {
|
||||
if (res.ok) {
|
||||
this.stack = res.stack;
|
||||
this.yamlCodeChange();
|
||||
|
|
|
@ -35,14 +35,10 @@ const routes = [
|
|||
component: Compose,
|
||||
},
|
||||
{
|
||||
path: "/compose/:stackName",
|
||||
path: "/compose/:stackID",
|
||||
name: "compose",
|
||||
component: Compose,
|
||||
},
|
||||
{
|
||||
path: "/compose/:stackName/:endpoint",
|
||||
component: Compose,
|
||||
},
|
||||
{
|
||||
path: "/terminal/:stackName/:serviceName/:type",
|
||||
component: ContainerTerminal,
|
||||
|
|
Loading…
Add table
Reference in a new issue