mirror of
https://github.com/louislam/dockge.git
synced 2024-12-01 04:33:12 +01:00
wip
This commit is contained in:
parent
5f70fa6baf
commit
7d1da2ad99
17
.dockerignore
Normal file
17
.dockerignore
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
# Should be identical to .gitignore
|
||||||
|
.env
|
||||||
|
node_modules
|
||||||
|
dist
|
||||||
|
frontend-dist
|
||||||
|
.idea
|
||||||
|
data
|
||||||
|
tmp
|
||||||
|
/private
|
||||||
|
|
||||||
|
# Docker extra
|
||||||
|
docker
|
||||||
|
frontend
|
||||||
|
.editorconfig
|
||||||
|
.eslintrc.cjs
|
||||||
|
.gitignore
|
||||||
|
README.md
|
@ -92,5 +92,6 @@ module.exports = {
|
|||||||
"one-var": [ "error", "never" ],
|
"one-var": [ "error", "never" ],
|
||||||
"max-statements-per-line": [ "error", { "max": 1 }],
|
"max-statements-per-line": [ "error", { "max": 1 }],
|
||||||
"@typescript-eslint/ban-ts-comment": "off",
|
"@typescript-eslint/ban-ts-comment": "off",
|
||||||
|
"prefer-const" : "off",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
4
.gitignore
vendored
4
.gitignore
vendored
@ -1,8 +1,10 @@
|
|||||||
# dotenv environment variable files
|
# Should update .dockerignore as well
|
||||||
.env
|
.env
|
||||||
node_modules
|
node_modules
|
||||||
dist
|
dist
|
||||||
|
frontend-dist
|
||||||
.idea
|
.idea
|
||||||
data
|
data
|
||||||
tmp
|
tmp
|
||||||
|
/private
|
||||||
|
|
||||||
|
34
README.md
34
README.md
@ -1,21 +1,33 @@
|
|||||||
|
<div align="center" width="100%">
|
||||||
|
<img src="./frontend/public/icon.svg" width="128" alt="" />
|
||||||
|
</div>
|
||||||
|
|
||||||
# Dockge
|
# Dockge
|
||||||
|
|
||||||
## Features
|
A fancy, easy-to-use and reactive docker stack (`docker-compose.yml`) manager.
|
||||||
|
|
||||||
- Easy-to-use
|
## ⭐ Features
|
||||||
- Fancy UI
|
|
||||||
- Focus on `docker-compose` stack management
|
- Focus on `docker-compose.yml` stack management
|
||||||
- Interactive editor for `docker-compose.yml` files
|
- Interactive editor for `docker-compose.yml` files
|
||||||
- Easy to expose your service to the internet with https
|
- Interactive web terminal for containers and any docker commands
|
||||||
|
- Reactive - Everything is just responsive. Progress and terminal output are in real-time
|
||||||
|
- Easy-to-use & fancy UI - If you love Uptime Kuma's UI, you will love this too
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
## Motivations
|
## Motivations
|
||||||
|
|
||||||
- Want to build my dream web-based container manager
|
- Try ES Module and TypeScript in 2023
|
||||||
- Want to try next gen runtime like Deno or Bun, but I chose Deno because at this moment, Deno is more stable and Jetbrains IDE support is better.
|
- I have been using Portainer for some time, but I am sometimes not satisfied with it. For example, sometimes when I deploy a stack, it keeps spinning the loading icon for a few minutes, and I don't know what's going on.
|
||||||
- Full TypeScript and ES module
|
|
||||||
- Try DaisyUI + TailwindCSS
|
|
||||||
|
|
||||||
## Dockge?
|
If you love this project, please consider giving this project a ⭐.
|
||||||
|
|
||||||
Naming idea is coming from Twitch emotes. There are many emotes sound like this such as `bedge` and `sadge`.
|
## More Ideas?
|
||||||
|
|
||||||
|
- Container file manager
|
||||||
|
- App store for yaml templates
|
||||||
|
- Container stats
|
||||||
|
- Get app icons
|
||||||
|
- Switch Docker context
|
||||||
|
- Zero-config private docker registry
|
||||||
|
@ -20,27 +20,20 @@ import { R } from "redbean-node";
|
|||||||
import { genSecret, isDev } from "./util-common";
|
import { genSecret, isDev } from "./util-common";
|
||||||
import { generatePasswordHash } from "./password-hash";
|
import { generatePasswordHash } from "./password-hash";
|
||||||
import { Bean } from "redbean-node/dist/bean";
|
import { Bean } from "redbean-node/dist/bean";
|
||||||
import { DockgeSocket } from "./util-server";
|
import { Arguments, Config, DockgeSocket } from "./util-server";
|
||||||
import { DockerSocketHandler } from "./socket-handlers/docker-socket-handler";
|
import { DockerSocketHandler } from "./socket-handlers/docker-socket-handler";
|
||||||
import { Terminal } from "./terminal";
|
import expressStaticGzip from "express-static-gzip";
|
||||||
|
import path from "path";
|
||||||
export interface Arguments {
|
import { TerminalSocketHandler } from "./socket-handlers/terminal-socket-handler";
|
||||||
sslKey? : string;
|
import { Stack } from "./stack";
|
||||||
sslCert? : string;
|
|
||||||
sslKeyPassphrase? : string;
|
|
||||||
port? : number;
|
|
||||||
hostname? : string;
|
|
||||||
dataDir? : string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class DockgeServer {
|
export class DockgeServer {
|
||||||
app : Express;
|
app : Express;
|
||||||
httpServer : http.Server;
|
httpServer : http.Server;
|
||||||
packageJSON : PackageJson;
|
packageJSON : PackageJson;
|
||||||
io : socketIO.Server;
|
io : socketIO.Server;
|
||||||
config : Arguments;
|
config : Config;
|
||||||
indexHTML : string;
|
indexHTML : string = "";
|
||||||
terminal : Terminal;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* List of express routers
|
* List of express routers
|
||||||
@ -55,6 +48,7 @@ export class DockgeServer {
|
|||||||
socketHandlerList : SocketHandler[] = [
|
socketHandlerList : SocketHandler[] = [
|
||||||
new MainSocketHandler(),
|
new MainSocketHandler(),
|
||||||
new DockerSocketHandler(),
|
new DockerSocketHandler(),
|
||||||
|
new TerminalSocketHandler(),
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -64,10 +58,20 @@ export class DockgeServer {
|
|||||||
|
|
||||||
jwtSecret? : string;
|
jwtSecret? : string;
|
||||||
|
|
||||||
|
stacksDir : string = "";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
constructor() {
|
constructor() {
|
||||||
|
// Catch unexpected errors here
|
||||||
|
let unexpectedErrorHandler = (error : unknown) => {
|
||||||
|
console.trace(error);
|
||||||
|
console.error("If you keep encountering errors, please report to https://github.com/louislam/uptime-kuma/issues");
|
||||||
|
};
|
||||||
|
process.addListener("unhandledRejection", unexpectedErrorHandler);
|
||||||
|
process.addListener("uncaughtException", unexpectedErrorHandler);
|
||||||
|
|
||||||
if (!process.env.NODE_ENV) {
|
if (!process.env.NODE_ENV) {
|
||||||
process.env.NODE_ENV = "production";
|
process.env.NODE_ENV = "production";
|
||||||
}
|
}
|
||||||
@ -75,8 +79,8 @@ export class DockgeServer {
|
|||||||
// Log NODE ENV
|
// Log NODE ENV
|
||||||
log.info("server", "NODE_ENV: " + process.env.NODE_ENV);
|
log.info("server", "NODE_ENV: " + process.env.NODE_ENV);
|
||||||
|
|
||||||
// Load arguments
|
// Define all possible arguments
|
||||||
const args = this.config = parse<Arguments>({
|
let args = parse<Arguments>({
|
||||||
sslKey: {
|
sslKey: {
|
||||||
type: String,
|
type: String,
|
||||||
optional: true,
|
optional: true,
|
||||||
@ -103,21 +107,101 @@ export class DockgeServer {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Load from environment variables or default values if args are not set
|
this.config = args as Config;
|
||||||
args.sslKey = args.sslKey || process.env.DOCKGE_SSL_KEY || undefined;
|
|
||||||
args.sslCert = args.sslCert || process.env.DOCKGE_SSL_CERT || undefined;
|
|
||||||
args.sslKeyPassphrase = args.sslKeyPassphrase || process.env.DOCKGE_SSL_KEY_PASSPHRASE || undefined;
|
|
||||||
args.port = args.port || parseInt(process.env.DOCKGE_PORT) || 5001;
|
|
||||||
args.hostname = args.hostname || process.env.DOCKGE_HOSTNAME || undefined;
|
|
||||||
args.dataDir = args.dataDir || process.env.DOCKGE_DATA_DIR || "./data/";
|
|
||||||
|
|
||||||
log.debug("server", args);
|
// Load from environment variables or default values if args are not set
|
||||||
|
this.config.sslKey = args.sslKey || process.env.DOCKGE_SSL_KEY || undefined;
|
||||||
|
this.config.sslCert = args.sslCert || process.env.DOCKGE_SSL_CERT || undefined;
|
||||||
|
this.config.sslKeyPassphrase = args.sslKeyPassphrase || process.env.DOCKGE_SSL_KEY_PASSPHRASE || undefined;
|
||||||
|
this.config.port = args.port || parseInt(process.env.DOCKGE_PORT) || 5001;
|
||||||
|
this.config.hostname = args.hostname || process.env.DOCKGE_HOSTNAME || undefined;
|
||||||
|
this.config.dataDir = args.dataDir || process.env.DOCKGE_DATA_DIR || "./data/";
|
||||||
|
|
||||||
|
log.debug("server", this.config);
|
||||||
|
|
||||||
this.packageJSON = packageJSON as PackageJson;
|
this.packageJSON = packageJSON as PackageJson;
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.indexHTML = fs.readFileSync("./frontend-dist/index.html").toString();
|
||||||
|
} catch (e) {
|
||||||
|
// "dist/index.html" is not necessary for development
|
||||||
|
if (process.env.NODE_ENV !== "development") {
|
||||||
|
log.error("server", "Error: Cannot find 'frontend-dist/index.html', did you install correctly?");
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create all the necessary directories
|
||||||
this.initDataDir();
|
this.initDataDir();
|
||||||
|
|
||||||
this.terminal = new Terminal(this);
|
// Create express
|
||||||
|
this.app = express();
|
||||||
|
|
||||||
|
// Create HTTP server
|
||||||
|
if (this.config.sslKey && this.config.sslCert) {
|
||||||
|
log.info("server", "Server Type: HTTPS");
|
||||||
|
this.httpServer = https.createServer({
|
||||||
|
key: fs.readFileSync(this.config.sslKey),
|
||||||
|
cert: fs.readFileSync(this.config.sslCert),
|
||||||
|
passphrase: this.config.sslKeyPassphrase,
|
||||||
|
}, this.app);
|
||||||
|
} else {
|
||||||
|
log.info("server", "Server Type: HTTP");
|
||||||
|
this.httpServer = http.createServer(this.app);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Binding Routers
|
||||||
|
for (const router of this.routerList) {
|
||||||
|
this.app.use(router.create(this.app, this));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Static files
|
||||||
|
this.app.use("/", expressStaticGzip("frontend-dist", {
|
||||||
|
enableBrotli: true,
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Universal Route Handler, must be at the end of all express routes.
|
||||||
|
this.app.get("*", async (_request, response) => {
|
||||||
|
response.send(this.indexHTML);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Allow all CORS origins in development
|
||||||
|
let cors = undefined;
|
||||||
|
if (isDev) {
|
||||||
|
cors = {
|
||||||
|
origin: "*",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create Socket.io
|
||||||
|
this.io = new socketIO.Server(this.httpServer, {
|
||||||
|
cors,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.io.on("connection", (socket: Socket) => {
|
||||||
|
log.info("server", "Socket connected!");
|
||||||
|
|
||||||
|
this.sendInfo(socket, true);
|
||||||
|
|
||||||
|
if (this.needSetup) {
|
||||||
|
log.info("server", "Redirect to setup page");
|
||||||
|
socket.emit("setup");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create socket handlers
|
||||||
|
for (const socketHandler of this.socketHandlerList) {
|
||||||
|
socketHandler.create(socket as DockgeSocket, this);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.io.on("disconnect", () => {
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
prepareServer() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -157,64 +241,6 @@ export class DockgeServer {
|
|||||||
this.needSetup = true;
|
this.needSetup = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create express
|
|
||||||
this.app = express();
|
|
||||||
|
|
||||||
if (this.config.sslKey && this.config.sslCert) {
|
|
||||||
log.info("server", "Server Type: HTTPS");
|
|
||||||
this.httpServer = https.createServer({
|
|
||||||
key: fs.readFileSync(this.config.sslKey),
|
|
||||||
cert: fs.readFileSync(this.config.sslCert),
|
|
||||||
passphrase: this.config.sslKeyPassphrase,
|
|
||||||
}, this.app);
|
|
||||||
} else {
|
|
||||||
log.info("server", "Server Type: HTTP");
|
|
||||||
this.httpServer = http.createServer(this.app);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
this.indexHTML = fs.readFileSync("./dist/index.html").toString();
|
|
||||||
} catch (e) {
|
|
||||||
// "dist/index.html" is not necessary for development
|
|
||||||
if (process.env.NODE_ENV !== "development") {
|
|
||||||
log.error("server", "Error: Cannot find 'dist/index.html', did you install correctly?");
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const router of this.routerList) {
|
|
||||||
this.app.use(router.create(this.app, this));
|
|
||||||
}
|
|
||||||
|
|
||||||
let cors = undefined;
|
|
||||||
|
|
||||||
if (isDev) {
|
|
||||||
cors = {
|
|
||||||
origin: "*",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create Socket.io
|
|
||||||
this.io = new socketIO.Server(this.httpServer, {
|
|
||||||
cors,
|
|
||||||
});
|
|
||||||
|
|
||||||
this.io.on("connection", (socket: Socket) => {
|
|
||||||
log.info("server", "Socket connected!");
|
|
||||||
|
|
||||||
this.sendInfo(socket, true);
|
|
||||||
|
|
||||||
if (this.needSetup) {
|
|
||||||
log.info("server", "Redirect to setup page");
|
|
||||||
socket.emit("setup");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create socket handlers
|
|
||||||
for (const socketHandler of this.socketHandlerList) {
|
|
||||||
socketHandler.create(socket as DockgeSocket, this);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Listen
|
// Listen
|
||||||
this.httpServer.listen(5001, this.config.hostname, () => {
|
this.httpServer.listen(5001, this.config.hostname, () => {
|
||||||
if (this.config.hostname) {
|
if (this.config.hostname) {
|
||||||
@ -349,14 +375,21 @@ export class DockgeServer {
|
|||||||
* Initialize the data directory
|
* Initialize the data directory
|
||||||
*/
|
*/
|
||||||
initDataDir() {
|
initDataDir() {
|
||||||
|
if (! fs.existsSync(this.config.dataDir)) {
|
||||||
|
fs.mkdirSync(this.config.dataDir, { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
// Check if a directory
|
// Check if a directory
|
||||||
if (!fs.lstatSync(this.config.dataDir).isDirectory()) {
|
if (!fs.lstatSync(this.config.dataDir).isDirectory()) {
|
||||||
throw new Error(`Fatal error: ${this.config.dataDir} is not a directory`);
|
throw new Error(`Fatal error: ${this.config.dataDir} is not a directory`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (! fs.existsSync(this.config.dataDir)) {
|
// Create data/stacks directory
|
||||||
fs.mkdirSync(this.config.dataDir, { recursive: true });
|
this.stacksDir = path.join(this.config.dataDir, "stacks");
|
||||||
|
if (!fs.existsSync(this.stacksDir)) {
|
||||||
|
fs.mkdirSync(this.stacksDir, { recursive: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
log.info("server", `Data Dir: ${this.config.dataDir}`);
|
log.info("server", `Data Dir: ${this.config.dataDir}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -378,4 +411,19 @@ export class DockgeServer {
|
|||||||
await R.store(jwtSecretBean);
|
await R.store(jwtSecretBean);
|
||||||
return jwtSecretBean;
|
return jwtSecretBean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sendStackList(socket : DockgeSocket) {
|
||||||
|
let room = socket.userID.toString();
|
||||||
|
let stackList = Stack.getStackList(this);
|
||||||
|
let list = {};
|
||||||
|
|
||||||
|
for (let stack of stackList) {
|
||||||
|
list[stack.name] = stack.toSimpleJSON();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.io.to(room).emit("stackList", {
|
||||||
|
ok: true,
|
||||||
|
stackList: list,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,7 @@ export class MainRouter extends Router {
|
|||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
router.get("/", (req, res) => {
|
router.get("/", (req, res) => {
|
||||||
|
res.send(server.indexHTML);
|
||||||
});
|
});
|
||||||
|
|
||||||
return router;
|
return router;
|
||||||
|
@ -1,61 +1,84 @@
|
|||||||
import { SocketHandler } from "../socket-handler.js";
|
import { SocketHandler } from "../socket-handler.js";
|
||||||
import { DockgeServer } from "../dockge-server";
|
import { DockgeServer } from "../dockge-server";
|
||||||
import { checkLogin, DockgeSocket } from "../util-server";
|
import { callbackError, checkLogin, DockgeSocket, ValidationError } from "../util-server";
|
||||||
import { log } from "../log";
|
import { log } from "../log";
|
||||||
|
import yaml from "yaml";
|
||||||
const allowedCommandList : string[] = [
|
import path from "path";
|
||||||
"docker",
|
import fs from "fs";
|
||||||
];
|
import {
|
||||||
|
allowedCommandList,
|
||||||
|
allowedRawKeys,
|
||||||
|
getComposeTerminalName,
|
||||||
|
isDev,
|
||||||
|
PROGRESS_TERMINAL_ROWS
|
||||||
|
} from "../util-common";
|
||||||
|
import { Terminal } from "../terminal";
|
||||||
|
import { Stack } from "../stack";
|
||||||
|
|
||||||
export class DockerSocketHandler extends SocketHandler {
|
export class DockerSocketHandler extends SocketHandler {
|
||||||
create(socket : DockgeSocket, server : DockgeServer) {
|
create(socket : DockgeSocket, server : DockgeServer) {
|
||||||
|
|
||||||
socket.on("composeUp", async (compose, callback) => {
|
socket.on("deployStack", async (name : unknown, composeYAML : unknown, isAdd : unknown, callback) => {
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
socket.on("terminalInput", async (cmd : unknown, errorCallback) => {
|
|
||||||
try {
|
|
||||||
checkLogin(socket);
|
|
||||||
|
|
||||||
if (typeof(cmd) !== "string") {
|
|
||||||
throw new Error("Command must be a string.");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if the command is allowed
|
|
||||||
const cmdParts = cmd.split(" ");
|
|
||||||
const executable = cmdParts[0].trim();
|
|
||||||
log.debug("console", "Executable: " + executable);
|
|
||||||
log.debug("console", "Executable length: " + executable.length);
|
|
||||||
|
|
||||||
if (!allowedCommandList.includes(executable)) {
|
|
||||||
throw new Error("Command not allowed.");
|
|
||||||
}
|
|
||||||
|
|
||||||
server.terminal.write(cmd);
|
|
||||||
} catch (e) {
|
|
||||||
errorCallback({
|
|
||||||
ok: false,
|
|
||||||
msg: e.message,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Setup
|
|
||||||
socket.on("getTerminalBuffer", async (callback) => {
|
|
||||||
try {
|
try {
|
||||||
checkLogin(socket);
|
checkLogin(socket);
|
||||||
|
const stack = this.saveStack(socket, server, name, composeYAML, isAdd);
|
||||||
|
await stack.deploy(socket);
|
||||||
callback({
|
callback({
|
||||||
ok: true,
|
ok: true,
|
||||||
buffer: server.terminal.getBuffer(),
|
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
callbackError(e, callback);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on("saveStack", async (name : unknown, composeYAML : unknown, isAdd : unknown, callback) => {
|
||||||
|
try {
|
||||||
|
checkLogin(socket);
|
||||||
|
this.saveStack(socket, server, name, composeYAML, isAdd);
|
||||||
callback({
|
callback({
|
||||||
ok: false,
|
ok: true,
|
||||||
msg: e.message,
|
"msg": "Saved"
|
||||||
|
});
|
||||||
|
server.sendStackList(socket);
|
||||||
|
} catch (e) {
|
||||||
|
callbackError(e, callback);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on("getStack", (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);
|
||||||
|
callback({
|
||||||
|
ok: true,
|
||||||
|
stack: stack.toJSON(),
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
callbackError(e, callback);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
saveStack(socket : DockgeSocket, server : DockgeServer, name : unknown, composeYAML : unknown, isAdd : unknown) : Stack {
|
||||||
|
// Check types
|
||||||
|
if (typeof(name) !== "string") {
|
||||||
|
throw new ValidationError("Name must be a string");
|
||||||
|
}
|
||||||
|
if (typeof(composeYAML) !== "string") {
|
||||||
|
throw new ValidationError("Compose YAML must be a string");
|
||||||
|
}
|
||||||
|
if (typeof(isAdd) !== "boolean") {
|
||||||
|
throw new ValidationError("isAdd must be a boolean");
|
||||||
|
}
|
||||||
|
|
||||||
|
const stack = new Stack(server, name, composeYAML);
|
||||||
|
stack.save(isAdd);
|
||||||
|
return stack;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -71,7 +71,7 @@ export class MainSocketHandler extends SocketHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
log.debug("auth", "afterLogin");
|
log.debug("auth", "afterLogin");
|
||||||
await this.afterLogin(socket, user);
|
await this.afterLogin(server, socket, user);
|
||||||
log.debug("auth", "afterLogin ok");
|
log.debug("auth", "afterLogin ok");
|
||||||
|
|
||||||
log.info("auth", `Successfully logged in user ${decoded.username}. IP=${clientIP}`);
|
log.info("auth", `Successfully logged in user ${decoded.username}. IP=${clientIP}`);
|
||||||
@ -128,7 +128,7 @@ export class MainSocketHandler extends SocketHandler {
|
|||||||
|
|
||||||
if (user) {
|
if (user) {
|
||||||
if (user.twofa_status === 0) {
|
if (user.twofa_status === 0) {
|
||||||
this.afterLogin(socket, user);
|
this.afterLogin(server, socket, user);
|
||||||
|
|
||||||
log.info("auth", `Successfully logged in user ${data.username}. IP=${clientIP}`);
|
log.info("auth", `Successfully logged in user ${data.username}. IP=${clientIP}`);
|
||||||
|
|
||||||
@ -151,7 +151,7 @@ export class MainSocketHandler extends SocketHandler {
|
|||||||
const verify = notp.totp.verify(data.token, user.twofa_secret, twoFAVerifyOptions);
|
const verify = notp.totp.verify(data.token, user.twofa_secret, twoFAVerifyOptions);
|
||||||
|
|
||||||
if (user.twofa_last_token !== data.token && verify) {
|
if (user.twofa_last_token !== data.token && verify) {
|
||||||
this.afterLogin(socket, user);
|
this.afterLogin(server, socket, user);
|
||||||
|
|
||||||
await R.exec("UPDATE `user` SET twofa_last_token = ? WHERE id = ? ", [
|
await R.exec("UPDATE `user` SET twofa_last_token = ? WHERE id = ? ", [
|
||||||
data.token,
|
data.token,
|
||||||
@ -189,10 +189,15 @@ export class MainSocketHandler extends SocketHandler {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async afterLogin(socket : DockgeSocket, user : User) {
|
async afterLogin(server: DockgeServer, socket : DockgeSocket, user : User) {
|
||||||
socket.userID = user.id;
|
socket.userID = user.id;
|
||||||
socket.join(user.id + "");
|
socket.join(user.id.toString());
|
||||||
socket.join("terminal");
|
|
||||||
|
try {
|
||||||
|
server.sendStackList(socket);
|
||||||
|
} catch (e) {
|
||||||
|
log.error("server", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async login(username : string, password : string) {
|
async login(username : string, password : string) {
|
||||||
|
111
backend/socket-handlers/terminal-socket-handler.ts
Normal file
111
backend/socket-handlers/terminal-socket-handler.ts
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
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, isDev } from "../util-common";
|
||||||
|
import { Terminal } from "../terminal";
|
||||||
|
|
||||||
|
export class TerminalSocketHandler extends SocketHandler {
|
||||||
|
create(socket : DockgeSocket, server : DockgeServer) {
|
||||||
|
|
||||||
|
socket.on("terminalInputRaw", async (key : unknown) => {
|
||||||
|
try {
|
||||||
|
checkLogin(socket);
|
||||||
|
|
||||||
|
if (typeof(key) !== "string") {
|
||||||
|
throw new Error("Key must be a string.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (allowedRawKeys.includes(key)) {
|
||||||
|
server.terminal.write(key);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on("terminalInput", async (terminalName : unknown, cmd : unknown, errorCallback : unknown) => {
|
||||||
|
try {
|
||||||
|
checkLogin(socket);
|
||||||
|
|
||||||
|
if (typeof(cmd) !== "string") {
|
||||||
|
throw new Error("Command must be a string.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the command is allowed
|
||||||
|
const cmdParts = cmd.split(" ");
|
||||||
|
const executable = cmdParts[0].trim();
|
||||||
|
log.debug("console", "Executable: " + executable);
|
||||||
|
log.debug("console", "Executable length: " + executable.length);
|
||||||
|
|
||||||
|
if (!allowedCommandList.includes(executable)) {
|
||||||
|
throw new Error("Command not allowed.");
|
||||||
|
}
|
||||||
|
|
||||||
|
server.terminal.write(cmd);
|
||||||
|
} catch (e) {
|
||||||
|
if (typeof(errorCallback) === "function") {
|
||||||
|
errorCallback({
|
||||||
|
ok: false,
|
||||||
|
msg: e.message,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create Terminal
|
||||||
|
socket.on("terminalCreate", async (terminalName : unknown, callback : unknown) => {
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
// Join Terminal
|
||||||
|
socket.on("terminalJoin", async (terminalName : unknown, callback : unknown) => {
|
||||||
|
if (typeof(callback) !== "function") {
|
||||||
|
log.debug("console", "Callback is not a function.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
checkLogin(socket);
|
||||||
|
if (typeof(terminalName) !== "string") {
|
||||||
|
throw new ValidationError("Terminal name must be a string.");
|
||||||
|
}
|
||||||
|
|
||||||
|
let buffer : string = Terminal.getTerminal(terminalName)?.getBuffer() ?? "";
|
||||||
|
|
||||||
|
if (!buffer) {
|
||||||
|
log.debug("console", "No buffer found.");
|
||||||
|
}
|
||||||
|
|
||||||
|
callback({
|
||||||
|
ok: true,
|
||||||
|
buffer,
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
callbackError(e, callback);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Close Terminal
|
||||||
|
socket.on("terminalClose", async (terminalName : unknown, callback : unknown) => {
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
// Resize Terminal
|
||||||
|
socket.on("terminalResize", async (rows : unknown) => {
|
||||||
|
try {
|
||||||
|
checkLogin(socket);
|
||||||
|
if (typeof(rows) !== "number") {
|
||||||
|
throw new Error("Rows must be a number.");
|
||||||
|
}
|
||||||
|
log.debug("console", "Resize terminal to " + rows + " rows.");
|
||||||
|
server.terminal.resize(rows);
|
||||||
|
} catch (e) {
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
158
backend/stack.ts
Normal file
158
backend/stack.ts
Normal file
@ -0,0 +1,158 @@
|
|||||||
|
import { DockgeServer } from "./dockge-server";
|
||||||
|
import fs from "fs";
|
||||||
|
import { log } from "./log";
|
||||||
|
import yaml from "yaml";
|
||||||
|
import { DockgeSocket, ValidationError } from "./util-server";
|
||||||
|
import path from "path";
|
||||||
|
import { getComposeTerminalName, PROGRESS_TERMINAL_ROWS } from "./util-common";
|
||||||
|
import { Terminal } from "./terminal";
|
||||||
|
|
||||||
|
export class Stack {
|
||||||
|
|
||||||
|
name: string;
|
||||||
|
protected _composeYAML?: string;
|
||||||
|
protected server: DockgeServer;
|
||||||
|
|
||||||
|
constructor(server : DockgeServer, name : string, composeYAML? : string) {
|
||||||
|
this.name = name;
|
||||||
|
this.server = server;
|
||||||
|
this._composeYAML = composeYAML;
|
||||||
|
}
|
||||||
|
|
||||||
|
toJSON() : object {
|
||||||
|
let obj = this.toSimpleJSON();
|
||||||
|
return {
|
||||||
|
...obj,
|
||||||
|
composeYAML: this.composeYAML,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
toSimpleJSON() : object {
|
||||||
|
return {
|
||||||
|
name: this.name,
|
||||||
|
tags: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
validate() {
|
||||||
|
// Check name, allows [a-z][A-Z][0-9] _ - only
|
||||||
|
if (!this.name.match(/^[a-zA-Z0-9_-]+$/)) {
|
||||||
|
throw new ValidationError("Stack name can only contain [a-z][A-Z][0-9] _ - only");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check YAML format
|
||||||
|
yaml.parse(this.composeYAML);
|
||||||
|
}
|
||||||
|
|
||||||
|
get composeYAML() : string {
|
||||||
|
if (this._composeYAML === undefined) {
|
||||||
|
try {
|
||||||
|
this._composeYAML = fs.readFileSync(path.join(this.path, "compose.yaml"), "utf-8");
|
||||||
|
} catch (e) {
|
||||||
|
this._composeYAML = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this._composeYAML;
|
||||||
|
}
|
||||||
|
|
||||||
|
get path() : string {
|
||||||
|
return path.join(this.server.stacksDir, this.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
get fullPath() : string {
|
||||||
|
let dir = this.path;
|
||||||
|
|
||||||
|
// Compose up via node-pty
|
||||||
|
let fullPathDir;
|
||||||
|
|
||||||
|
// if dir is relative, make it absolute
|
||||||
|
if (!path.isAbsolute(dir)) {
|
||||||
|
fullPathDir = path.join(process.cwd(), dir);
|
||||||
|
} else {
|
||||||
|
fullPathDir = dir;
|
||||||
|
}
|
||||||
|
return fullPathDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save the stack to the disk
|
||||||
|
* @param isAdd
|
||||||
|
*/
|
||||||
|
save(isAdd : boolean) {
|
||||||
|
this.validate();
|
||||||
|
|
||||||
|
let dir = this.path;
|
||||||
|
|
||||||
|
// Check if the name is used if isAdd
|
||||||
|
if (isAdd) {
|
||||||
|
if (fs.existsSync(dir)) {
|
||||||
|
throw new ValidationError("Stack name already exists");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the stack folder
|
||||||
|
fs.mkdirSync(dir);
|
||||||
|
} else {
|
||||||
|
if (!fs.existsSync(dir)) {
|
||||||
|
throw new ValidationError("Stack not found");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write or overwrite the compose.yaml
|
||||||
|
fs.writeFileSync(path.join(dir, "compose.yaml"), this.composeYAML);
|
||||||
|
}
|
||||||
|
|
||||||
|
async deploy(socket? : DockgeSocket) : Promise<number> {
|
||||||
|
const terminalName = getComposeTerminalName(this.name);
|
||||||
|
log.debug("deployStack", "Terminal name: " + terminalName);
|
||||||
|
|
||||||
|
const terminal = new Terminal(this.server, terminalName, "docker-compose", [ "up", "-d" ], this.path);
|
||||||
|
log.debug("deployStack", "Terminal created");
|
||||||
|
|
||||||
|
terminal.rows = PROGRESS_TERMINAL_ROWS;
|
||||||
|
|
||||||
|
if (socket) {
|
||||||
|
terminal.join(socket);
|
||||||
|
log.debug("deployStack", "Terminal joined");
|
||||||
|
} else {
|
||||||
|
log.debug("deployStack", "No socket, not joining");
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
terminal.onExit((exitCode : number) => {
|
||||||
|
if (exitCode === 0) {
|
||||||
|
resolve(exitCode);
|
||||||
|
} else {
|
||||||
|
reject(new Error("Failed to deploy, please check the terminal output for more information."));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
terminal.start();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static getStackList(server : DockgeServer) : Stack[] {
|
||||||
|
let stacksDir = server.stacksDir;
|
||||||
|
let stackList : Stack[] = [];
|
||||||
|
|
||||||
|
// Scan the stacks directory, and get the stack list
|
||||||
|
let filenameList = fs.readdirSync(stacksDir);
|
||||||
|
|
||||||
|
log.debug("stack", filenameList);
|
||||||
|
|
||||||
|
for (let filename of filenameList) {
|
||||||
|
let relativePath = path.join(stacksDir, filename);
|
||||||
|
if (fs.statSync(relativePath).isDirectory()) {
|
||||||
|
let stack = new Stack(server, filename);
|
||||||
|
stackList.push(stack);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return stackList;
|
||||||
|
}
|
||||||
|
|
||||||
|
static getStack(server: DockgeServer, stackName: string) : Stack {
|
||||||
|
let dir = path.join(server.stacksDir, stackName);
|
||||||
|
if (!fs.existsSync(dir)) {
|
||||||
|
throw new ValidationError("Stack not found");
|
||||||
|
}
|
||||||
|
return new Stack(server, stackName);
|
||||||
|
}
|
||||||
|
}
|
@ -1,43 +1,151 @@
|
|||||||
import { DockgeServer } from "./dockge-server";
|
import { DockgeServer } from "./dockge-server";
|
||||||
import * as os from "node:os";
|
import * as os from "node:os";
|
||||||
import * as pty from "node-pty";
|
import * as pty from "@homebridge/node-pty-prebuilt-multiarch";
|
||||||
import { LimitQueue } from "./utils/limit-queue";
|
import { LimitQueue } from "./utils/limit-queue";
|
||||||
|
import { DockgeSocket } from "./util-server";
|
||||||
|
import { getCryptoRandomInt, TERMINAL_COLS, TERMINAL_ROWS } from "./util-common";
|
||||||
|
import { sync as commandExistsSync } from "command-exists";
|
||||||
|
import { log } from "./log";
|
||||||
|
|
||||||
const shell = os.platform() === "win32" ? "pwsh.exe" : "bash";
|
/**
|
||||||
|
* Terminal for running commands, no user interaction
|
||||||
|
*/
|
||||||
export class Terminal {
|
export class Terminal {
|
||||||
|
|
||||||
ptyProcess;
|
protected static terminalMap : Map<string, Terminal> = new Map();
|
||||||
private server : DockgeServer;
|
|
||||||
private buffer : LimitQueue<string> = new LimitQueue(100);
|
|
||||||
|
|
||||||
constructor(server : DockgeServer) {
|
protected _ptyProcess? : pty.IPty;
|
||||||
|
protected server : DockgeServer;
|
||||||
|
protected buffer : LimitQueue<string> = new LimitQueue(100);
|
||||||
|
protected _name : string;
|
||||||
|
|
||||||
|
protected file : string;
|
||||||
|
protected args : string | string[];
|
||||||
|
protected cwd : string;
|
||||||
|
protected callback? : (exitCode : number) => void;
|
||||||
|
|
||||||
|
protected _rows : number = TERMINAL_ROWS;
|
||||||
|
|
||||||
|
constructor(server : DockgeServer, name : string, file : string, args : string | string[], cwd : string) {
|
||||||
this.server = server;
|
this.server = server;
|
||||||
|
this._name = name;
|
||||||
|
//this._name = "terminal-" + Date.now() + "-" + getCryptoRandomInt(0, 1000000);
|
||||||
|
this.file = file;
|
||||||
|
this.args = args;
|
||||||
|
this.cwd = cwd;
|
||||||
|
|
||||||
this.ptyProcess = pty.spawn(shell, [], {
|
Terminal.terminalMap.set(this.name, this);
|
||||||
name: "dockge-terminal",
|
|
||||||
cwd: "./tmp",
|
|
||||||
});
|
|
||||||
|
|
||||||
// this.ptyProcess.write("npm remove lodash\r");
|
|
||||||
//this.ptyProcess.write("npm install lodash\r");
|
|
||||||
|
|
||||||
this.ptyProcess.onData((data) => {
|
|
||||||
this.buffer.push(data);
|
|
||||||
this.server.io.to("terminal").emit("commandOutput", data);
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
write(input : string) {
|
get rows() {
|
||||||
this.ptyProcess.write(input);
|
return this._rows;
|
||||||
|
}
|
||||||
|
|
||||||
|
set rows(rows : number) {
|
||||||
|
this._rows = rows;
|
||||||
|
this.ptyProcess?.resize(TERMINAL_COLS, rows);
|
||||||
|
}
|
||||||
|
|
||||||
|
public start() {
|
||||||
|
this._ptyProcess = pty.spawn(this.file, this.args, {
|
||||||
|
name: this.name,
|
||||||
|
cwd: this.cwd,
|
||||||
|
cols: TERMINAL_COLS,
|
||||||
|
rows: this.rows,
|
||||||
|
});
|
||||||
|
|
||||||
|
// On Data
|
||||||
|
this._ptyProcess.onData((data) => {
|
||||||
|
this.buffer.push(data);
|
||||||
|
if (this.server.io) {
|
||||||
|
this.server.io.to(this.name).emit("terminalWrite", this.name, data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// On Exit
|
||||||
|
this._ptyProcess.onExit((res) => {
|
||||||
|
this.server.io.to(this.name).emit("terminalExit", this.name, res.exitCode);
|
||||||
|
|
||||||
|
// Remove room
|
||||||
|
this.server.io.in(this.name).socketsLeave(this.name);
|
||||||
|
|
||||||
|
if (this.callback) {
|
||||||
|
this.callback(res.exitCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
Terminal.terminalMap.delete(this.name);
|
||||||
|
log.debug("Terminal", "Terminal " + this.name + " exited with code " + res.exitCode);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public onExit(callback : (exitCode : number) => void) {
|
||||||
|
this.callback = callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
public join(socket : DockgeSocket) {
|
||||||
|
socket.join(this.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public leave(socket : DockgeSocket) {
|
||||||
|
socket.leave(this.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public get ptyProcess() {
|
||||||
|
return this._ptyProcess;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get name() {
|
||||||
|
return this._name;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the terminal output string for re-connecting
|
* Get the terminal output string
|
||||||
*/
|
*/
|
||||||
getBuffer() : string {
|
getBuffer() : string {
|
||||||
|
if (this.buffer.length === 0) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
return this.buffer.join("");
|
return this.buffer.join("");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
close() {
|
||||||
|
this._ptyProcess?.kill();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static getTerminal(name : string) : Terminal | undefined {
|
||||||
|
return Terminal.terminalMap.get(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interactive terminal
|
||||||
|
* Mainly used for container exec
|
||||||
|
*/
|
||||||
|
export class InteractiveTerminal extends Terminal {
|
||||||
|
public write(input : string) {
|
||||||
|
this.ptyProcess?.write(input);
|
||||||
|
}
|
||||||
|
|
||||||
|
resetCWD() {
|
||||||
|
const cwd = process.cwd();
|
||||||
|
this.ptyProcess?.write(`cd "${cwd}"\r`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* User interactive terminal that use bash or powershell with limited commands such as docker, ls, cd, dir
|
||||||
|
*/
|
||||||
|
export class MainTerminal extends InteractiveTerminal {
|
||||||
|
constructor(server : DockgeServer, name : string, cwd : string = "./") {
|
||||||
|
let shell;
|
||||||
|
|
||||||
|
if (commandExistsSync("pwsh")) {
|
||||||
|
shell = "pwsh";
|
||||||
|
} else if (os.platform() === "win32") {
|
||||||
|
shell = "powershell.exe";
|
||||||
|
} else {
|
||||||
|
shell = "bash";
|
||||||
|
}
|
||||||
|
super(server, name, shell, [], cwd);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,38 @@
|
|||||||
import dayjs from "dayjs";
|
|
||||||
|
|
||||||
// For loading dayjs plugins, don't remove event though it is not used in this file
|
// For loading dayjs plugins, don't remove event though it is not used in this file
|
||||||
|
import dayjs from "dayjs";
|
||||||
import timezone from "dayjs/plugin/timezone";
|
import timezone from "dayjs/plugin/timezone";
|
||||||
import utc from "dayjs/plugin/utc";
|
import utc from "dayjs/plugin/utc";
|
||||||
|
|
||||||
import { randomBytes } from "crypto";
|
let randomBytes : (numBytes: number) => Uint8Array;
|
||||||
|
|
||||||
|
if (typeof window !== "undefined" && window.crypto) {
|
||||||
|
randomBytes = function randomBytes(numBytes: number) {
|
||||||
|
const bytes = new Uint8Array(numBytes);
|
||||||
|
for (let i = 0; i < numBytes; i += 65536) {
|
||||||
|
window.crypto.getRandomValues(bytes.subarray(i, i + Math.min(numBytes - i, 65536)));
|
||||||
|
}
|
||||||
|
return bytes;
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
randomBytes = (await import("node:crypto")).randomBytes;
|
||||||
|
}
|
||||||
|
|
||||||
export const isDev = process.env.NODE_ENV === "development";
|
export const isDev = process.env.NODE_ENV === "development";
|
||||||
|
export const TERMINAL_COLS = 80;
|
||||||
|
export const TERMINAL_ROWS = 10;
|
||||||
|
export const PROGRESS_TERMINAL_ROWS = 8;
|
||||||
|
|
||||||
|
export const ERROR_TYPE_VALIDATION = 1;
|
||||||
|
|
||||||
|
export const allowedCommandList : string[] = [
|
||||||
|
"docker",
|
||||||
|
"ls",
|
||||||
|
"cd",
|
||||||
|
"dir",
|
||||||
|
];
|
||||||
|
export const allowedRawKeys = [
|
||||||
|
"\u0003", // Ctrl + C
|
||||||
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate a decimal integer number from a string
|
* Generate a decimal integer number from a string
|
||||||
@ -54,7 +80,6 @@ export function genSecret(length = 64) {
|
|||||||
* @returns Cryptographically suitable random integer
|
* @returns Cryptographically suitable random integer
|
||||||
*/
|
*/
|
||||||
export function getCryptoRandomInt(min: number, max: number):number {
|
export function getCryptoRandomInt(min: number, max: number):number {
|
||||||
|
|
||||||
// synchronous version of: https://github.com/joepie91/node-random-number-csprng
|
// synchronous version of: https://github.com/joepie91/node-random-number-csprng
|
||||||
|
|
||||||
const range = max - min;
|
const range = max - min;
|
||||||
@ -76,11 +101,11 @@ export function getCryptoRandomInt(min: number, max: number):number {
|
|||||||
tmpRange = tmpRange >>> 1;
|
tmpRange = tmpRange >>> 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
const randomBytes = getRandomBytes(bytesNeeded);
|
const bytes = randomBytes(bytesNeeded);
|
||||||
let randomValue = 0;
|
let randomValue = 0;
|
||||||
|
|
||||||
for (let i = 0; i < bytesNeeded; i++) {
|
for (let i = 0; i < bytesNeeded; i++) {
|
||||||
randomValue |= randomBytes[i] << 8 * i;
|
randomValue |= bytes[i] << 8 * i;
|
||||||
}
|
}
|
||||||
|
|
||||||
randomValue = randomValue & mask;
|
randomValue = randomValue & mask;
|
||||||
@ -92,27 +117,15 @@ export function getCryptoRandomInt(min: number, max: number):number {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
export function getComposeTerminalName(stack : string) {
|
||||||
* Returns either the NodeJS crypto.randomBytes() function or its
|
return "compose-" + stack;
|
||||||
* browser equivalent implemented via window.crypto.getRandomValues()
|
|
||||||
*/
|
|
||||||
const getRandomBytes = (
|
|
||||||
(typeof window !== "undefined" && window.crypto)
|
|
||||||
|
|
||||||
// Browsers
|
|
||||||
? function () {
|
|
||||||
return (numBytes: number) => {
|
|
||||||
const randomBytes = new Uint8Array(numBytes);
|
|
||||||
for (let i = 0; i < numBytes; i += 65536) {
|
|
||||||
window.crypto.getRandomValues(randomBytes.subarray(i, i + Math.min(numBytes - i, 65536)));
|
|
||||||
}
|
|
||||||
return randomBytes;
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Node
|
export function getContainerTerminalName(container : string) {
|
||||||
: function () {
|
return "container-" + container;
|
||||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
||||||
return randomBytes;
|
|
||||||
}
|
}
|
||||||
)();
|
|
||||||
|
export function getContainerExecTerminalName(container : string, index : number) {
|
||||||
|
return "container-exec-" + container + "-" + index;
|
||||||
|
}
|
||||||
|
|
||||||
|
@ -1,7 +1,27 @@
|
|||||||
import { Socket } from "socket.io";
|
import { Socket } from "socket.io";
|
||||||
|
import { Terminal } from "./terminal";
|
||||||
|
import { randomBytes } from "crypto";
|
||||||
|
import { log } from "./log";
|
||||||
|
import { ERROR_TYPE_VALIDATION } from "./util-common";
|
||||||
|
|
||||||
export interface DockgeSocket extends Socket {
|
export interface DockgeSocket extends Socket {
|
||||||
userID: number;
|
userID: number;
|
||||||
|
consoleTerminal? : Terminal;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For command line arguments, so they are nullable
|
||||||
|
export interface Arguments {
|
||||||
|
sslKey? : string;
|
||||||
|
sslCert? : string;
|
||||||
|
sslKeyPassphrase? : string;
|
||||||
|
port? : number;
|
||||||
|
hostname? : string;
|
||||||
|
dataDir? : string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Some config values are required
|
||||||
|
export interface Config extends Arguments {
|
||||||
|
dataDir : string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function checkLogin(socket : DockgeSocket) {
|
export function checkLogin(socket : DockgeSocket) {
|
||||||
@ -9,3 +29,31 @@ export function checkLogin(socket : DockgeSocket) {
|
|||||||
throw new Error("You are not logged in.");
|
throw new Error("You are not logged in.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class ValidationError extends Error {
|
||||||
|
constructor(message : string) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function callbackError(error : unknown, callback : unknown) {
|
||||||
|
if (typeof(callback) !== "function") {
|
||||||
|
log.error("console", "Callback is not a function");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error instanceof Error) {
|
||||||
|
callback({
|
||||||
|
ok: false,
|
||||||
|
msg: error.message,
|
||||||
|
});
|
||||||
|
} else if (error instanceof ValidationError) {
|
||||||
|
callback({
|
||||||
|
ok: false,
|
||||||
|
type: ERROR_TYPE_VALIDATION,
|
||||||
|
msg: error.message,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
log.debug("console", "Unknown error: " + error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,20 +1,39 @@
|
|||||||
FROM debian:bookworm-slim
|
FROM node:20-bookworm-slim
|
||||||
|
ENV PNPM_HOME="/pnpm"
|
||||||
|
ENV PATH="$PNPM_HOME:$PATH"
|
||||||
|
|
||||||
COPY --from=docker:dind /usr/local/bin/docker /usr/local/bin/
|
# COPY --from=docker:dind /usr/local/bin/docker /usr/local/bin/
|
||||||
|
|
||||||
RUN apt update && apt install --yes --no-install-recommends \
|
RUN apt update && apt install --yes --no-install-recommends \
|
||||||
curl \
|
curl \
|
||||||
ca-certificates \
|
ca-certificates \
|
||||||
|
gnupg \
|
||||||
unzip \
|
unzip \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
dumb-init \
|
||||||
RUN curl https://bun.sh/install | bash -s "bun-v1.0.3"
|
&& install -m 0755 -d /etc/apt/keyrings \
|
||||||
|
&& curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg \
|
||||||
|
&& chmod a+r /etc/apt/keyrings/docker.gpg \
|
||||||
|
&& echo \
|
||||||
|
"deb [arch="$(dpkg --print-architecture)" signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/debian \
|
||||||
|
"$(. /etc/os-release && echo "$VERSION_CODENAME")" stable" | \
|
||||||
|
tee /etc/apt/sources.list.d/docker.list > /dev/null \
|
||||||
|
&& apt update \
|
||||||
|
&& apt --yes --no-install-recommends install \
|
||||||
|
docker-ce-cli \
|
||||||
|
docker-compose-plugin \
|
||||||
|
&& rm -rf /var/lib/apt/lists/* \
|
||||||
|
&& npm install pnpm -g \
|
||||||
|
&& pnpm install -g tsx
|
||||||
|
|
||||||
|
# ensures that /var/run/docker.sock exists
|
||||||
|
# changes the ownership of /var/run/docker.sock
|
||||||
|
RUN touch /var/run/docker.sock && chown node:node /var/run/docker.sock
|
||||||
|
|
||||||
# Full Base Image
|
# Full Base Image
|
||||||
# MariaDB, Chromium and fonts
|
# MariaDB, Chromium and fonts
|
||||||
FROM base-slim AS base
|
#FROM base-slim AS base
|
||||||
ENV DOCKGE_ENABLE_EMBEDDED_MARIADB=1
|
#ENV DOCKGE_ENABLE_EMBEDDED_MARIADB=1
|
||||||
RUN apt update && \
|
#RUN apt update && \
|
||||||
apt --yes --no-install-recommends install mariadb-server && \
|
# apt --yes --no-install-recommends install mariadb-server && \
|
||||||
rm -rf /var/lib/apt/lists/* && \
|
# rm -rf /var/lib/apt/lists/* && \
|
||||||
apt --yes autoremove
|
# apt --yes autoremove
|
||||||
|
@ -1,15 +1,9 @@
|
|||||||
FROM debian:bookworm-slim
|
FROM louislam/dockge:base
|
||||||
|
|
||||||
COPY --from=docker:dind /usr/local/bin/docker /usr/local/bin/
|
|
||||||
|
|
||||||
RUN apt update && apt install --yes --no-install-recommends \
|
|
||||||
curl \
|
|
||||||
ca-certificates \
|
|
||||||
unzip \
|
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
|
||||||
RUN curl https://bun.sh/install | bash -s "bun-v1.0.3"
|
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
COPY --chown=node:node . .
|
||||||
COPY . .
|
RUN pnpm install --prod --frozen-lockfile && \
|
||||||
RUN bun install --production --frozen-lockfile
|
mkdir ./data \
|
||||||
|
VOLUME /app/data
|
||||||
|
EXPOSE 5001
|
||||||
|
ENTRYPOINT ["/usr/bin/dumb-init", "--"]
|
||||||
|
CMD ["tsx", "./backend/index.ts"]
|
||||||
|
0
docker/compose.yaml
Normal file
0
docker/compose.yaml
Normal file
5
frontend/components.d.ts
vendored
5
frontend/components.d.ts
vendored
@ -7,14 +7,13 @@ export {}
|
|||||||
|
|
||||||
declare module 'vue' {
|
declare module 'vue' {
|
||||||
export interface GlobalComponents {
|
export interface GlobalComponents {
|
||||||
BDropdown: typeof import('bootstrap-vue-next')['BDropdown']
|
|
||||||
BDropdownItem: typeof import('bootstrap-vue-next')['BDropdownItem']
|
|
||||||
Confirm: typeof import('./src/components/Confirm.vue')['default']
|
Confirm: typeof import('./src/components/Confirm.vue')['default']
|
||||||
Login: typeof import('./src/components/Login.vue')['default']
|
Login: typeof import('./src/components/Login.vue')['default']
|
||||||
MonitorListItem: typeof import('./src/components/MonitorListItem.vue')['default']
|
|
||||||
RouterLink: typeof import('vue-router')['RouterLink']
|
RouterLink: typeof import('vue-router')['RouterLink']
|
||||||
RouterView: typeof import('vue-router')['RouterView']
|
RouterView: typeof import('vue-router')['RouterView']
|
||||||
StackList: typeof import('./src/components/StackList.vue')['default']
|
StackList: typeof import('./src/components/StackList.vue')['default']
|
||||||
StackListItem: typeof import('./src/components/StackListItem.vue')['default']
|
StackListItem: typeof import('./src/components/StackListItem.vue')['default']
|
||||||
|
Terminal: typeof import('./src/components/Terminal.vue')['default']
|
||||||
|
Uptime: typeof import('./src/components/Uptime.vue')['default']
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -107,6 +107,8 @@ export default {
|
|||||||
this.$root.loggedIn = true;
|
this.$root.loggedIn = true;
|
||||||
this.$root.username = this.$root.getJWTPayload()?.username;
|
this.$root.username = this.$root.getJWTPayload()?.username;
|
||||||
|
|
||||||
|
this.$root.afterLogin();
|
||||||
|
|
||||||
// Trigger Chrome Save Password
|
// Trigger Chrome Save Password
|
||||||
history.pushState({}, "");
|
history.pushState({}, "");
|
||||||
}
|
}
|
||||||
|
@ -26,7 +26,7 @@
|
|||||||
|
|
||||||
<!-- TODO -->
|
<!-- TODO -->
|
||||||
<div v-if="false" class="header-filter">
|
<div v-if="false" class="header-filter">
|
||||||
<!--<MonitorListFilter :filterState="filterState" @update-filter="updateFilter" />-->
|
<!--<StackListFilter :filterState="filterState" @update-filter="updateFilter" />-->
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- TODO: Selection Controls -->
|
<!-- TODO: Selection Controls -->
|
||||||
@ -40,12 +40,12 @@
|
|||||||
<button class="btn-outline-normal" @click="pauseDialog"><font-awesome-icon icon="pause" size="sm" /> {{ $t("Pause") }}</button>
|
<button class="btn-outline-normal" @click="pauseDialog"><font-awesome-icon icon="pause" size="sm" /> {{ $t("Pause") }}</button>
|
||||||
<button class="btn-outline-normal" @click="resumeSelected"><font-awesome-icon icon="play" size="sm" /> {{ $t("Resume") }}</button>
|
<button class="btn-outline-normal" @click="resumeSelected"><font-awesome-icon icon="play" size="sm" /> {{ $t("Resume") }}</button>
|
||||||
|
|
||||||
<span v-if="selectedMonitorCount > 0">
|
<span v-if="selectedStackCount > 0">
|
||||||
{{ $t("selectedMonitorCount", [ selectedMonitorCount ]) }}
|
{{ $t("selectedStackCount", [ selectedStackCount ]) }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div ref="monitorList" class="monitor-list" :class="{ scrollbar: scrollbar }" :style="monitorListStyle">
|
<div ref="stackList" class="stack-list" :class="{ scrollbar: scrollbar }" :style="stackListStyle">
|
||||||
<div v-if="Object.keys($root.stackList).length === 0" class="text-center mt-3">
|
<div v-if="Object.keys($root.stackList).length === 0" class="text-center mt-3">
|
||||||
<router-link to="/compose">{{ $t("addFirstStackMsg") }}</router-link>
|
<router-link to="/compose">{{ $t("addFirstStackMsg") }}</router-link>
|
||||||
</div>
|
</div>
|
||||||
@ -53,7 +53,7 @@
|
|||||||
<StackListItem
|
<StackListItem
|
||||||
v-for="(item, index) in sortedStackList"
|
v-for="(item, index) in sortedStackList"
|
||||||
:key="index"
|
:key="index"
|
||||||
:monitor="item"
|
:stack="item"
|
||||||
:showPathName="filtersActive"
|
:showPathName="filtersActive"
|
||||||
:isSelectMode="selectMode"
|
:isSelectMode="selectMode"
|
||||||
:isSelected="isSelected"
|
:isSelected="isSelected"
|
||||||
@ -64,7 +64,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Confirm ref="confirmPause" :yes-text="$t('Yes')" :no-text="$t('No')" @yes="pauseSelected">
|
<Confirm ref="confirmPause" :yes-text="$t('Yes')" :no-text="$t('No')" @yes="pauseSelected">
|
||||||
{{ $t("pauseMonitorMsg") }}
|
{{ $t("pauseStackMsg") }}
|
||||||
</Confirm>
|
</Confirm>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -89,7 +89,7 @@ export default {
|
|||||||
selectMode: false,
|
selectMode: false,
|
||||||
selectAll: false,
|
selectAll: false,
|
||||||
disableSelectAllWatcher: false,
|
disableSelectAllWatcher: false,
|
||||||
selectedMonitors: {},
|
selectedStacks: {},
|
||||||
windowTop: 0,
|
windowTop: 0,
|
||||||
filterState: {
|
filterState: {
|
||||||
status: null,
|
status: null,
|
||||||
@ -103,7 +103,7 @@ export default {
|
|||||||
* Improve the sticky appearance of the list by increasing its
|
* Improve the sticky appearance of the list by increasing its
|
||||||
* height as user scrolls down.
|
* height as user scrolls down.
|
||||||
* Not used on mobile.
|
* Not used on mobile.
|
||||||
* @returns {object} Style for monitor list
|
* @returns {object} Style for stack list
|
||||||
*/
|
*/
|
||||||
boxStyle() {
|
boxStyle() {
|
||||||
if (window.innerWidth > 550) {
|
if (window.innerWidth > 550) {
|
||||||
@ -119,80 +119,43 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a sorted list of monitors based on the applied filters and search text.
|
* Returns a sorted list of stacks based on the applied filters and search text.
|
||||||
* @returns {Array} The sorted list of monitors.
|
* @returns {Array} The sorted list of stacks.
|
||||||
*/
|
*/
|
||||||
sortedStackList() {
|
sortedStackList() {
|
||||||
let result = Object.values(this.$root.stackList);
|
let result = Object.values(this.$root.stackList);
|
||||||
|
|
||||||
result = result.filter(monitor => {
|
result = result.filter(stack => {
|
||||||
// filter by search text
|
// filter by search text
|
||||||
// finds monitor name, tag name or tag value
|
// finds stack name, tag name or tag value
|
||||||
let searchTextMatch = true;
|
let searchTextMatch = true;
|
||||||
if (this.searchText !== "") {
|
if (this.searchText !== "") {
|
||||||
const loweredSearchText = this.searchText.toLowerCase();
|
const loweredSearchText = this.searchText.toLowerCase();
|
||||||
searchTextMatch =
|
searchTextMatch =
|
||||||
monitor.name.toLowerCase().includes(loweredSearchText)
|
stack.name.toLowerCase().includes(loweredSearchText)
|
||||||
|| monitor.tags.find(tag => tag.name.toLowerCase().includes(loweredSearchText)
|
|| stack.tags.find(tag => tag.name.toLowerCase().includes(loweredSearchText)
|
||||||
|| tag.value?.toLowerCase().includes(loweredSearchText));
|
|| tag.value?.toLowerCase().includes(loweredSearchText));
|
||||||
}
|
}
|
||||||
|
|
||||||
// filter by status
|
|
||||||
let statusMatch = true;
|
|
||||||
if (this.filterState.status != null && this.filterState.status.length > 0) {
|
|
||||||
if (monitor.id in this.$root.lastHeartbeatList && this.$root.lastHeartbeatList[monitor.id]) {
|
|
||||||
monitor.status = this.$root.lastHeartbeatList[monitor.id].status;
|
|
||||||
}
|
|
||||||
statusMatch = this.filterState.status.includes(monitor.status);
|
|
||||||
}
|
|
||||||
|
|
||||||
// filter by active
|
// filter by active
|
||||||
let activeMatch = true;
|
let activeMatch = true;
|
||||||
if (this.filterState.active != null && this.filterState.active.length > 0) {
|
if (this.filterState.active != null && this.filterState.active.length > 0) {
|
||||||
activeMatch = this.filterState.active.includes(monitor.active);
|
activeMatch = this.filterState.active.includes(stack.active);
|
||||||
}
|
}
|
||||||
|
|
||||||
// filter by tags
|
// filter by tags
|
||||||
let tagsMatch = true;
|
let tagsMatch = true;
|
||||||
if (this.filterState.tags != null && this.filterState.tags.length > 0) {
|
if (this.filterState.tags != null && this.filterState.tags.length > 0) {
|
||||||
tagsMatch = monitor.tags.map(tag => tag.tag_id) // convert to array of tag IDs
|
tagsMatch = stack.tags.map(tag => tag.tag_id) // convert to array of tag IDs
|
||||||
.filter(monitorTagId => this.filterState.tags.includes(monitorTagId)) // perform Array Intersaction between filter and monitor's tags
|
.filter(stackTagId => this.filterState.tags.includes(stackTagId)) // perform Array Intersaction between filter and stack's tags
|
||||||
.length > 0;
|
.length > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hide children if not filtering
|
return searchTextMatch && activeMatch && tagsMatch;
|
||||||
let showChild = true;
|
|
||||||
if (this.filterState.status == null && this.filterState.active == null && this.filterState.tags == null && this.searchText === "") {
|
|
||||||
if (monitor.parent !== null) {
|
|
||||||
showChild = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return searchTextMatch && statusMatch && activeMatch && tagsMatch && showChild;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Filter result by active state, weight and alphabetical
|
// Filter result by active state, weight and alphabetical
|
||||||
result.sort((m1, m2) => {
|
result.sort((m1, m2) => {
|
||||||
if (m1.active !== m2.active) {
|
|
||||||
if (m1.active === false) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m2.active === false) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m1.weight !== m2.weight) {
|
|
||||||
if (m1.weight > m2.weight) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m1.weight < m2.weight) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return m1.name.localeCompare(m2.name);
|
return m1.name.localeCompare(m2.name);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -203,8 +166,9 @@ export default {
|
|||||||
return document.body.classList.contains("dark");
|
return document.body.classList.contains("dark");
|
||||||
},
|
},
|
||||||
|
|
||||||
monitorListStyle() {
|
stackListStyle() {
|
||||||
let listHeaderHeight = 107;
|
//let listHeaderHeight = 107;
|
||||||
|
let listHeaderHeight = 60;
|
||||||
|
|
||||||
if (this.selectMode) {
|
if (this.selectMode) {
|
||||||
listHeaderHeight += 42;
|
listHeaderHeight += 42;
|
||||||
@ -215,8 +179,8 @@ export default {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
selectedMonitorCount() {
|
selectedStackCount() {
|
||||||
return Object.keys(this.selectedMonitors).length;
|
return Object.keys(this.selectedStacks).length;
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -229,8 +193,8 @@ export default {
|
|||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
searchText() {
|
searchText() {
|
||||||
for (let monitor of this.sortedMonitorList) {
|
for (let stack of this.sortedStackList) {
|
||||||
if (!this.selectedMonitors[monitor.id]) {
|
if (!this.selectedStacks[stack.id]) {
|
||||||
if (this.selectAll) {
|
if (this.selectAll) {
|
||||||
this.disableSelectAllWatcher = true;
|
this.disableSelectAllWatcher = true;
|
||||||
this.selectAll = false;
|
this.selectAll = false;
|
||||||
@ -241,11 +205,11 @@ export default {
|
|||||||
},
|
},
|
||||||
selectAll() {
|
selectAll() {
|
||||||
if (!this.disableSelectAllWatcher) {
|
if (!this.disableSelectAllWatcher) {
|
||||||
this.selectedMonitors = {};
|
this.selectedStacks = {};
|
||||||
|
|
||||||
if (this.selectAll) {
|
if (this.selectAll) {
|
||||||
this.sortedMonitorList.forEach((item) => {
|
this.sortedStackList.forEach((item) => {
|
||||||
this.selectedMonitors[item.id] = true;
|
this.selectedStacks[item.id] = true;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -255,7 +219,7 @@ export default {
|
|||||||
selectMode() {
|
selectMode() {
|
||||||
if (!this.selectMode) {
|
if (!this.selectMode) {
|
||||||
this.selectAll = false;
|
this.selectAll = false;
|
||||||
this.selectedMonitors = {};
|
this.selectedStacks = {};
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -286,7 +250,7 @@ export default {
|
|||||||
this.searchText = "";
|
this.searchText = "";
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Update the MonitorList Filter
|
* Update the StackList Filter
|
||||||
* @param {object} newFilter Object with new filter
|
* @param {object} newFilter Object with new filter
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
@ -294,28 +258,28 @@ export default {
|
|||||||
this.filterState = newFilter;
|
this.filterState = newFilter;
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Deselect a monitor
|
* Deselect a stack
|
||||||
* @param {number} id ID of monitor
|
* @param {number} id ID of stack
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
deselect(id) {
|
deselect(id) {
|
||||||
delete this.selectedMonitors[id];
|
delete this.selectedStacks[id];
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Select a monitor
|
* Select a stack
|
||||||
* @param {number} id ID of monitor
|
* @param {number} id ID of stack
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
select(id) {
|
select(id) {
|
||||||
this.selectedMonitors[id] = true;
|
this.selectedStacks[id] = true;
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Determine if monitor is selected
|
* Determine if stack is selected
|
||||||
* @param {number} id ID of monitor
|
* @param {number} id ID of stack
|
||||||
* @returns {bool} Is the monitor selected?
|
* @returns {bool} Is the stack selected?
|
||||||
*/
|
*/
|
||||||
isSelected(id) {
|
isSelected(id) {
|
||||||
return id in this.selectedMonitors;
|
return id in this.selectedStacks;
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Disable select mode and reset selection
|
* Disable select mode and reset selection
|
||||||
@ -323,7 +287,7 @@ export default {
|
|||||||
*/
|
*/
|
||||||
cancelSelectMode() {
|
cancelSelectMode() {
|
||||||
this.selectMode = false;
|
this.selectMode = false;
|
||||||
this.selectedMonitors = {};
|
this.selectedStacks = {};
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Show dialog to confirm pause
|
* Show dialog to confirm pause
|
||||||
@ -333,24 +297,24 @@ export default {
|
|||||||
this.$refs.confirmPause.show();
|
this.$refs.confirmPause.show();
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Pause each selected monitor
|
* Pause each selected stack
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
pauseSelected() {
|
pauseSelected() {
|
||||||
Object.keys(this.selectedMonitors)
|
Object.keys(this.selectedStacks)
|
||||||
.filter(id => this.$root.monitorList[id].active)
|
.filter(id => this.$root.stackList[id].active)
|
||||||
.forEach(id => this.$root.getSocket().emit("pauseMonitor", id, () => {}));
|
.forEach(id => this.$root.getSocket().emit("pauseStack", id, () => {}));
|
||||||
|
|
||||||
this.cancelSelectMode();
|
this.cancelSelectMode();
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Resume each selected monitor
|
* Resume each selected stack
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
resumeSelected() {
|
resumeSelected() {
|
||||||
Object.keys(this.selectedMonitors)
|
Object.keys(this.selectedStacks)
|
||||||
.filter(id => !this.$root.monitorList[id].active)
|
.filter(id => !this.$root.stackList[id].active)
|
||||||
.forEach(id => this.$root.getSocket().emit("resumeMonitor", id, () => {}));
|
.forEach(id => this.$root.getSocket().emit("resumeStack", id, () => {}));
|
||||||
|
|
||||||
this.cancelSelectMode();
|
this.cancelSelectMode();
|
||||||
},
|
},
|
||||||
@ -428,7 +392,7 @@ export default {
|
|||||||
max-width: 15em;
|
max-width: 15em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.monitor-item {
|
.stack-item {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,68 +7,44 @@
|
|||||||
class="form-check-input select-input"
|
class="form-check-input select-input"
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
:aria-label="$t('Check/Uncheck')"
|
:aria-label="$t('Check/Uncheck')"
|
||||||
:checked="isSelected(monitor.id)"
|
:checked="isSelected(stack.id)"
|
||||||
@click.stop="toggleSelection"
|
@click.stop="toggleSelection"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<router-link :to="monitorURL(monitor.id)" class="item" :class="{ 'disabled': ! monitor.active }">
|
<router-link :to="`/compose/${stack.name}`" class="item">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-9 col-md-8 small-padding" :class="{ 'monitor-item': $root.userHeartbeatBar == 'bottom' || $root.userHeartbeatBar == 'none' }">
|
<div class="col-9 col-md-8 small-padding">
|
||||||
<div class="info">
|
<div class="info">
|
||||||
<Uptime :monitor="monitor" type="24" :pill="true" />
|
<Uptime :stack="stack" type="24" :pill="true" />
|
||||||
<span v-if="hasChildren" class="collapse-padding" @click.prevent="changeCollapsed">
|
{{ stackName }}
|
||||||
<font-awesome-icon icon="chevron-down" class="animated" :class="{ collapsed: isCollapsed}" />
|
|
||||||
</span>
|
|
||||||
{{ monitorName }}
|
|
||||||
</div>
|
</div>
|
||||||
<div v-if="monitor.tags.length > 0" class="tags">
|
<div v-if="stack.tags.length > 0" class="tags">
|
||||||
<Tag v-for="tag in monitor.tags" :key="tag" :item="tag" :size="'sm'" />
|
<!--<Tag v-for="tag in stack.tags" :key="tag" :item="tag" :size="'sm'" />-->
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-show="$root.userHeartbeatBar == 'normal'" :key="$root.userHeartbeatBar" class="col-3 col-md-4">
|
|
||||||
<HeartbeatBar ref="heartbeatBar" size="small" :monitor-id="monitor.id" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="$root.userHeartbeatBar == 'bottom'" class="row">
|
|
||||||
<div class="col-12 bottom-style">
|
|
||||||
<HeartbeatBar ref="heartbeatBar" size="small" :monitor-id="monitor.id" />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</router-link>
|
</router-link>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<transition name="slide-fade-up">
|
|
||||||
<div v-if="!isCollapsed" class="childs">
|
|
||||||
<MonitorListItem
|
|
||||||
v-for="(item, index) in sortedChildMonitorList"
|
|
||||||
:key="index" :monitor="item"
|
|
||||||
:showPathName="showPathName"
|
|
||||||
:isSelectMode="isSelectMode"
|
|
||||||
:isSelected="isSelected"
|
|
||||||
:select="select"
|
|
||||||
:deselect="deselect"
|
|
||||||
:depth="depth + 1"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</transition>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
|
import Uptime from "./Uptime.vue";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
|
Uptime
|
||||||
|
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
/** Monitor this represents */
|
/** Stack this represents */
|
||||||
monitor: {
|
stack: {
|
||||||
type: Object,
|
type: Object,
|
||||||
default: null,
|
default: null,
|
||||||
},
|
},
|
||||||
/** Should the monitor name show it's parent */
|
/** Should the stack name show it's parent */
|
||||||
showPathName: {
|
showPathName: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
@ -78,22 +54,22 @@ export default {
|
|||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
/** How many ancestors are above this monitor */
|
/** How many ancestors are above this stack */
|
||||||
depth: {
|
depth: {
|
||||||
type: Number,
|
type: Number,
|
||||||
default: 0,
|
default: 0,
|
||||||
},
|
},
|
||||||
/** Callback to determine if monitor is selected */
|
/** Callback to determine if stack is selected */
|
||||||
isSelected: {
|
isSelected: {
|
||||||
type: Function,
|
type: Function,
|
||||||
default: () => {}
|
default: () => {}
|
||||||
},
|
},
|
||||||
/** Callback fired when monitor is selected */
|
/** Callback fired when stack is selected */
|
||||||
select: {
|
select: {
|
||||||
type: Function,
|
type: Function,
|
||||||
default: () => {}
|
default: () => {}
|
||||||
},
|
},
|
||||||
/** Callback fired when monitor is deselected */
|
/** Callback fired when stack is deselected */
|
||||||
deselect: {
|
deselect: {
|
||||||
type: Function,
|
type: Function,
|
||||||
default: () => {}
|
default: () => {}
|
||||||
@ -105,51 +81,16 @@ export default {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
sortedChildMonitorList() {
|
|
||||||
let result = Object.values(this.$root.monitorList);
|
|
||||||
|
|
||||||
result = result.filter(childMonitor => childMonitor.parent === this.monitor.id);
|
|
||||||
|
|
||||||
result.sort((m1, m2) => {
|
|
||||||
|
|
||||||
if (m1.active !== m2.active) {
|
|
||||||
if (m1.active === 0) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m2.active === 0) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m1.weight !== m2.weight) {
|
|
||||||
if (m1.weight > m2.weight) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m1.weight < m2.weight) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return m1.name.localeCompare(m2.name);
|
|
||||||
});
|
|
||||||
|
|
||||||
return result;
|
|
||||||
},
|
|
||||||
hasChildren() {
|
|
||||||
return this.sortedChildMonitorList.length > 0;
|
|
||||||
},
|
|
||||||
depthMargin() {
|
depthMargin() {
|
||||||
return {
|
return {
|
||||||
marginLeft: `${31 * this.depth}px`,
|
marginLeft: `${31 * this.depth}px`,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
monitorName() {
|
stackName() {
|
||||||
if (this.showPathName) {
|
if (this.showPathName) {
|
||||||
return this.monitor.pathName;
|
return this.stack.pathName;
|
||||||
} else {
|
} else {
|
||||||
return this.monitor.name;
|
return this.stack.name;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -161,28 +102,10 @@ export default {
|
|||||||
},
|
},
|
||||||
beforeMount() {
|
beforeMount() {
|
||||||
|
|
||||||
// Always unfold if monitor is accessed directly
|
|
||||||
if (this.monitor.childrenIDs.includes(parseInt(this.$route.params.id))) {
|
|
||||||
this.isCollapsed = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set collapsed value based on local storage
|
|
||||||
let storage = window.localStorage.getItem("monitorCollapsed");
|
|
||||||
if (storage === null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let storageObject = JSON.parse(storage);
|
|
||||||
if (storageObject[`monitor_${this.monitor.id}`] == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.isCollapsed = storageObject[`monitor_${this.monitor.id}`];
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
/**
|
/**
|
||||||
* Changes the collapsed value of the current monitor and saves
|
* Changes the collapsed value of the current stack and saves
|
||||||
* it to local storage
|
* it to local storage
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
@ -190,25 +113,25 @@ export default {
|
|||||||
this.isCollapsed = !this.isCollapsed;
|
this.isCollapsed = !this.isCollapsed;
|
||||||
|
|
||||||
// Save collapsed value into local storage
|
// Save collapsed value into local storage
|
||||||
let storage = window.localStorage.getItem("monitorCollapsed");
|
let storage = window.localStorage.getItem("stackCollapsed");
|
||||||
let storageObject = {};
|
let storageObject = {};
|
||||||
if (storage !== null) {
|
if (storage !== null) {
|
||||||
storageObject = JSON.parse(storage);
|
storageObject = JSON.parse(storage);
|
||||||
}
|
}
|
||||||
storageObject[`monitor_${this.monitor.id}`] = this.isCollapsed;
|
storageObject[`stack_${this.stack.id}`] = this.isCollapsed;
|
||||||
|
|
||||||
window.localStorage.setItem("monitorCollapsed", JSON.stringify(storageObject));
|
window.localStorage.setItem("stackCollapsed", JSON.stringify(storageObject));
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Toggle selection of monitor
|
* Toggle selection of stack
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
toggleSelection() {
|
toggleSelection() {
|
||||||
if (this.isSelected(this.monitor.id)) {
|
if (this.isSelected(this.stack.id)) {
|
||||||
this.deselect(this.monitor.id);
|
this.deselect(this.stack.id);
|
||||||
} else {
|
} else {
|
||||||
this.select(this.monitor.id);
|
this.select(this.stack.id);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -228,7 +151,7 @@ export default {
|
|||||||
padding-right: 2px !important;
|
padding-right: 2px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
// .monitor-item {
|
// .stack-item {
|
||||||
// width: 100%;
|
// width: 100%;
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
93
frontend/src/components/Terminal.vue
Normal file
93
frontend/src/components/Terminal.vue
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
<template>
|
||||||
|
<div class="shadow-box">
|
||||||
|
<div v-pre ref="terminal" class="main-terminal"></div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { Terminal } from "xterm";
|
||||||
|
import { WebLinksAddon } from "xterm-addon-web-links";
|
||||||
|
import { TERMINAL_COLS, TERMINAL_ROWS } from "../../../backend/util-common";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
/**
|
||||||
|
* @type {Terminal}
|
||||||
|
*/
|
||||||
|
terminal: null,
|
||||||
|
components: {
|
||||||
|
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
allowInput: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
rows: {
|
||||||
|
type: Number,
|
||||||
|
default: TERMINAL_ROWS,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
emits: [ "has-data" ],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
name: null,
|
||||||
|
first: true,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.terminal = new Terminal({
|
||||||
|
fontSize: 16,
|
||||||
|
fontFamily: "monospace",
|
||||||
|
cursorBlink: this.allowInput,
|
||||||
|
cols: TERMINAL_COLS,
|
||||||
|
rows: this.rows,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.terminal.loadAddon(new WebLinksAddon());
|
||||||
|
|
||||||
|
// Bind to a div
|
||||||
|
this.terminal.open(this.$refs.terminal);
|
||||||
|
this.terminal.focus();
|
||||||
|
|
||||||
|
// Notify parent component when data is received
|
||||||
|
this.terminal.onCursorMove(() => {
|
||||||
|
console.debug("onData triggered");
|
||||||
|
if (this.first) {
|
||||||
|
this.$emit("has-data");
|
||||||
|
this.first = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
unmounted() {
|
||||||
|
this.$root.unbindTerminal(this.name);
|
||||||
|
this.terminal.dispose();
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
bind(name) {
|
||||||
|
if (this.name) {
|
||||||
|
this.$root.unbindTerminal(this.name);
|
||||||
|
}
|
||||||
|
this.name = name;
|
||||||
|
this.$root.bindTerminal(this.name, this.terminal);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.main-terminal {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.terminal {
|
||||||
|
padding: 10px 15px;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
58
frontend/src/components/Uptime.vue
Normal file
58
frontend/src/components/Uptime.vue
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
<template>
|
||||||
|
<span :class="className" :title="title">{{ uptime }}</span>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
/** Monitor this represents */
|
||||||
|
monitor: {
|
||||||
|
type: Object,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
/** Type of monitor */
|
||||||
|
type: {
|
||||||
|
type: String,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
/** Is this a pill? */
|
||||||
|
pill: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
uptime() {
|
||||||
|
return this.$t("notAvailableShort");
|
||||||
|
},
|
||||||
|
|
||||||
|
color() {
|
||||||
|
return "secondary";
|
||||||
|
},
|
||||||
|
|
||||||
|
className() {
|
||||||
|
if (this.pill) {
|
||||||
|
return `badge rounded-pill bg-${this.color}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return "";
|
||||||
|
},
|
||||||
|
|
||||||
|
title() {
|
||||||
|
if (this.type === "720") {
|
||||||
|
return `30${this.$t("-day")}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return `24${this.$t("-hour")}`;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.badge {
|
||||||
|
min-width: 62px;
|
||||||
|
}
|
||||||
|
</style>
|
@ -50,7 +50,7 @@ import {
|
|||||||
faInfoCircle,
|
faInfoCircle,
|
||||||
faClone,
|
faClone,
|
||||||
faCertificate,
|
faCertificate,
|
||||||
faTerminal, faWarehouse, faHome,
|
faTerminal, faWarehouse, faHome, faRocket,
|
||||||
} from "@fortawesome/free-solid-svg-icons";
|
} from "@fortawesome/free-solid-svg-icons";
|
||||||
|
|
||||||
library.add(
|
library.add(
|
||||||
@ -101,6 +101,7 @@ library.add(
|
|||||||
faTerminal,
|
faTerminal,
|
||||||
faWarehouse,
|
faWarehouse,
|
||||||
faHome,
|
faHome,
|
||||||
|
faRocket,
|
||||||
);
|
);
|
||||||
|
|
||||||
export { FontAwesomeIcon };
|
export { FontAwesomeIcon };
|
||||||
|
@ -8,5 +8,15 @@
|
|||||||
"console": "Console",
|
"console": "Console",
|
||||||
"registry": "Registry",
|
"registry": "Registry",
|
||||||
"compose": "Compose",
|
"compose": "Compose",
|
||||||
"addFirstStackMsg": "Compose your first stack!"
|
"addFirstStackMsg": "Compose your first stack!",
|
||||||
|
"stackName" : "Stack Name",
|
||||||
|
"deployStack": "Deploy",
|
||||||
|
"deleteStack": "Delete",
|
||||||
|
"stopStack": "Stop",
|
||||||
|
"restartStack": "Restart",
|
||||||
|
"startStack": "Start",
|
||||||
|
"editStack": "Edit",
|
||||||
|
"discardStack": "Discard",
|
||||||
|
"saveStackDraft": "Save",
|
||||||
|
"notAvailableShort" : "N/A"
|
||||||
}
|
}
|
||||||
|
@ -3,20 +3,13 @@ import { Socket } from "socket.io-client";
|
|||||||
import { defineComponent } from "vue";
|
import { defineComponent } from "vue";
|
||||||
import jwtDecode from "jwt-decode";
|
import jwtDecode from "jwt-decode";
|
||||||
import { Terminal } from "xterm";
|
import { Terminal } from "xterm";
|
||||||
import { FitAddon } from "xterm-addon-fit";
|
|
||||||
import { WebLinksAddon } from "xterm-addon-web-links";
|
|
||||||
|
|
||||||
const terminal = new Terminal({
|
|
||||||
fontSize: 16,
|
|
||||||
fontFamily: "monospace",
|
|
||||||
cursorBlink: true,
|
|
||||||
});
|
|
||||||
terminal.loadAddon(new FitAddon());
|
|
||||||
terminal.loadAddon(new WebLinksAddon());
|
|
||||||
let terminalInputBuffer = "";
|
let terminalInputBuffer = "";
|
||||||
let cursorPosition = 0;
|
let cursorPosition = 0;
|
||||||
let socket : Socket;
|
let socket : Socket;
|
||||||
|
|
||||||
|
let terminalMap : Map<string, Terminal> = new Map();
|
||||||
|
|
||||||
function removeInput() {
|
function removeInput() {
|
||||||
const backspaceCount = terminalInputBuffer.length;
|
const backspaceCount = terminalInputBuffer.length;
|
||||||
const backspaces = "\b \b".repeat(backspaceCount);
|
const backspaces = "\b \b".repeat(backspaceCount);
|
||||||
@ -44,7 +37,6 @@ export default defineComponent({
|
|||||||
loggedIn: false,
|
loggedIn: false,
|
||||||
allowLoginDialog: false,
|
allowLoginDialog: false,
|
||||||
username: null,
|
username: null,
|
||||||
|
|
||||||
stackList: {},
|
stackList: {},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
@ -66,6 +58,7 @@ export default defineComponent({
|
|||||||
this.initSocketIO();
|
this.initSocketIO();
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
|
return;
|
||||||
terminal.onKey(e => {
|
terminal.onKey(e => {
|
||||||
const code = e.key.charCodeAt(0);
|
const code = e.key.charCodeAt(0);
|
||||||
console.debug("Encode: " + JSON.stringify(e.key));
|
console.debug("Encode: " + JSON.stringify(e.key));
|
||||||
@ -100,6 +93,7 @@ export default defineComponent({
|
|||||||
// TODO
|
// TODO
|
||||||
} else if (e.key === "\u0003") { // Ctrl + C
|
} else if (e.key === "\u0003") { // Ctrl + C
|
||||||
console.debug("Ctrl + C");
|
console.debug("Ctrl + C");
|
||||||
|
socket.emit("terminalInputRaw", e.key);
|
||||||
removeInput();
|
removeInput();
|
||||||
} else {
|
} else {
|
||||||
cursorPosition++;
|
cursorPosition++;
|
||||||
@ -131,7 +125,7 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
|
|
||||||
socket = io(url, {
|
socket = io(url, {
|
||||||
|
transports: [ "websocket", "polling" ]
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on("connect", () => {
|
socket.on("connect", () => {
|
||||||
@ -195,9 +189,20 @@ export default defineComponent({
|
|||||||
this.$router.push("/setup");
|
this.$router.push("/setup");
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on("commandOutput", (data) => {
|
socket.on("terminalWrite", (terminalName, data) => {
|
||||||
|
const terminal = terminalMap.get(terminalName);
|
||||||
|
if (!terminal) {
|
||||||
|
console.error("Terminal not found: " + terminalName);
|
||||||
|
return;
|
||||||
|
}
|
||||||
terminal.write(data);
|
terminal.write(data);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
socket.on("stackList", (res) => {
|
||||||
|
if (res.ok) {
|
||||||
|
this.stackList = res.stackList;
|
||||||
|
}
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -212,10 +217,6 @@ export default defineComponent({
|
|||||||
return socket;
|
return socket;
|
||||||
},
|
},
|
||||||
|
|
||||||
getTerminal() : Terminal {
|
|
||||||
return terminal;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get payload of JWT cookie
|
* Get payload of JWT cookie
|
||||||
* @returns {(object | undefined)} JWT payload
|
* @returns {(object | undefined)} JWT payload
|
||||||
@ -269,13 +270,23 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
|
|
||||||
afterLogin() {
|
afterLogin() {
|
||||||
terminal.clear();
|
|
||||||
|
},
|
||||||
|
|
||||||
|
bindTerminal(terminalName : string, terminal : Terminal) {
|
||||||
// Load terminal, get terminal screen
|
// Load terminal, get terminal screen
|
||||||
socket.emit("getTerminalBuffer", (res) => {
|
socket.emit("terminalJoin", terminalName, (res) => {
|
||||||
console.log("getTerminalBuffer");
|
if (res.ok) {
|
||||||
terminal.write(res.buffer);
|
terminal.write(res.buffer);
|
||||||
|
terminalMap.set(terminalName, terminal);
|
||||||
|
} else {
|
||||||
|
this.toastRes(res);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
unbindTerminal(terminalName : string) {
|
||||||
|
terminalMap.delete(terminalName);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -1,16 +1,224 @@
|
|||||||
<template>
|
<template>
|
||||||
<transition name="slide-fade" appear>
|
<transition name="slide-fade" appear>
|
||||||
<div>
|
<div>
|
||||||
|
<h1 v-if="isAdd" class="mb-3">Compose</h1>
|
||||||
|
<h1 v-else class="mb-3">Stack: {{ stack.name }}</h1>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<div class="btn-group" role="group">
|
||||||
|
<button v-if="isEditMode" class="btn btn-primary" :disabled="processing" @click="deployStack">
|
||||||
|
<font-awesome-icon icon="rocket" class="me-1" />
|
||||||
|
{{ $t("deployStack") }}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button v-if="isEditMode" class="btn btn-normal" :disabled="processing" @click="saveStack">
|
||||||
|
<font-awesome-icon icon="save" class="me-1" />
|
||||||
|
{{ $t("saveStackDraft") }}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button v-if="!isEditMode" class="btn btn-normal" :disabled="processing" @click="isEditMode = true">{{ $t("editStack") }}</button>
|
||||||
|
<button v-if="isEditMode && !isAdd" class="btn btn-normal" :disabled="processing" @click="discardStack">{{ $t("discardStack") }}</button>
|
||||||
|
<button v-if="!isEditMode" class="btn btn-primary" :disabled="processing">{{ $t("startStack") }}</button>
|
||||||
|
<button v-if="!isEditMode" class="btn btn-primary " :disabled="processing">{{ $t("restartStack") }}</button>
|
||||||
|
<button v-if="!isEditMode" class="btn btn-danger" :disabled="processing">{{ $t("stopStack") }}</button>
|
||||||
|
<button v-if="!isEditMode" class="btn btn-danger" :disabled="processing">{{ $t("deleteStack") }}</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Progress Terminal -->
|
||||||
|
<transition name="slide-fade" appear>
|
||||||
|
<Terminal
|
||||||
|
v-show="showProgressTerminal"
|
||||||
|
ref="progressTerminal"
|
||||||
|
:allow-input="false"
|
||||||
|
class="mb-3 terminal"
|
||||||
|
:rows="progressTerminalRows"
|
||||||
|
@has-data="showProgressTerminal = true"
|
||||||
|
></Terminal>
|
||||||
|
</transition>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<h4 class="mb-3">General</h4>
|
||||||
|
<div class="shadow-box big-padding mb-3">
|
||||||
|
<!-- Stack Name -->
|
||||||
|
<div v-if="isAdd" class="mb-3">
|
||||||
|
<label for="name" class="form-label">{{ $t("stackName") }}</label>
|
||||||
|
<input id="name" v-model="stack.name" type="text" class="form-control" required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h4 class="mb-3">Containers</h4>
|
||||||
|
<div class="shadow-box big-padding mb-3">
|
||||||
|
<div v-for="(service, name) in jsonConfig.services" :key="name">
|
||||||
|
{{ name }} {{ service }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col">
|
||||||
|
<h4 class="mb-3">compose.yaml</h4>
|
||||||
|
|
||||||
|
<div class="shadow-box mb-3">
|
||||||
|
<prism-editor v-model="stack.composeYAML" class="yaml-editor" :highlight="highlighter" line-numbers :readonly="!isEditMode" @input="yamlCodeChange"></prism-editor>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
{{ yamlError }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- <div class="shadow-box big-padding mb-3">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="name" class="form-label"> Search Templates</label>
|
||||||
|
<input id="name" v-model="name" type="text" class="form-control" placeholder="Search..." required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<prism-editor v-if="false" v-model="yamlConfig" class="yaml-editor" :highlight="highlighter" line-numbers @input="yamlCodeChange"></prism-editor>
|
||||||
|
</div>-->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</transition>
|
</transition>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script>
|
||||||
export default {
|
import { highlight, languages } from "prismjs/components/prism-core";
|
||||||
|
import { PrismEditor } from "vue-prism-editor";
|
||||||
|
import "prismjs/components/prism-yaml";
|
||||||
|
import * as yaml from "yaml";
|
||||||
|
|
||||||
|
import "prismjs/themes/prism-tomorrow.css";
|
||||||
|
import "vue-prism-editor/dist/prismeditor.min.css";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
|
||||||
|
import { getComposeTerminalName, PROGRESS_TERMINAL_ROWS } from "../../../backend/util-common";
|
||||||
|
|
||||||
|
const template = `version: "3.8"
|
||||||
|
services:
|
||||||
|
nginx:
|
||||||
|
image: nginx:latest
|
||||||
|
ports:
|
||||||
|
- "8080:8080"
|
||||||
|
`;
|
||||||
|
|
||||||
|
let yamlErrorTimeout = null;
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
FontAwesomeIcon,
|
||||||
|
PrismEditor,
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
jsonConfig: {},
|
||||||
|
yamlError: "",
|
||||||
|
processing: true,
|
||||||
|
showProgressTerminal: false,
|
||||||
|
progressTerminalRows: PROGRESS_TERMINAL_ROWS,
|
||||||
|
stack: {},
|
||||||
|
isEditMode: false,
|
||||||
|
submitted: false,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
isAdd() {
|
||||||
|
return this.$route.path === "/compose" && !this.submitted;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
"stack.composeYAML": {
|
||||||
|
handler() {
|
||||||
|
this.yamlCodeChange();
|
||||||
|
},
|
||||||
|
deep: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
if (this.isAdd) {
|
||||||
|
this.processing = false;
|
||||||
|
this.isEditMode = true;
|
||||||
|
|
||||||
|
// Default Values
|
||||||
|
this.stack = {
|
||||||
|
name: "",
|
||||||
|
composeYAML: template,
|
||||||
|
};
|
||||||
|
|
||||||
|
} else {
|
||||||
|
this.stack.name = this.$route.params.stackName;
|
||||||
|
this.loadStack();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
loadStack() {
|
||||||
|
this.$root.getSocket().emit("getStack", this.stack.name, (res) => {
|
||||||
|
if (res.ok) {
|
||||||
|
this.stack = res.stack;
|
||||||
|
this.processing = false;
|
||||||
|
} else {
|
||||||
|
this.$root.toastRes(res);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
deployStack() {
|
||||||
|
this.processing = true;
|
||||||
|
|
||||||
|
// Bind Terminal output
|
||||||
|
const terminalName = getComposeTerminalName(this.stack.name);
|
||||||
|
this.$refs.progressTerminal.bind(terminalName);
|
||||||
|
|
||||||
|
this.$root.getSocket().emit("deployStack", this.stack.name, this.stack.composeYAML, this.isAdd, (res) => {
|
||||||
|
this.processing = false;
|
||||||
|
this.$root.toastRes(res);
|
||||||
|
|
||||||
|
if (res.ok) {
|
||||||
|
this.$router.push("/compose/" + this.stack.name);
|
||||||
|
} else {
|
||||||
|
this.submitted = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
saveStack() {
|
||||||
|
this.processing = true;
|
||||||
|
|
||||||
|
this.$root.getSocket().emit("saveStack", this.stack.name, this.stack.composeYAML, this.isAdd, (res) => {
|
||||||
|
this.processing = false;
|
||||||
|
this.$root.toastRes(res);
|
||||||
|
|
||||||
|
if (res.ok) {
|
||||||
|
this.$router.push("/compose/" + this.stack.name);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
discardStack() {
|
||||||
|
this.loadStack();
|
||||||
|
this.isEditMode = false;
|
||||||
|
},
|
||||||
|
|
||||||
|
highlighter(code) {
|
||||||
|
return highlight(code, languages.yaml);
|
||||||
|
},
|
||||||
|
yamlCodeChange() {
|
||||||
|
try {
|
||||||
|
this.jsonConfig = yaml.parse(this.stack.composeYAML) ?? {};
|
||||||
|
this.yamlError = "";
|
||||||
|
} catch (e) {
|
||||||
|
clearTimeout(yamlErrorTimeout);
|
||||||
|
|
||||||
|
if (this.yamlError) {
|
||||||
|
this.yamlError = e.message;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
yamlErrorTimeout = setTimeout(() => {
|
||||||
|
this.yamlError = e.message;
|
||||||
|
}, 3000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
|
.terminal {
|
||||||
|
height: 200px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -3,9 +3,13 @@
|
|||||||
<div>
|
<div>
|
||||||
<h1 class="mb-3">Console</h1>
|
<h1 class="mb-3">Console</h1>
|
||||||
|
|
||||||
<div class="shadow-box">
|
<div>
|
||||||
<div v-pre id="terminal"></div>
|
<p>
|
||||||
|
Allowed commands: <code>docker</code>, <code>ls</code>, <code>cd</code>
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<Terminal :allow-input="true" class="terminal"></Terminal>
|
||||||
</div>
|
</div>
|
||||||
</transition>
|
</transition>
|
||||||
</template>
|
</template>
|
||||||
@ -14,10 +18,9 @@
|
|||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
|
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.$root.getTerminal().open(document.querySelector("#terminal"));
|
this.$root.terminalFit(50);
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
|
||||||
@ -27,9 +30,6 @@ export default {
|
|||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
.terminal {
|
.terminal {
|
||||||
font-family: monospace;
|
|
||||||
font-size: 18px;
|
|
||||||
padding: 10px 15px;
|
|
||||||
height: calc(100vh - 200px);
|
height: calc(100vh - 200px);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -1,14 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<transition name="slide-fade" appear>
|
|
||||||
<div>
|
|
||||||
</div>
|
|
||||||
</transition>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped lang="scss">
|
|
||||||
|
|
||||||
</style>
|
|
@ -4,8 +4,8 @@ import Layout from "./layouts/Layout.vue";
|
|||||||
import Setup from "./pages/Setup.vue";
|
import Setup from "./pages/Setup.vue";
|
||||||
import Dashboard from "./pages/Dashboard.vue";
|
import Dashboard from "./pages/Dashboard.vue";
|
||||||
import DashboardHome from "./pages/DashboardHome.vue";
|
import DashboardHome from "./pages/DashboardHome.vue";
|
||||||
import EditStack from "./pages/EditStack.vue";
|
|
||||||
import Console from "./pages/Console.vue";
|
import Console from "./pages/Console.vue";
|
||||||
|
import Compose from "./pages/Compose.vue";
|
||||||
|
|
||||||
const routes = [
|
const routes = [
|
||||||
{
|
{
|
||||||
@ -23,7 +23,13 @@ const routes = [
|
|||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: "/compose",
|
path: "/compose",
|
||||||
component: EditStack,
|
component: Compose,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/compose/:stackName",
|
||||||
|
name: "compose",
|
||||||
|
component: Compose,
|
||||||
|
props: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
]
|
]
|
||||||
|
@ -377,7 +377,7 @@ optgroup {
|
|||||||
color: $dark-font-color;
|
color: $dark-font-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
.monitor-list {
|
.stack-list {
|
||||||
.item {
|
.item {
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: $dark-bg2;
|
background-color: $dark-bg2;
|
||||||
@ -474,7 +474,7 @@ optgroup {
|
|||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.monitor-list {
|
.stack-list {
|
||||||
&.scrollbar {
|
&.scrollbar {
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
@ -653,12 +653,28 @@ $shadow-box-padding: 20px;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#terminal {
|
.main-terminal {
|
||||||
.xterm-viewport {
|
.xterm-viewport {
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
background-color: $dark-bg !important;
|
background-color: $dark-bg !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
code {
|
||||||
|
padding: .2em .4em;
|
||||||
|
margin: 0;
|
||||||
|
font-size: 85%;
|
||||||
|
white-space: break-spaces;
|
||||||
|
background-color: rgba(239, 239, 239, 0.15);
|
||||||
|
|
||||||
|
border-radius: 6px;
|
||||||
|
font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
||||||
|
color: black;
|
||||||
|
|
||||||
|
.dark & {
|
||||||
|
color: $dark-font-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Localization
|
// Localization
|
||||||
@import "localization.scss";
|
@import "localization.scss";
|
||||||
|
@ -158,7 +158,7 @@ export function colorOptions(self) {
|
|||||||
export function loadToastSettings() {
|
export function loadToastSettings() {
|
||||||
return {
|
return {
|
||||||
position: POSITION.BOTTOM_RIGHT,
|
position: POSITION.BOTTOM_RIGHT,
|
||||||
containerClassName: "toast-container mb-5",
|
containerClassName: "toast-container",
|
||||||
showCloseButtonOnHover: true,
|
showCloseButtonOnHover: true,
|
||||||
|
|
||||||
filterBeforeCreate: (toast, toasts) => {
|
filterBeforeCreate: (toast, toasts) => {
|
||||||
|
@ -14,7 +14,7 @@ export default defineConfig({
|
|||||||
},
|
},
|
||||||
root: "./frontend",
|
root: "./frontend",
|
||||||
build: {
|
build: {
|
||||||
outDir: "../dist",
|
outDir: "../frontend-dist",
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
vue(),
|
vue(),
|
||||||
|
31
package.json
31
package.json
@ -5,27 +5,33 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"fmt": "eslint \"**/*.{ts,vue}\" --fix",
|
"fmt": "eslint \"**/*.{ts,vue}\" --fix",
|
||||||
"lint": "eslint \"**/*.{ts,vue}\"",
|
"lint": "eslint \"**/*.{ts,vue}\"",
|
||||||
|
"start": "tsx ./backend/index.ts",
|
||||||
"dev:backend": "cross-env NODE_ENV=development tsx watch ./backend/index.ts",
|
"dev:backend": "cross-env NODE_ENV=development tsx watch ./backend/index.ts",
|
||||||
"dev:frontend": "cross-env NODE_ENV=development vite --host --config ./frontend/vite.config.ts",
|
"dev:frontend": "cross-env NODE_ENV=development vite --host --config ./frontend/vite.config.ts",
|
||||||
"build:frontend": "vite build --config ./frontend/vite.config.ts"
|
"build:frontend": "vite build --config ./frontend/vite.config.ts",
|
||||||
|
"build:docker-base": "docker build -t louislam/dockge:base -f ./docker/Base.Dockerfile .",
|
||||||
|
"build:docker": "pnpm run build:frontend && docker build -t louislam/dockge:latest -f ./docker/Dockerfile .",
|
||||||
|
"start-docker": "docker run --rm -p 5001:5001 --name dockge louislam/dockge:latest"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@homebridge/node-pty-prebuilt-multiarch": "~0.11.7",
|
||||||
"@louislam/sqlite3": "~15.1.6",
|
"@louislam/sqlite3": "~15.1.6",
|
||||||
"bcryptjs": "^2.4.3",
|
"bcryptjs": "~2.4.3",
|
||||||
"check-password-strength": "^2.0.7",
|
"check-password-strength": "~2.0.7",
|
||||||
|
"command-exists": "^1.2.9",
|
||||||
"compare-versions": "~6.1.0",
|
"compare-versions": "~6.1.0",
|
||||||
"dayjs": "^1.11.10",
|
"dayjs": "^1.11.10",
|
||||||
"express": "~4.18.2",
|
"express": "~4.18.2",
|
||||||
"jsonwebtoken": "^9.0.2",
|
"express-static-gzip": "~2.1.7",
|
||||||
"jwt-decode": "^3.1.2",
|
"jsonwebtoken": "~9.0.2",
|
||||||
|
"jwt-decode": "~3.1.2",
|
||||||
"knex": "~2.5.1",
|
"knex": "~2.5.1",
|
||||||
"limiter-es6-compat": "~2.1.2",
|
"limiter-es6-compat": "~2.1.2",
|
||||||
"mysql2": "^3.6.2",
|
"mysql2": "^3.6.2",
|
||||||
"node-pty": "^1.0.0",
|
|
||||||
"redbean-node": "0.3.1",
|
"redbean-node": "0.3.1",
|
||||||
"socket.io": "~4.7.2",
|
"socket.io": "~4.7.2",
|
||||||
"socket.io-client": "~4.7.2",
|
"socket.io-client": "~4.7.2",
|
||||||
"timezones-list": "^3.0.2",
|
"timezones-list": "~3.0.2",
|
||||||
"ts-command-line-args": "~2.5.1",
|
"ts-command-line-args": "~2.5.1",
|
||||||
"tsx": "~3.14.0",
|
"tsx": "~3.14.0",
|
||||||
"type-fest": "~4.3.3",
|
"type-fest": "~4.3.3",
|
||||||
@ -39,7 +45,9 @@
|
|||||||
"@fortawesome/free-regular-svg-icons": "6.4.2",
|
"@fortawesome/free-regular-svg-icons": "6.4.2",
|
||||||
"@fortawesome/free-solid-svg-icons": "6.4.2",
|
"@fortawesome/free-solid-svg-icons": "6.4.2",
|
||||||
"@fortawesome/vue-fontawesome": "3.0.3",
|
"@fortawesome/vue-fontawesome": "3.0.3",
|
||||||
|
"@types/command-exists": "~1.2.2",
|
||||||
"@types/express": "~4.17.20",
|
"@types/express": "~4.17.20",
|
||||||
|
"@types/jsonwebtoken": "~9.0.4",
|
||||||
"@typescript-eslint/eslint-plugin": "~6.8.0",
|
"@typescript-eslint/eslint-plugin": "~6.8.0",
|
||||||
"@typescript-eslint/parser": "~6.8.0",
|
"@typescript-eslint/parser": "~6.8.0",
|
||||||
"@vitejs/plugin-vue": "~4.3.4",
|
"@vitejs/plugin-vue": "~4.3.4",
|
||||||
@ -49,6 +57,9 @@
|
|||||||
"eslint": "~8.50.0",
|
"eslint": "~8.50.0",
|
||||||
"eslint-plugin-jsdoc": "~46.8.2",
|
"eslint-plugin-jsdoc": "~46.8.2",
|
||||||
"eslint-plugin-vue": "~9.17.0",
|
"eslint-plugin-vue": "~9.17.0",
|
||||||
|
"monaco-editor": "^0.44.0",
|
||||||
|
"monaco-yaml": "^5.1.0",
|
||||||
|
"prismjs": "~1.29.0",
|
||||||
"sass": "~1.68.0",
|
"sass": "~1.68.0",
|
||||||
"typescript": "~5.2.2",
|
"typescript": "~5.2.2",
|
||||||
"unplugin-vue-components": "^0.25.2",
|
"unplugin-vue-components": "^0.25.2",
|
||||||
@ -57,10 +68,10 @@
|
|||||||
"vue": "~3.3.6",
|
"vue": "~3.3.6",
|
||||||
"vue-eslint-parser": "^9.3.2",
|
"vue-eslint-parser": "^9.3.2",
|
||||||
"vue-i18n": "~9.5.0",
|
"vue-i18n": "~9.5.0",
|
||||||
|
"vue-prism-editor": "~2.0.0-alpha.2",
|
||||||
"vue-router": "~4.2.5",
|
"vue-router": "~4.2.5",
|
||||||
"vue-toastification": "~2.0.0-rc.5",
|
"vue-toastification": "~2.0.0-rc.5",
|
||||||
"xterm": "^5.3.0",
|
"xterm": "~5.3.0",
|
||||||
"xterm-addon-fit": "^0.8.0",
|
"xterm-addon-web-links": "~0.9.0"
|
||||||
"xterm-addon-web-links": "^0.9.0"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
353
pnpm-lock.yaml
353
pnpm-lock.yaml
@ -5,15 +5,21 @@ settings:
|
|||||||
excludeLinksFromLockfile: false
|
excludeLinksFromLockfile: false
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
|
'@homebridge/node-pty-prebuilt-multiarch':
|
||||||
|
specifier: ~0.11.7
|
||||||
|
version: 0.11.7
|
||||||
'@louislam/sqlite3':
|
'@louislam/sqlite3':
|
||||||
specifier: ~15.1.6
|
specifier: ~15.1.6
|
||||||
version: 15.1.6
|
version: 15.1.6
|
||||||
bcryptjs:
|
bcryptjs:
|
||||||
specifier: ^2.4.3
|
specifier: ~2.4.3
|
||||||
version: 2.4.3
|
version: 2.4.3
|
||||||
check-password-strength:
|
check-password-strength:
|
||||||
specifier: ^2.0.7
|
specifier: ~2.0.7
|
||||||
version: 2.0.7
|
version: 2.0.7
|
||||||
|
command-exists:
|
||||||
|
specifier: ^1.2.9
|
||||||
|
version: 1.2.9
|
||||||
compare-versions:
|
compare-versions:
|
||||||
specifier: ~6.1.0
|
specifier: ~6.1.0
|
||||||
version: 6.1.0
|
version: 6.1.0
|
||||||
@ -23,11 +29,14 @@ dependencies:
|
|||||||
express:
|
express:
|
||||||
specifier: ~4.18.2
|
specifier: ~4.18.2
|
||||||
version: 4.18.2
|
version: 4.18.2
|
||||||
|
express-static-gzip:
|
||||||
|
specifier: ~2.1.7
|
||||||
|
version: 2.1.7
|
||||||
jsonwebtoken:
|
jsonwebtoken:
|
||||||
specifier: ^9.0.2
|
specifier: ~9.0.2
|
||||||
version: 9.0.2
|
version: 9.0.2
|
||||||
jwt-decode:
|
jwt-decode:
|
||||||
specifier: ^3.1.2
|
specifier: ~3.1.2
|
||||||
version: 3.1.2
|
version: 3.1.2
|
||||||
knex:
|
knex:
|
||||||
specifier: ~2.5.1
|
specifier: ~2.5.1
|
||||||
@ -38,9 +47,6 @@ dependencies:
|
|||||||
mysql2:
|
mysql2:
|
||||||
specifier: ^3.6.2
|
specifier: ^3.6.2
|
||||||
version: 3.6.2
|
version: 3.6.2
|
||||||
node-pty:
|
|
||||||
specifier: ^1.0.0
|
|
||||||
version: 1.0.0
|
|
||||||
redbean-node:
|
redbean-node:
|
||||||
specifier: 0.3.1
|
specifier: 0.3.1
|
||||||
version: 0.3.1(mysql2@3.6.2)
|
version: 0.3.1(mysql2@3.6.2)
|
||||||
@ -51,7 +57,7 @@ dependencies:
|
|||||||
specifier: ~4.7.2
|
specifier: ~4.7.2
|
||||||
version: 4.7.2
|
version: 4.7.2
|
||||||
timezones-list:
|
timezones-list:
|
||||||
specifier: ^3.0.2
|
specifier: ~3.0.2
|
||||||
version: 3.0.2
|
version: 3.0.2
|
||||||
ts-command-line-args:
|
ts-command-line-args:
|
||||||
specifier: ~2.5.1
|
specifier: ~2.5.1
|
||||||
@ -84,9 +90,15 @@ devDependencies:
|
|||||||
'@fortawesome/vue-fontawesome':
|
'@fortawesome/vue-fontawesome':
|
||||||
specifier: 3.0.3
|
specifier: 3.0.3
|
||||||
version: 3.0.3(@fortawesome/fontawesome-svg-core@6.4.2)(vue@3.3.6)
|
version: 3.0.3(@fortawesome/fontawesome-svg-core@6.4.2)(vue@3.3.6)
|
||||||
|
'@types/command-exists':
|
||||||
|
specifier: ~1.2.2
|
||||||
|
version: 1.2.2
|
||||||
'@types/express':
|
'@types/express':
|
||||||
specifier: ~4.17.20
|
specifier: ~4.17.20
|
||||||
version: 4.17.20
|
version: 4.17.20
|
||||||
|
'@types/jsonwebtoken':
|
||||||
|
specifier: ~9.0.4
|
||||||
|
version: 9.0.4
|
||||||
'@typescript-eslint/eslint-plugin':
|
'@typescript-eslint/eslint-plugin':
|
||||||
specifier: ~6.8.0
|
specifier: ~6.8.0
|
||||||
version: 6.8.0(@typescript-eslint/parser@6.8.0)(eslint@8.50.0)(typescript@5.2.2)
|
version: 6.8.0(@typescript-eslint/parser@6.8.0)(eslint@8.50.0)(typescript@5.2.2)
|
||||||
@ -114,6 +126,15 @@ devDependencies:
|
|||||||
eslint-plugin-vue:
|
eslint-plugin-vue:
|
||||||
specifier: ~9.17.0
|
specifier: ~9.17.0
|
||||||
version: 9.17.0(eslint@8.50.0)
|
version: 9.17.0(eslint@8.50.0)
|
||||||
|
monaco-editor:
|
||||||
|
specifier: ^0.44.0
|
||||||
|
version: 0.44.0
|
||||||
|
monaco-yaml:
|
||||||
|
specifier: ^5.1.0
|
||||||
|
version: 5.1.0(monaco-editor@0.44.0)
|
||||||
|
prismjs:
|
||||||
|
specifier: ~1.29.0
|
||||||
|
version: 1.29.0
|
||||||
sass:
|
sass:
|
||||||
specifier: ~1.68.0
|
specifier: ~1.68.0
|
||||||
version: 1.68.0
|
version: 1.68.0
|
||||||
@ -138,6 +159,9 @@ devDependencies:
|
|||||||
vue-i18n:
|
vue-i18n:
|
||||||
specifier: ~9.5.0
|
specifier: ~9.5.0
|
||||||
version: 9.5.0(vue@3.3.6)
|
version: 9.5.0(vue@3.3.6)
|
||||||
|
vue-prism-editor:
|
||||||
|
specifier: ~2.0.0-alpha.2
|
||||||
|
version: 2.0.0-alpha.2(vue@3.3.6)
|
||||||
vue-router:
|
vue-router:
|
||||||
specifier: ~4.2.5
|
specifier: ~4.2.5
|
||||||
version: 4.2.5(vue@3.3.6)
|
version: 4.2.5(vue@3.3.6)
|
||||||
@ -145,13 +169,10 @@ devDependencies:
|
|||||||
specifier: ~2.0.0-rc.5
|
specifier: ~2.0.0-rc.5
|
||||||
version: 2.0.0-rc.5(vue@3.3.6)
|
version: 2.0.0-rc.5(vue@3.3.6)
|
||||||
xterm:
|
xterm:
|
||||||
specifier: ^5.3.0
|
specifier: ~5.3.0
|
||||||
version: 5.3.0
|
version: 5.3.0
|
||||||
xterm-addon-fit:
|
|
||||||
specifier: ^0.8.0
|
|
||||||
version: 0.8.0(xterm@5.3.0)
|
|
||||||
xterm-addon-web-links:
|
xterm-addon-web-links:
|
||||||
specifier: ^0.9.0
|
specifier: ~0.9.0
|
||||||
version: 0.9.0(xterm@5.3.0)
|
version: 0.9.0(xterm@5.3.0)
|
||||||
|
|
||||||
packages:
|
packages:
|
||||||
@ -487,6 +508,14 @@ packages:
|
|||||||
dev: false
|
dev: false
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
/@homebridge/node-pty-prebuilt-multiarch@0.11.7:
|
||||||
|
resolution: {integrity: sha512-aaw66RDwHZ2Xs821U6hwEl2wPDyv9PAWEzNxS32YSRaoYmbie1AREehAfjbASGgpub+7d+3l98xft3oCCUKhSw==}
|
||||||
|
requiresBuild: true
|
||||||
|
dependencies:
|
||||||
|
nan: 2.18.0
|
||||||
|
prebuild-install: 7.1.1
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@humanwhocodes/config-array@0.11.13:
|
/@humanwhocodes/config-array@0.11.13:
|
||||||
resolution: {integrity: sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==}
|
resolution: {integrity: sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==}
|
||||||
engines: {node: '>=10.10.0'}
|
engines: {node: '>=10.10.0'}
|
||||||
@ -777,6 +806,10 @@ packages:
|
|||||||
'@types/node': 20.8.7
|
'@types/node': 20.8.7
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/@types/command-exists@1.2.2:
|
||||||
|
resolution: {integrity: sha512-1qKPTkjLmghE5C7UUHXGcLaG8MNftchOcLAIryUXNKahRO5beS+iJ9rIL8XD4+B8K2phjYUsPQDox1FRX4KMTQ==}
|
||||||
|
dev: true
|
||||||
|
|
||||||
/@types/connect@3.4.37:
|
/@types/connect@3.4.37:
|
||||||
resolution: {integrity: sha512-zBUSRqkfZ59OcwXon4HVxhx5oWCJmc0OtBTK05M+p0dYjgN6iTwIL2T/WbsQZrEsdnwaF9cWQ+azOnpPvIqY3Q==}
|
resolution: {integrity: sha512-zBUSRqkfZ59OcwXon4HVxhx5oWCJmc0OtBTK05M+p0dYjgN6iTwIL2T/WbsQZrEsdnwaF9cWQ+azOnpPvIqY3Q==}
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -823,6 +856,12 @@ packages:
|
|||||||
resolution: {integrity: sha512-U3PUjAudAdJBeC2pgN8uTIKgxrb4nlDF3SF0++EldXQvQBGkpFZMSnwQiIoDU77tv45VgNkl/L4ouD+rEomujw==}
|
resolution: {integrity: sha512-U3PUjAudAdJBeC2pgN8uTIKgxrb4nlDF3SF0++EldXQvQBGkpFZMSnwQiIoDU77tv45VgNkl/L4ouD+rEomujw==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/@types/jsonwebtoken@9.0.4:
|
||||||
|
resolution: {integrity: sha512-8UYapdmR0QlxgvJmyE8lP7guxD0UGVMfknsdtCFZh4ovShdBl3iOI4zdvqBHrB/IS+xUj3PSx73Qkey1fhWz+g==}
|
||||||
|
dependencies:
|
||||||
|
'@types/node': 20.8.7
|
||||||
|
dev: true
|
||||||
|
|
||||||
/@types/mime@1.3.4:
|
/@types/mime@1.3.4:
|
||||||
resolution: {integrity: sha512-1Gjee59G25MrQGk8bsNvC6fxNiRgUlGn2wlhGf95a59DrprnnHk80FIMMFG9XHMdrfsuA119ht06QPDXA1Z7tw==}
|
resolution: {integrity: sha512-1Gjee59G25MrQGk8bsNvC6fxNiRgUlGn2wlhGf95a59DrprnnHk80FIMMFG9XHMdrfsuA119ht06QPDXA1Z7tw==}
|
||||||
dev: true
|
dev: true
|
||||||
@ -1363,6 +1402,10 @@ packages:
|
|||||||
/balanced-match@1.0.2:
|
/balanced-match@1.0.2:
|
||||||
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
|
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
|
||||||
|
|
||||||
|
/base64-js@1.5.1:
|
||||||
|
resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/base64id@2.0.0:
|
/base64id@2.0.0:
|
||||||
resolution: {integrity: sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==}
|
resolution: {integrity: sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==}
|
||||||
engines: {node: ^4.5.0 || >= 5.9}
|
engines: {node: ^4.5.0 || >= 5.9}
|
||||||
@ -1383,6 +1426,14 @@ packages:
|
|||||||
resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==}
|
resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
|
/bl@4.1.0:
|
||||||
|
resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==}
|
||||||
|
dependencies:
|
||||||
|
buffer: 5.7.1
|
||||||
|
inherits: 2.0.4
|
||||||
|
readable-stream: 3.6.2
|
||||||
|
dev: false
|
||||||
|
|
||||||
/blessed@0.1.81:
|
/blessed@0.1.81:
|
||||||
resolution: {integrity: sha512-LoF5gae+hlmfORcG1M5+5XZi4LBmvlXTzwJWzUlPryN/SJdSflZvROM2TwkT0GMpq7oqT48NRd4GS7BiVBc5OQ==}
|
resolution: {integrity: sha512-LoF5gae+hlmfORcG1M5+5XZi4LBmvlXTzwJWzUlPryN/SJdSflZvROM2TwkT0GMpq7oqT48NRd4GS7BiVBc5OQ==}
|
||||||
engines: {node: '>= 0.8.0'}
|
engines: {node: '>= 0.8.0'}
|
||||||
@ -1466,6 +1517,13 @@ packages:
|
|||||||
resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
|
resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/buffer@5.7.1:
|
||||||
|
resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==}
|
||||||
|
dependencies:
|
||||||
|
base64-js: 1.5.1
|
||||||
|
ieee754: 1.2.1
|
||||||
|
dev: false
|
||||||
|
|
||||||
/builtin-modules@3.3.0:
|
/builtin-modules@3.3.0:
|
||||||
resolution: {integrity: sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==}
|
resolution: {integrity: sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
@ -1567,6 +1625,10 @@ packages:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
fsevents: 2.3.3
|
fsevents: 2.3.3
|
||||||
|
|
||||||
|
/chownr@1.1.4:
|
||||||
|
resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/chownr@2.0.0:
|
/chownr@2.0.0:
|
||||||
resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==}
|
resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
@ -1616,6 +1678,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==}
|
resolution: {integrity: sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/command-exists@1.2.9:
|
||||||
|
resolution: {integrity: sha512-LTQ/SGc+s0Xc0Fu5WaKnR0YiygZkm9eKFvyS+fRsU7/ZWFF8ykFM6Pc9aCVf1+xasOOZpO3BAVgVrKvsqKHV7w==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/command-line-args@5.2.1:
|
/command-line-args@5.2.1:
|
||||||
resolution: {integrity: sha512-H4UfQhZyakIjC74I9d34fGYDwk3XpSr17QhEd0Q3I9Xq1CETHo4Hcuo87WyWHpAF1aSLjLRf5lD9ZGX2qStUvg==}
|
resolution: {integrity: sha512-H4UfQhZyakIjC74I9d34fGYDwk3XpSr17QhEd0Q3I9Xq1CETHo4Hcuo87WyWHpAF1aSLjLRf5lD9ZGX2qStUvg==}
|
||||||
engines: {node: '>=4.0.0'}
|
engines: {node: '>=4.0.0'}
|
||||||
@ -1801,6 +1867,13 @@ packages:
|
|||||||
dependencies:
|
dependencies:
|
||||||
ms: 2.1.2
|
ms: 2.1.2
|
||||||
|
|
||||||
|
/decompress-response@6.0.0:
|
||||||
|
resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==}
|
||||||
|
engines: {node: '>=10'}
|
||||||
|
dependencies:
|
||||||
|
mimic-response: 3.1.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
/deep-extend@0.6.0:
|
/deep-extend@0.6.0:
|
||||||
resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==}
|
resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==}
|
||||||
engines: {node: '>=4.0.0'}
|
engines: {node: '>=4.0.0'}
|
||||||
@ -1912,6 +1985,12 @@ packages:
|
|||||||
dev: false
|
dev: false
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
/end-of-stream@1.4.4:
|
||||||
|
resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==}
|
||||||
|
dependencies:
|
||||||
|
once: 1.4.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
/engine.io-client@6.5.2:
|
/engine.io-client@6.5.2:
|
||||||
resolution: {integrity: sha512-CQZqbrpEYnrpGqC07a9dJDz4gePZUgTPMU3NKJPSeQOyw27Tst4Pl3FemKoFGAlHzgZmKjoRmiJvbWfhCXUlIg==}
|
resolution: {integrity: sha512-CQZqbrpEYnrpGqC07a9dJDz4gePZUgTPMU3NKJPSeQOyw27Tst4Pl3FemKoFGAlHzgZmKjoRmiJvbWfhCXUlIg==}
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -2202,6 +2281,19 @@ packages:
|
|||||||
dev: false
|
dev: false
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
/expand-template@2.0.3:
|
||||||
|
resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==}
|
||||||
|
engines: {node: '>=6'}
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/express-static-gzip@2.1.7:
|
||||||
|
resolution: {integrity: sha512-QOCZUC+lhPPCjIJKpQGu1Oa61Axg9Mq09Qvit8Of7kzpMuwDeMSqjjQteQS3OVw/GkENBoSBheuQDWPlngImvw==}
|
||||||
|
dependencies:
|
||||||
|
serve-static: 1.15.0
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
|
dev: false
|
||||||
|
|
||||||
/express@4.18.2:
|
/express@4.18.2:
|
||||||
resolution: {integrity: sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==}
|
resolution: {integrity: sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==}
|
||||||
engines: {node: '>= 0.10.0'}
|
engines: {node: '>= 0.10.0'}
|
||||||
@ -2370,6 +2462,10 @@ packages:
|
|||||||
engines: {node: '>= 0.6'}
|
engines: {node: '>= 0.6'}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/fs-constants@1.0.0:
|
||||||
|
resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/fs-extra@10.1.0:
|
/fs-extra@10.1.0:
|
||||||
resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==}
|
resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
@ -2505,6 +2601,10 @@ packages:
|
|||||||
dev: false
|
dev: false
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
/github-from-package@0.0.0:
|
||||||
|
resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/glob-parent@5.1.2:
|
/glob-parent@5.1.2:
|
||||||
resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
|
resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
|
||||||
engines: {node: '>= 6'}
|
engines: {node: '>= 6'}
|
||||||
@ -2695,6 +2795,10 @@ packages:
|
|||||||
safer-buffer: 2.1.2
|
safer-buffer: 2.1.2
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/ieee754@1.2.1:
|
||||||
|
resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/ignore@5.2.4:
|
/ignore@5.2.4:
|
||||||
resolution: {integrity: sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==}
|
resolution: {integrity: sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==}
|
||||||
engines: {node: '>= 4'}
|
engines: {node: '>= 4'}
|
||||||
@ -2743,7 +2847,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==}
|
resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==}
|
||||||
requiresBuild: true
|
requiresBuild: true
|
||||||
dev: false
|
dev: false
|
||||||
optional: true
|
|
||||||
|
|
||||||
/interpret@2.2.0:
|
/interpret@2.2.0:
|
||||||
resolution: {integrity: sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw==}
|
resolution: {integrity: sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw==}
|
||||||
@ -2874,6 +2977,10 @@ packages:
|
|||||||
dev: false
|
dev: false
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
/jsonc-parser@3.2.0:
|
||||||
|
resolution: {integrity: sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==}
|
||||||
|
dev: true
|
||||||
|
|
||||||
/jsonfile@4.0.0:
|
/jsonfile@4.0.0:
|
||||||
resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==}
|
resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==}
|
||||||
requiresBuild: true
|
requiresBuild: true
|
||||||
@ -3219,6 +3326,11 @@ packages:
|
|||||||
hasBin: true
|
hasBin: true
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/mimic-response@3.1.0:
|
||||||
|
resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==}
|
||||||
|
engines: {node: '>=10'}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/minimatch@3.1.2:
|
/minimatch@3.1.2:
|
||||||
resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
|
resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -3230,6 +3342,10 @@ packages:
|
|||||||
dependencies:
|
dependencies:
|
||||||
brace-expansion: 2.0.1
|
brace-expansion: 2.0.1
|
||||||
|
|
||||||
|
/minimist@1.2.8:
|
||||||
|
resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/minipass-collect@1.0.2:
|
/minipass-collect@1.0.2:
|
||||||
resolution: {integrity: sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==}
|
resolution: {integrity: sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==}
|
||||||
engines: {node: '>= 8'}
|
engines: {node: '>= 8'}
|
||||||
@ -3304,6 +3420,10 @@ packages:
|
|||||||
yallist: 4.0.0
|
yallist: 4.0.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/mkdirp-classic@0.5.3:
|
||||||
|
resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/mkdirp@1.0.4:
|
/mkdirp@1.0.4:
|
||||||
resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==}
|
resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
@ -3316,6 +3436,57 @@ packages:
|
|||||||
dev: false
|
dev: false
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
/monaco-editor@0.44.0:
|
||||||
|
resolution: {integrity: sha512-5SmjNStN6bSuSE5WPT2ZV+iYn1/yI9sd4Igtk23ChvqB7kDk9lZbB9F5frsuvpB+2njdIeGGFf2G4gbE6rCC9Q==}
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/monaco-languageserver-types@0.2.3:
|
||||||
|
resolution: {integrity: sha512-QyV5R7s+rJ87bX1sRioMJZULWiTnMp0Vm+RLILgMEL0SqWuBsQBSW0EZunr4xMZhv6Qun3UZNCN4JrCCLURcgQ==}
|
||||||
|
dependencies:
|
||||||
|
monaco-types: 0.1.0
|
||||||
|
vscode-languageserver-protocol: 3.17.5
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/monaco-marker-data-provider@1.1.1(monaco-editor@0.44.0):
|
||||||
|
resolution: {integrity: sha512-PGB7TJSZE5tmHzkxv/OEwK2RGNC2A7dcq4JRJnnj31CUAsfmw0Gl+1QTrH0W0deKhcQmQM0YVPaqgQ+0wCt8Mg==}
|
||||||
|
peerDependencies:
|
||||||
|
monaco-editor: '>=0.30.0'
|
||||||
|
dependencies:
|
||||||
|
monaco-editor: 0.44.0
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/monaco-types@0.1.0:
|
||||||
|
resolution: {integrity: sha512-aWK7SN9hAqNYi0WosPoMjenMeXJjwCxDibOqWffyQ/qXdzB/86xshGQobRferfmNz7BSNQ8GB0MD0oby9/5fTQ==}
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/monaco-worker-manager@2.0.1(monaco-editor@0.44.0):
|
||||||
|
resolution: {integrity: sha512-kdPL0yvg5qjhKPNVjJoym331PY/5JC11aPJXtCZNwWRvBr6jhkIamvYAyiY5P1AWFmNOy0aRDRoMdZfa71h8kg==}
|
||||||
|
peerDependencies:
|
||||||
|
monaco-editor: '>=0.30.0'
|
||||||
|
dependencies:
|
||||||
|
monaco-editor: 0.44.0
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/monaco-yaml@5.1.0(monaco-editor@0.44.0):
|
||||||
|
resolution: {integrity: sha512-DU+cgXSJdOFKQ4I4oLg0V+mHKq1dJX+7hbIE4fJsgegUf1zEHW3PNlGj6qabUU2HZIPJ5NmXEf005GU9YDzTYQ==}
|
||||||
|
peerDependencies:
|
||||||
|
monaco-editor: '>=0.36'
|
||||||
|
dependencies:
|
||||||
|
'@types/json-schema': 7.0.14
|
||||||
|
jsonc-parser: 3.2.0
|
||||||
|
monaco-editor: 0.44.0
|
||||||
|
monaco-languageserver-types: 0.2.3
|
||||||
|
monaco-marker-data-provider: 1.1.1(monaco-editor@0.44.0)
|
||||||
|
monaco-types: 0.1.0
|
||||||
|
monaco-worker-manager: 2.0.1(monaco-editor@0.44.0)
|
||||||
|
path-browserify: 1.0.1
|
||||||
|
prettier: 2.8.8
|
||||||
|
vscode-languageserver-textdocument: 1.0.11
|
||||||
|
vscode-languageserver-types: 3.17.5
|
||||||
|
vscode-uri: 3.0.8
|
||||||
|
yaml: 2.3.3
|
||||||
|
dev: true
|
||||||
|
|
||||||
/ms@2.0.0:
|
/ms@2.0.0:
|
||||||
resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==}
|
resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==}
|
||||||
dev: false
|
dev: false
|
||||||
@ -3364,6 +3535,10 @@ packages:
|
|||||||
hasBin: true
|
hasBin: true
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/napi-build-utils@1.0.2:
|
||||||
|
resolution: {integrity: sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/natural-compare@1.4.0:
|
/natural-compare@1.4.0:
|
||||||
resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
|
resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
|
||||||
dev: true
|
dev: true
|
||||||
@ -3395,6 +3570,13 @@ packages:
|
|||||||
dev: false
|
dev: false
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
/node-abi@3.51.0:
|
||||||
|
resolution: {integrity: sha512-SQkEP4hmNWjlniS5zdnfIXTk1x7Ome85RDzHlTbBtzE97Gfwz/Ipw4v/Ryk20DWIy3yCNVLVlGKApCnmvYoJbA==}
|
||||||
|
engines: {node: '>=10'}
|
||||||
|
dependencies:
|
||||||
|
semver: 7.5.4
|
||||||
|
dev: false
|
||||||
|
|
||||||
/node-addon-api@4.3.0:
|
/node-addon-api@4.3.0:
|
||||||
resolution: {integrity: sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ==}
|
resolution: {integrity: sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ==}
|
||||||
dev: false
|
dev: false
|
||||||
@ -3433,13 +3615,6 @@ packages:
|
|||||||
dev: false
|
dev: false
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
/node-pty@1.0.0:
|
|
||||||
resolution: {integrity: sha512-wtBMWWS7dFZm/VgqElrTvtfMq4GzJ6+edFI0Y0zyzygUSZMgZdraDUMUhCIvkjhJjme15qWmbyJbtAx4ot4uZA==}
|
|
||||||
requiresBuild: true
|
|
||||||
dependencies:
|
|
||||||
nan: 2.18.0
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/nopt@5.0.0:
|
/nopt@5.0.0:
|
||||||
resolution: {integrity: sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==}
|
resolution: {integrity: sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
@ -3593,6 +3768,10 @@ packages:
|
|||||||
engines: {node: '>= 0.8'}
|
engines: {node: '>= 0.8'}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/path-browserify@1.0.1:
|
||||||
|
resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==}
|
||||||
|
dev: true
|
||||||
|
|
||||||
/path-exists@4.0.0:
|
/path-exists@4.0.0:
|
||||||
resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
|
resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
@ -3779,11 +3958,41 @@ packages:
|
|||||||
source-map-js: 1.0.2
|
source-map-js: 1.0.2
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/prebuild-install@7.1.1:
|
||||||
|
resolution: {integrity: sha512-jAXscXWMcCK8GgCoHOfIr0ODh5ai8mj63L2nWrjuAgXE6tDyYGnx4/8o/rCgU+B4JSyZBKbeZqzhtwtC3ovxjw==}
|
||||||
|
engines: {node: '>=10'}
|
||||||
|
hasBin: true
|
||||||
|
dependencies:
|
||||||
|
detect-libc: 2.0.2
|
||||||
|
expand-template: 2.0.3
|
||||||
|
github-from-package: 0.0.0
|
||||||
|
minimist: 1.2.8
|
||||||
|
mkdirp-classic: 0.5.3
|
||||||
|
napi-build-utils: 1.0.2
|
||||||
|
node-abi: 3.51.0
|
||||||
|
pump: 3.0.0
|
||||||
|
rc: 1.2.8
|
||||||
|
simple-get: 4.0.1
|
||||||
|
tar-fs: 2.1.1
|
||||||
|
tunnel-agent: 0.6.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
/prelude-ls@1.2.1:
|
/prelude-ls@1.2.1:
|
||||||
resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
|
resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
|
||||||
engines: {node: '>= 0.8.0'}
|
engines: {node: '>= 0.8.0'}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/prettier@2.8.8:
|
||||||
|
resolution: {integrity: sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==}
|
||||||
|
engines: {node: '>=10.13.0'}
|
||||||
|
hasBin: true
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/prismjs@1.29.0:
|
||||||
|
resolution: {integrity: sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==}
|
||||||
|
engines: {node: '>=6'}
|
||||||
|
dev: true
|
||||||
|
|
||||||
/promise-inflight@1.0.1:
|
/promise-inflight@1.0.1:
|
||||||
resolution: {integrity: sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==}
|
resolution: {integrity: sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==}
|
||||||
requiresBuild: true
|
requiresBuild: true
|
||||||
@ -3845,6 +4054,13 @@ packages:
|
|||||||
dev: false
|
dev: false
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
/pump@3.0.0:
|
||||||
|
resolution: {integrity: sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==}
|
||||||
|
dependencies:
|
||||||
|
end-of-stream: 1.4.4
|
||||||
|
once: 1.4.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
/punycode@2.3.0:
|
/punycode@2.3.0:
|
||||||
resolution: {integrity: sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==}
|
resolution: {integrity: sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
@ -3876,6 +4092,16 @@ packages:
|
|||||||
unpipe: 1.0.0
|
unpipe: 1.0.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/rc@1.2.8:
|
||||||
|
resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==}
|
||||||
|
hasBin: true
|
||||||
|
dependencies:
|
||||||
|
deep-extend: 0.6.0
|
||||||
|
ini: 1.3.8
|
||||||
|
minimist: 1.2.8
|
||||||
|
strip-json-comments: 2.0.1
|
||||||
|
dev: false
|
||||||
|
|
||||||
/read@1.0.7:
|
/read@1.0.7:
|
||||||
resolution: {integrity: sha512-rSOKNYUmaxy0om1BNjMN4ezNT6VKK+2xF4GBhc81mkH7L60i6dp8qPYrkndNLT3QPphoII3maL9PVC9XmhHwVQ==}
|
resolution: {integrity: sha512-rSOKNYUmaxy0om1BNjMN4ezNT6VKK+2xF4GBhc81mkH7L60i6dp8qPYrkndNLT3QPphoII3maL9PVC9XmhHwVQ==}
|
||||||
engines: {node: '>=0.8'}
|
engines: {node: '>=0.8'}
|
||||||
@ -4136,6 +4362,18 @@ packages:
|
|||||||
engines: {node: '>=14'}
|
engines: {node: '>=14'}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/simple-concat@1.0.1:
|
||||||
|
resolution: {integrity: sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/simple-get@4.0.1:
|
||||||
|
resolution: {integrity: sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==}
|
||||||
|
dependencies:
|
||||||
|
decompress-response: 6.0.0
|
||||||
|
once: 1.4.0
|
||||||
|
simple-concat: 1.0.1
|
||||||
|
dev: false
|
||||||
|
|
||||||
/slash@3.0.0:
|
/slash@3.0.0:
|
||||||
resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==}
|
resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
@ -4339,6 +4577,11 @@ packages:
|
|||||||
ansi-regex: 6.0.1
|
ansi-regex: 6.0.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/strip-json-comments@2.0.1:
|
||||||
|
resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==}
|
||||||
|
engines: {node: '>=0.10.0'}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/strip-json-comments@3.1.1:
|
/strip-json-comments@3.1.1:
|
||||||
resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
|
resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
@ -4380,6 +4623,26 @@ packages:
|
|||||||
wordwrapjs: 4.0.1
|
wordwrapjs: 4.0.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/tar-fs@2.1.1:
|
||||||
|
resolution: {integrity: sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==}
|
||||||
|
dependencies:
|
||||||
|
chownr: 1.1.4
|
||||||
|
mkdirp-classic: 0.5.3
|
||||||
|
pump: 3.0.0
|
||||||
|
tar-stream: 2.2.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/tar-stream@2.2.0:
|
||||||
|
resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==}
|
||||||
|
engines: {node: '>=6'}
|
||||||
|
dependencies:
|
||||||
|
bl: 4.1.0
|
||||||
|
end-of-stream: 1.4.4
|
||||||
|
fs-constants: 1.0.0
|
||||||
|
inherits: 2.0.4
|
||||||
|
readable-stream: 3.6.2
|
||||||
|
dev: false
|
||||||
|
|
||||||
/tar@6.2.0:
|
/tar@6.2.0:
|
||||||
resolution: {integrity: sha512-/Wo7DcT0u5HUV486xg675HtjNd3BXZ6xDbzsCUZPt5iw8bTQ63bP0Raut3mvro9u+CUyq7YQd8Cx55fsZXxqLQ==}
|
resolution: {integrity: sha512-/Wo7DcT0u5HUV486xg675HtjNd3BXZ6xDbzsCUZPt5iw8bTQ63bP0Raut3mvro9u+CUyq7YQd8Cx55fsZXxqLQ==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
@ -4472,6 +4735,12 @@ packages:
|
|||||||
fsevents: 2.3.3
|
fsevents: 2.3.3
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/tunnel-agent@0.6.0:
|
||||||
|
resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==}
|
||||||
|
dependencies:
|
||||||
|
safe-buffer: 5.2.1
|
||||||
|
dev: false
|
||||||
|
|
||||||
/tv4@1.3.0:
|
/tv4@1.3.0:
|
||||||
resolution: {integrity: sha512-afizzfpJgvPr+eDkREK4MxJ/+r8nEEHcmitwgnPUqpaP+FpwQyadnxNoSACbgc/b1LsZYtODGoPiFxQrgJgjvw==}
|
resolution: {integrity: sha512-afizzfpJgvPr+eDkREK4MxJ/+r8nEEHcmitwgnPUqpaP+FpwQyadnxNoSACbgc/b1LsZYtODGoPiFxQrgJgjvw==}
|
||||||
engines: {node: '>= 0.8.0'}
|
engines: {node: '>= 0.8.0'}
|
||||||
@ -4690,6 +4959,30 @@ packages:
|
|||||||
dev: false
|
dev: false
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
/vscode-jsonrpc@8.2.0:
|
||||||
|
resolution: {integrity: sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==}
|
||||||
|
engines: {node: '>=14.0.0'}
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/vscode-languageserver-protocol@3.17.5:
|
||||||
|
resolution: {integrity: sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==}
|
||||||
|
dependencies:
|
||||||
|
vscode-jsonrpc: 8.2.0
|
||||||
|
vscode-languageserver-types: 3.17.5
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/vscode-languageserver-textdocument@1.0.11:
|
||||||
|
resolution: {integrity: sha512-X+8T3GoiwTVlJbicx/sIAF+yuJAqz8VvwJyoMVhwEMoEKE/fkDmrqUgDMyBECcM2A2frVZIUj5HI/ErRXCfOeA==}
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/vscode-languageserver-types@3.17.5:
|
||||||
|
resolution: {integrity: sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==}
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/vscode-uri@3.0.8:
|
||||||
|
resolution: {integrity: sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==}
|
||||||
|
dev: true
|
||||||
|
|
||||||
/vue-demi@0.14.6(vue@3.3.6):
|
/vue-demi@0.14.6(vue@3.3.6):
|
||||||
resolution: {integrity: sha512-8QA7wrYSHKaYgUxDA5ZC24w+eHm3sYCbp0EzcDwKqN3p6HqtTCGR/GVsPyZW92unff4UlcSh++lmqDWN3ZIq4w==}
|
resolution: {integrity: sha512-8QA7wrYSHKaYgUxDA5ZC24w+eHm3sYCbp0EzcDwKqN3p6HqtTCGR/GVsPyZW92unff4UlcSh++lmqDWN3ZIq4w==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
@ -4735,6 +5028,15 @@ packages:
|
|||||||
vue: 3.3.6(typescript@5.2.2)
|
vue: 3.3.6(typescript@5.2.2)
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/vue-prism-editor@2.0.0-alpha.2(vue@3.3.6):
|
||||||
|
resolution: {integrity: sha512-Gu42ba9nosrE+gJpnAEuEkDMqG9zSUysIR8SdXUw8MQKDjBnnNR9lHC18uOr/ICz7yrA/5c7jHJr9lpElODC7w==}
|
||||||
|
engines: {node: '>=10'}
|
||||||
|
peerDependencies:
|
||||||
|
vue: ^3.0.0
|
||||||
|
dependencies:
|
||||||
|
vue: 3.3.6(typescript@5.2.2)
|
||||||
|
dev: true
|
||||||
|
|
||||||
/vue-router@4.2.5(vue@3.3.6):
|
/vue-router@4.2.5(vue@3.3.6):
|
||||||
resolution: {integrity: sha512-DIUpKcyg4+PTQKfFPX88UWhlagBEBEfJ5A8XDXRJLUnZOvcpMF8o/dnL90vpVkGaPbjvXazV/rC1qBKrZlFugw==}
|
resolution: {integrity: sha512-DIUpKcyg4+PTQKfFPX88UWhlagBEBEfJ5A8XDXRJLUnZOvcpMF8o/dnL90vpVkGaPbjvXazV/rC1qBKrZlFugw==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@ -4884,14 +5186,6 @@ packages:
|
|||||||
engines: {node: '>=0.4.0'}
|
engines: {node: '>=0.4.0'}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/xterm-addon-fit@0.8.0(xterm@5.3.0):
|
|
||||||
resolution: {integrity: sha512-yj3Np7XlvxxhYF/EJ7p3KHaMt6OdwQ+HDu573Vx1lRXsVxOcnVJs51RgjZOouIZOczTsskaS+CpXspK81/DLqw==}
|
|
||||||
peerDependencies:
|
|
||||||
xterm: ^5.0.0
|
|
||||||
dependencies:
|
|
||||||
xterm: 5.3.0
|
|
||||||
dev: true
|
|
||||||
|
|
||||||
/xterm-addon-web-links@0.9.0(xterm@5.3.0):
|
/xterm-addon-web-links@0.9.0(xterm@5.3.0):
|
||||||
resolution: {integrity: sha512-LIzi4jBbPlrKMZF3ihoyqayWyTXAwGfu4yprz1aK2p71e9UKXN6RRzVONR0L+Zd+Ik5tPVI9bwp9e8fDTQh49Q==}
|
resolution: {integrity: sha512-LIzi4jBbPlrKMZF3ihoyqayWyTXAwGfu4yprz1aK2p71e9UKXN6RRzVONR0L+Zd+Ik5tPVI9bwp9e8fDTQh49Q==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@ -4910,7 +5204,6 @@ packages:
|
|||||||
/yaml@2.3.3:
|
/yaml@2.3.3:
|
||||||
resolution: {integrity: sha512-zw0VAJxgeZ6+++/su5AFoqBbZbrEakwu+X0M5HmcwUiBL7AzcuPKjj5we4xfQLp78LkEMpD0cOnUhmgOVy3KdQ==}
|
resolution: {integrity: sha512-zw0VAJxgeZ6+++/su5AFoqBbZbrEakwu+X0M5HmcwUiBL7AzcuPKjj5we4xfQLp78LkEMpD0cOnUhmgOVy3KdQ==}
|
||||||
engines: {node: '>= 14'}
|
engines: {node: '>= 14'}
|
||||||
dev: false
|
|
||||||
|
|
||||||
/yamljs@0.3.0:
|
/yamljs@0.3.0:
|
||||||
resolution: {integrity: sha512-C/FsVVhht4iPQYXOInoxUM/1ELSf9EsgKH34FofQOp6hwCPrW4vG4w5++TED3xRUo8gD7l0P1J1dLlDYzODsTQ==}
|
resolution: {integrity: sha512-C/FsVVhht4iPQYXOInoxUM/1ELSf9EsgKH34FofQOp6hwCPrW4vG4w5++TED3xRUo8gD7l0P1J1dLlDYzODsTQ==}
|
||||||
|
Loading…
Reference in New Issue
Block a user