Compare commits

...

7 Commits

Author SHA1 Message Date
Louis Lam 4d32398d6d WIP 2023-12-06 01:37:56 +08:00
Louis Lam cc3fe01c1b Merge branch 'master' into multiple
# Conflicts:
#	backend/socket-handlers/docker-socket-handler.ts
#	backend/util-server.ts
#	frontend/src/components/StackList.vue
#	package.json
#	pnpm-lock.yaml
2023-12-05 19:32:17 +08:00
Louis Lam 43a0c22e41 WIP 2023-12-03 15:46:02 +08:00
Louis Lam 08813cbc29 WIP 2023-12-01 21:22:21 +08:00
Louis Lam f50c7cead8 Merge branch 'master' into multiple 2023-12-01 20:25:30 +08:00
Louis Lam a8d5784725 WIP 2023-11-29 14:26:13 +08:00
Louis Lam 49fb055da4 WIP 2023-11-28 17:37:59 +08:00
17 changed files with 555 additions and 96 deletions

View File

@ -0,0 +1,125 @@
import { DockgeSocket } from "./util-server";
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 socket : DockgeSocket;
protected instanceSocketList : Record<string, SocketClient> = {};
constructor(socket: DockgeSocket) {
this.socket = socket;
}
connect(endpoint : string, tls : boolean, username : string, password : string) {
if (this.instanceSocketList[endpoint]) {
log.debug("INSTANCEMANAGER", "Already connected to the socket server: " + endpoint);
return;
}
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;
}
disconnect(endpoint : string) {
let client = this.instanceSocketList[endpoint];
client?.disconnect();
}
connectAll() {
let list : Record<string, {tls : boolean, username : string, password : string}> = {
};
if (process.env.DOCKGE_TEST_REMOTE_HOST) {
list[process.env.DOCKGE_TEST_REMOTE_HOST] = {
tls: false,
username: "admin",
password: process.env.DOCKGE_TEST_REMOTE_PW || "",
};
}
if (Object.keys(list).length !== 0) {
log.info("INSTANCEMANAGER", "Connecting to all instance socket server(s)...");
}
for (let endpoint in list) {
let item = list[endpoint];
this.connect(endpoint, item.tls, item.username, item.password);
}
}
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];
if (!client) {
log.error("INSTANCEMANAGER", "Socket client not found for endpoint: " + endpoint);
return;
}
client?.emit(eventName, ...args);
}
}

View File

@ -31,6 +31,9 @@ import gracefulShutdown from "http-graceful-shutdown";
import User from "./models/user";
import childProcessAsync from "promisify-child-process";
import { Terminal } from "./terminal";
import { DockgeInstanceManager } from "./dockge-instance-manager";
import "dotenv/config";
import "dotenv/config";
@ -71,6 +74,7 @@ export class DockgeServer {
*
*/
constructor() {
// Catch unexpected errors here
let unexpectedErrorHandler = (error : unknown) => {
console.trace(error);
@ -199,16 +203,20 @@ export class DockgeServer {
this.io.on("connection", async (socket: Socket) => {
log.info("server", "Socket connected!");
this.sendInfo(socket, true);
let dockgeSocket = socket as DockgeSocket;
dockgeSocket.isAgentMode = false;
dockgeSocket.instanceManager = new DockgeInstanceManager(dockgeSocket);
this.sendInfo(dockgeSocket, true);
if (this.needSetup) {
log.info("server", "Redirect to setup page");
socket.emit("setup");
dockgeSocket.emit("setup");
}
// Create socket handlers
for (const socketHandler of this.socketHandlerList) {
socketHandler.create(socket as DockgeSocket, this);
socketHandler.create(dockgeSocket, this);
}
// ***************************
@ -218,12 +226,18 @@ export class DockgeServer {
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") as User);
socket.emit("autoLogin");
this.afterLogin(dockgeSocket, await R.findOne("user") as User);
dockgeSocket.emit("autoLogin");
} else {
log.debug("auth", "need auth");
}
// Socket disconnect
dockgeSocket.on("disconnect", () => {
log.info("server", "Socket disconnected!");
dockgeSocket.instanceManager.disconnectAll();
});
});
this.io.on("disconnect", () => {
@ -248,6 +262,9 @@ export class DockgeServer {
} catch (e) {
log.error("server", e);
}
// Also connect to other dockge instances
socket.instanceManager.connectAll();
}
/**

View File

@ -1,6 +1,22 @@
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];
if (req.endpoint) {
let endpoint = req.endpoint;
req.responseAsEndpoint = endpoint;
req.endpoint = undefined;
socket.instanceManager.emitToEndpoint(endpoint, eventName, ...args);
} else {
callback(...args);
}
});
}
}

View File

@ -5,6 +5,13 @@ import { Stack } from "../stack";
// @ts-ignore
import composerize from "composerize";
import {
convertToLocalStackName,
convertToRemoteStackID,
isRemoteStackName, isStackRequest,
LooseObject,
StackRequest
} from "../util-common";
export class DockerSocketHandler extends SocketHandler {
create(socket : DockgeSocket, server : DockgeServer) {
@ -65,15 +72,20 @@ export class DockerSocketHandler extends SocketHandler {
}
});
socket.on("getStack", async (stackName : unknown, callback) => {
this.event("getStack", socket, async (req : unknown, callback) => {
console.log("here", req);
try {
checkLogin(socket);
if (typeof(stackName) !== "string") {
throw new ValidationError("Stack name must be a string");
if (!isStackRequest(req)) {
throw new ValidationError("Invalid request");
}
const stack = await Stack.getStack(server, stackName);
let stackName = req.stackName;
let endpoint = req.responseAsEndpoint;
const stack = await Stack.getStack(server, stackName, false, endpoint);
console.log(stack.toJSON());
if (stack.isManagedByDockge) {
stack.joinCombinedTerminal(socket);

View File

@ -260,8 +260,6 @@ export class MainSocketHandler extends SocketHandler {
await doubleCheckPassword(socket, currentPassword);
}
console.log(data);
await Settings.setSettings("general", data);
callback({

View File

@ -2,16 +2,6 @@ import { SocketHandler } from "../socket-handler.js";
import { DockgeServer } from "../dockge-server";
import { callbackError, checkLogin, DockgeSocket, ValidationError } from "../util-server";
import { log } from "../log";
import yaml from "yaml";
import path from "path";
import fs from "fs";
import {
allowedCommandList,
allowedRawKeys,
getComposeTerminalName, getContainerExecTerminalName,
isDev,
PROGRESS_TERMINAL_ROWS
} from "../util-common";
import { InteractiveTerminal, MainTerminal, Terminal } from "../terminal";
import { Stack } from "../stack";

View File

@ -6,7 +6,7 @@ import { DockgeSocket, fileExists, ValidationError } from "./util-server";
import path from "path";
import {
COMBINED_TERMINAL_COLS,
COMBINED_TERMINAL_ROWS,
COMBINED_TERMINAL_ROWS, convertToRemoteStackID,
CREATED_FILE,
CREATED_STACK,
EXITED, getCombinedTerminalName,
@ -21,22 +21,24 @@ import childProcessAsync from "promisify-child-process";
export class Stack {
name: string;
protected _status: number = UNKNOWN;
protected _composeYAML?: string;
protected _composeENV?: string;
protected _configFilePath?: string;
protected _composeFileName: string = "compose.yaml";
protected server: DockgeServer;
protected endpoint: string | undefined;
protected combinedTerminal? : Terminal;
protected static managedStackList: Map<string, Stack> = new Map();
constructor(server : DockgeServer, name : string, composeYAML? : string, composeENV? : string, skipFSOperations = false) {
constructor(server : DockgeServer, name : string, composeYAML? : string, composeENV? : string, skipFSOperations = false, endpoint : string | undefined = undefined) {
this.name = name;
this.server = server;
this._composeYAML = composeYAML;
this._composeENV = composeENV;
this.endpoint = endpoint;
if (!skipFSOperations) {
// Check if compose file name is different from compose.yaml
@ -50,6 +52,14 @@ export class Stack {
}
}
/**
* Get the remote stack ID
* If endpoint is undefined, it will return the local stack ID
*/
getRemoteStackID() : string {
return convertToRemoteStackID(this.name, this.endpoint);
}
toJSON() : object {
let obj = this.toSimpleJSON();
return {
@ -62,6 +72,8 @@ export class Stack {
toSimpleJSON() : object {
return {
name: this.name,
id: this.getRemoteStackID(),
endpoint: undefined,
status: this._status,
tags: [],
isManagedByDockge: this.isManagedByDockge,
@ -330,12 +342,12 @@ export class Stack {
}
}
static async getStack(server: DockgeServer, stackName: string, skipFSOperations = false) : Promise<Stack> {
static async getStack(server: DockgeServer, stackName: string, skipFSOperations = false, endpoint : string | undefined = undefined) : Promise<Stack> {
let dir = path.join(server.stacksDir, stackName);
if (!skipFSOperations) {
// Maybe it is a stack managed out of Dockge
if (!await fileExists(dir) || !(await fsAsync.stat(dir)).isDirectory()) {
// Maybe it is a stack managed by docker compose directly
let stackList = await this.getStackList(server, true);
let stack = stackList.get(stackName);
@ -346,16 +358,14 @@ export class Stack {
throw new ValidationError("Stack not found");
}
}
} else {
//log.debug("getStack", "Skip FS operations");
}
let stack : Stack;
if (!skipFSOperations) {
stack = new Stack(server, stackName);
stack = new Stack(server, stackName, undefined, undefined, false, endpoint);
} else {
stack = new Stack(server, stackName, undefined, undefined, true);
stack = new Stack(server, stackName, undefined, undefined, true, endpoint);
}
stack._status = UNKNOWN;

View File

@ -12,9 +12,14 @@ dayjs.extend(utc);
dayjs.extend(timezone);
dayjs.extend(relativeTime);
export interface LooseObject {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
[key: string]: any
export interface StackRequest {
stackName : string;
endpoint? : string;
responseAsEndpoint? : string;
}
export function isStackRequest(obj : unknown) : obj is StackRequest {
return (obj as StackRequest).stackName !== undefined;
}
let randomBytes : (numBytes: number) => Uint8Array;
@ -190,20 +195,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 +359,43 @@ export function parseDockerPort(input : string, defaultHostname : string = "loca
display: display,
};
}
const remote = "remote";
const splitChar : string = "::";
export function convertToRemoteStackID(stackName? : string, endpoint? : string) : string {
if (!stackName) {
return "";
}
if (!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);
}

View File

@ -1,11 +1,12 @@
import { Socket } from "socket.io";
import { Socket as SocketClient } from "socket.io-client";
import { Terminal } from "./terminal";
import { randomBytes } from "crypto";
import { log } from "./log";
import { ERROR_TYPE_VALIDATION } from "./util-common";
import { R } from "redbean-node";
import { verifyPassword } from "./password-hash";
import fs from "fs";
import { DockgeInstanceManager } from "./dockge-instance-manager";
export interface JWTDecoded {
username : string;
@ -15,6 +16,8 @@ export interface JWTDecoded {
export interface DockgeSocket extends Socket {
userID: number;
consoleTerminal? : Terminal;
instanceManager : DockgeInstanceManager;
isAgentMode : boolean;
}
// For command line arguments, so they are nullable

View File

@ -120,7 +120,7 @@ export default {
* @returns {Array} The sorted list of stacks.
*/
sortedStackList() {
let result = Object.values(this.$root.stackList);
let result = Object.values(this.$root.completeStackList);
result = result.filter(stack => {
// filter by search text
@ -160,6 +160,7 @@ export default {
return 1;
}
// sort by status
if (m1.status !== m2.status) {
if (m2.status === RUNNING) {
return 1;

View File

@ -1,13 +1,17 @@
<template>
<router-link :to="`/compose/${stack.name}`" :class="{ 'dim' : !stack.isManagedByDockge }" class="item">
<router-link :to="url" :class="{ 'dim' : !stack.isManagedByDockge }" class="item">
<Uptime :stack="stack" :fixed-width="true" class="me-2" />
<span class="title">{{ stackName }}</span>
<div class="title">
<span>{{ stackName }}</span>
<div v-if="stack.endpoint" class="endpoint">{{ stack.endpoint }}</div>
</div>
</router-link>
</template>
<script>
import Uptime from "./Uptime.vue";
import { convertToLocalStackName } from "../../../backend/util-common";
export default {
components: {
@ -51,13 +55,16 @@ export default {
};
},
computed: {
url() {
return `/compose/${this.stack.id}`;
},
depthMargin() {
return {
marginLeft: `${31 * this.depth}px`,
};
},
stackName() {
return this.stack.name;
return convertToLocalStackName(this.stack.name).stackName;
}
},
watch: {
@ -117,16 +124,36 @@ export default {
padding-right: 2px !important;
}
// .stack-item {
// width: 100%;
// }
.tags {
margin-top: 4px;
padding-left: 67px;
.item {
text-decoration: none;
display: flex;
flex-wrap: wrap;
gap: 0;
align-items: center;
min-height: 52px;
border-radius: 10px;
transition: all ease-in-out 0.15s;
width: 100%;
padding: 5px 8px;
&.disabled {
opacity: 0.3;
}
&:hover {
background-color: $highlight-white;
}
&.active {
background-color: #cdf8f4;
}
.title {
margin-top: -4px;
}
.endpoint {
font-size: 12px;
color: $dark-font-color3;
}
}
.collapsed {

View File

@ -28,11 +28,24 @@ export default defineComponent({
loggedIn: false,
allowLoginDialog: false,
username: null,
instanceList: {} as Record<string, any>,
stackList: {},
composeTemplate: "",
};
},
computed: {
completeStackList() {
let list : Record<string, any> = this.stackList;
for (let endpoint in this.instanceList) {
let instance = this.instanceList[endpoint];
for (let stackName in instance.stackList) {
list[stackName + "_" + endpoint] = instance.stackList[stackName];
}
}
return list;
},
usernameFirstChar() {
if (typeof this.username == "string" && this.username.length >= 1) {
return this.username.charAt(0).toUpperCase();
@ -81,7 +94,6 @@ export default defineComponent({
},
mounted() {
return;
},
methods: {
/**
@ -188,7 +200,23 @@ export default defineComponent({
socket.on("stackList", (res) => {
if (res.ok) {
this.stackList = res.stackList;
if (!res.endpoint) {
this.stackList = res.stackList;
} else {
if (!this.instanceList[res.endpoint]) {
this.instanceList[res.endpoint] = {
stackList: {},
};
}
// Set endpoint for each stack
for (let stackName in res.stackList) {
const stackObj = res.stackList[stackName];
stackObj.endpoint = res.endpoint;
}
this.instanceList[res.endpoint].stackList = res.stackList;
}
}
});

View File

@ -230,7 +230,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,
@ -344,17 +344,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() {
@ -393,7 +393,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, () => {});
}
},
@ -429,7 +429,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();
}
@ -477,7 +480,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();

View File

@ -35,10 +35,9 @@ const routes = [
component: Compose,
},
{
path: "/compose/:stackName",
path: "/compose/:stackID",
name: "compose",
component: Compose,
props: true,
},
{
path: "/terminal/:stackName/:serviceName/:type",

View File

@ -490,33 +490,7 @@ optgroup {
}
}
.item {
display: flex;
align-items: center;
height: 52px;
text-decoration: none;
border-radius: 10px;
transition: all ease-in-out 0.15s;
width: 100%;
padding: 0 8px;
&.disabled {
opacity: 0.3;
}
&:hover {
background-color: $highlight-white;
}
&.active {
background-color: #cdf8f4;
}
.title {
display: inline-block;
margin-top: -4px;
}
}
}
.alert-success {

View File

@ -10,6 +10,7 @@
"lint": "eslint \"**/*.{ts,vue}\"",
"check-ts": "tsc --noEmit",
"start": "tsx ./backend/index.ts",
"dev": "concurrently -k -r \"wait-on tcp:5000 && pnpm run dev:backend \" \"pnpm run dev:frontend\"",
"dev:backend": "cross-env NODE_ENV=development tsx watch --inspect ./backend/index.ts",
"dev:frontend": "cross-env NODE_ENV=development vite --host --config ./frontend/vite.config.ts",
"release-final": "tsx ./extra/test-docker.ts && tsx extra/update-version.ts && pnpm run build:frontend && npm run build:docker",
@ -69,6 +70,7 @@
"@vitejs/plugin-vue": "~4.5.0",
"bootstrap": "5.3.2",
"bootstrap-vue-next": "~0.14.10",
"concurrently": "^8.2.2",
"cross-env": "~7.0.3",
"eslint": "~8.50.0",
"eslint-plugin-jsdoc": "~46.8.2",
@ -86,6 +88,7 @@
"vue-qrcode": "~2.2.0",
"vue-router": "~4.2.5",
"vue-toastification": "2.0.0-rc.5",
"wait-on": "^7.2.0",
"xterm": "5.4.0-beta.37",
"xterm-addon-web-links": "~0.9.0"
}

View File

@ -136,6 +136,9 @@ devDependencies:
bootstrap-vue-next:
specifier: ~0.14.10
version: 0.14.10(vue@3.3.8)
concurrently:
specifier: ^8.2.2
version: 8.2.2
cross-env:
specifier: ~7.0.3
version: 7.0.3
@ -187,6 +190,9 @@ devDependencies:
vue-toastification:
specifier: 2.0.0-rc.5
version: 2.0.0-rc.5(vue@3.3.8)
wait-on:
specifier: ^7.2.0
version: 7.2.0
xterm:
specifier: 5.4.0-beta.37
version: 5.4.0-beta.37
@ -239,6 +245,13 @@ packages:
'@babel/types': 7.23.3
dev: true
/@babel/runtime@7.23.5:
resolution: {integrity: sha512-NdUTHcPe4C99WxPub+K9l9tK5/lV4UXIoaHSYgzco9BCyjKAAwzdBI+wWtYqHt7LJdbo74ZjRPJgzVweq1sz0w==}
engines: {node: '>=6.9.0'}
dependencies:
regenerator-runtime: 0.14.0
dev: true
/@babel/types@7.23.3:
resolution: {integrity: sha512-OZnvoH2l8PK5eUvEcUyCt/sXgr/h+UWpVuBbOljwcrAgUl6lpchoQ++PHGyQy1AtYnVA6CEq3y5xeEI10brpXw==}
engines: {node: '>=6.9.0'}
@ -772,6 +785,16 @@ packages:
dev: false
optional: true
/@hapi/hoek@9.3.0:
resolution: {integrity: sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==}
dev: true
/@hapi/topo@5.1.0:
resolution: {integrity: sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==}
dependencies:
'@hapi/hoek': 9.3.0
dev: true
/@homebridge/node-pty-prebuilt-multiarch@0.11.11:
resolution: {integrity: sha512-g7XB2DxGXUuJV4ZS5+8BbztaeqKyihK1zowPL2EeLRp4wfew1qZ3Xw1FWYncpiuRbRvjXrEzXDkcTiYe/XC/ZA==}
requiresBuild: true
@ -1121,6 +1144,20 @@ packages:
dev: true
optional: true
/@sideway/address@4.1.4:
resolution: {integrity: sha512-7vwq+rOHVWjyXxVlR76Agnvhy8I9rpzjosTESvmhNeXOXdZZB15Fl+TI9x1SiHZH5Jv2wTGduSxFDIaq0m3DUw==}
dependencies:
'@hapi/hoek': 9.3.0
dev: true
/@sideway/formula@3.0.1:
resolution: {integrity: sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==}
dev: true
/@sideway/pinpoint@2.0.0:
resolution: {integrity: sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==}
dev: true
/@socket.io/component-emitter@3.1.0:
resolution: {integrity: sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==}
dev: false
@ -1663,10 +1700,24 @@ packages:
engines: {node: '>=8'}
dev: true
/asynckit@0.4.0:
resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
dev: true
/await-lock@2.2.2:
resolution: {integrity: sha512-aDczADvlvTGajTDjcjpJMqRkOF6Qdz3YbPZm/PyW6tKPkx2hlYBzxMhEywM/tU72HrVZjgl5VCdRuMlA7pZ8Gw==}
dev: false
/axios@1.6.2:
resolution: {integrity: sha512-7i24Ri4pmDRfJTR7LDBhsOTtcm+9kjX5WiY1X3wIisx6G9So3pfMkEiU7emUBe46oceVImccTEM3k6C5dbVW8A==}
dependencies:
follow-redirects: 1.15.3
form-data: 4.0.0
proxy-from-env: 1.1.0
transitivePeerDependencies:
- debug
dev: true
/balanced-match@1.0.2:
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
@ -1891,6 +1942,15 @@ packages:
wrap-ansi: 6.2.0
dev: true
/cliui@8.0.1:
resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==}
engines: {node: '>=12'}
dependencies:
string-width: 4.2.3
strip-ansi: 6.0.1
wrap-ansi: 7.0.0
dev: true
/color-convert@1.9.3:
resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==}
dependencies:
@ -1919,6 +1979,13 @@ packages:
resolution: {integrity: sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==}
dev: false
/combined-stream@1.0.8:
resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
engines: {node: '>= 0.8'}
dependencies:
delayed-stream: 1.0.0
dev: true
/command-exists@1.2.9:
resolution: {integrity: sha512-LTQ/SGc+s0Xc0Fu5WaKnR0YiygZkm9eKFvyS+fRsU7/ZWFF8ykFM6Pc9aCVf1+xasOOZpO3BAVgVrKvsqKHV7w==}
dev: false
@ -1976,6 +2043,22 @@ packages:
/concat-map@0.0.1:
resolution: {integrity: sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=}
/concurrently@8.2.2:
resolution: {integrity: sha512-1dP4gpXFhei8IOtlXRE/T/4H88ElHgTiUzh71YUmtjTEHMSRS2Z/fgOxHSxxusGHogsRfxNq1vyAwxSC+EVyDg==}
engines: {node: ^14.13.0 || >=16.0.0}
hasBin: true
dependencies:
chalk: 4.1.2
date-fns: 2.30.0
lodash: 4.17.21
rxjs: 7.8.1
shell-quote: 1.8.1
spawn-command: 0.0.2
supports-color: 8.1.1
tree-kill: 1.2.2
yargs: 17.7.2
dev: true
/console-control-strings@1.1.0:
resolution: {integrity: sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==}
dev: false
@ -2051,6 +2134,13 @@ packages:
resolution: {integrity: sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==}
dev: true
/date-fns@2.30.0:
resolution: {integrity: sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==}
engines: {node: '>=0.11'}
dependencies:
'@babel/runtime': 7.23.5
dev: true
/dayjs@1.11.10:
resolution: {integrity: sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==}
dev: false
@ -2111,6 +2201,11 @@ packages:
has-property-descriptors: 1.0.1
dev: false
/delayed-stream@1.0.0:
resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
engines: {node: '>=0.4.0'}
dev: true
/delegates@1.0.0:
resolution: {integrity: sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==}
dev: false
@ -2322,7 +2417,6 @@ packages:
/escalade@3.1.1:
resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==}
engines: {node: '>=6'}
dev: false
/escape-html@1.0.3:
resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==}
@ -2628,6 +2722,16 @@ packages:
resolution: {integrity: sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==}
dev: true
/follow-redirects@1.15.3:
resolution: {integrity: sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==}
engines: {node: '>=4.0'}
peerDependencies:
debug: '*'
peerDependenciesMeta:
debug:
optional: true
dev: true
/foreground-child@3.1.1:
resolution: {integrity: sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==}
engines: {node: '>=14'}
@ -2636,6 +2740,15 @@ packages:
signal-exit: 4.1.0
dev: false
/form-data@4.0.0:
resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==}
engines: {node: '>= 6'}
dependencies:
asynckit: 0.4.0
combined-stream: 1.0.8
mime-types: 2.1.35
dev: true
/forwarded@0.2.0:
resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==}
engines: {node: '>= 0.6'}
@ -3075,6 +3188,16 @@ packages:
'@pkgjs/parseargs': 0.11.0
dev: false
/joi@17.11.0:
resolution: {integrity: sha512-NgB+lZLNoqISVy1rZocE9PZI36bL/77ie924Ri43yEvi9GUUMPeyVIr8KdFTMUlby1p0PBYMk9spIxEUQYqrJQ==}
dependencies:
'@hapi/hoek': 9.3.0
'@hapi/topo': 5.1.0
'@sideway/address': 4.1.4
'@sideway/formula': 3.0.1
'@sideway/pinpoint': 2.0.0
dev: true
/js-tokens@4.0.0:
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
dev: false
@ -3427,14 +3550,12 @@ packages:
/mime-db@1.52.0:
resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==}
engines: {node: '>= 0.6'}
dev: false
/mime-types@2.1.35:
resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
engines: {node: '>= 0.6'}
dependencies:
mime-db: 1.52.0
dev: false
/mime@1.6.0:
resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==}
@ -3460,7 +3581,6 @@ packages:
/minimist@1.2.8:
resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==}
dev: false
/minipass-collect@1.0.2:
resolution: {integrity: sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==}
@ -3911,6 +4031,10 @@ packages:
ipaddr.js: 1.9.1
dev: false
/proxy-from-env@1.1.0:
resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==}
dev: true
/pump@3.0.0:
resolution: {integrity: sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==}
dependencies:
@ -4018,6 +4142,10 @@ packages:
engines: {node: '>=6'}
dev: false
/regenerator-runtime@0.14.0:
resolution: {integrity: sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==}
dev: true
/require-directory@2.1.1:
resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==}
engines: {node: '>=0.10.0'}
@ -4093,6 +4221,12 @@ packages:
queue-microtask: 1.2.3
dev: true
/rxjs@7.8.1:
resolution: {integrity: sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==}
dependencies:
tslib: 2.6.2
dev: true
/safe-buffer@5.2.1:
resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
dev: false
@ -4187,6 +4321,10 @@ packages:
resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==}
engines: {node: '>=8'}
/shell-quote@1.8.1:
resolution: {integrity: sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==}
dev: true
/side-channel@1.0.4:
resolution: {integrity: sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==}
dependencies:
@ -4318,6 +4456,10 @@ packages:
engines: {node: '>=0.10.0'}
dev: false
/spawn-command@0.0.2:
resolution: {integrity: sha1-lUThpDygRfhTGqwaSMspva5iM44=}
dev: true
/spdx-exceptions@2.3.0:
resolution: {integrity: sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==}
dev: true
@ -4421,6 +4563,13 @@ packages:
dependencies:
has-flag: 4.0.0
/supports-color@8.1.1:
resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==}
engines: {node: '>=10'}
dependencies:
has-flag: 4.0.0
dev: true
/supports-preserve-symlinks-flag@1.0.0:
resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
engines: {node: '>= 0.4'}
@ -4506,6 +4655,11 @@ packages:
resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==}
dev: false
/tree-kill@1.2.2:
resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==}
hasBin: true
dev: true
/ts-api-utils@1.0.3(typescript@5.2.2):
resolution: {integrity: sha512-wNMeqtMz5NtwpT/UZGY5alT+VoKdSsOOP/kqHFcUW1P/VRhH2wJ48+DN2WwUliNbQ976ETwDL0Ifd2VVvgonvg==}
engines: {node: '>=16.13.0'}
@ -4837,6 +4991,20 @@ packages:
typescript: 5.2.2
dev: true
/wait-on@7.2.0:
resolution: {integrity: sha512-wCQcHkRazgjG5XoAq9jbTMLpNIjoSlZslrJ2+N9MxDsGEv1HnFoVjOCexL0ESva7Y9cu350j+DWADdk54s4AFQ==}
engines: {node: '>=12.0.0'}
hasBin: true
dependencies:
axios: 1.6.2
joi: 17.11.0
lodash: 4.17.21
minimist: 1.2.8
rxjs: 7.8.1
transitivePeerDependencies:
- debug
dev: true
/webidl-conversions@3.0.1:
resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==}
dev: false
@ -4898,7 +5066,6 @@ packages:
ansi-styles: 4.3.0
string-width: 4.2.3
strip-ansi: 6.0.1
dev: false
/wrap-ansi@8.1.0:
resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==}
@ -4952,6 +5119,11 @@ packages:
resolution: {integrity: sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==}
dev: true
/y18n@5.0.8:
resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==}
engines: {node: '>=10'}
dev: true
/yallist@4.0.0:
resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==}
@ -4984,6 +5156,11 @@ packages:
decamelize: 1.2.0
dev: true
/yargs-parser@21.1.1:
resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==}
engines: {node: '>=12'}
dev: true
/yargs@15.4.1:
resolution: {integrity: sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==}
engines: {node: '>=8'}
@ -5001,6 +5178,19 @@ packages:
yargs-parser: 18.1.3
dev: true
/yargs@17.7.2:
resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==}
engines: {node: '>=12'}
dependencies:
cliui: 8.0.1
escalade: 3.1.1
get-caller-file: 2.0.5
require-directory: 2.1.1
string-width: 4.2.3
y18n: 5.0.8
yargs-parser: 21.1.1
dev: true
/yocto-queue@0.1.0:
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
engines: {node: '>=10'}