Compare commits

..

17 Commits

Author SHA1 Message Date
4400ba5dd6 WIP 2023-12-10 20:29:14 +08:00
2b428e139c Merge branch 'master' into build-windows
# Conflicts:
#	pnpm-lock.yaml
2023-12-10 13:23:15 +08:00
587d2dcaca Fix URLs with env 2023-12-10 13:17:04 +08:00
5f1f3593fd Add pnpm run dev 2023-12-10 13:16:40 +08:00
e570374a48 WIP 2023-12-10 13:07:24 +08:00
316c566c76 Update dependencies 2023-12-10 02:39:39 +08:00
d32bd3937f Fix: .env code change is not reactive (#270) 2023-12-10 02:29:05 +08:00
007eac7b58 Envsubst YAML in order to display the correct values in the UI instead of tags (#268)
* WIP

* Add envsubst

* WIP
2023-12-10 00:59:28 +08:00
b945ddea55 Fix check update (#269)
* Fix check-version.ts
2023-12-10 00:59:05 +08:00
bd58de535e Update README.md (#264)
Correct spelling errors.
2023-12-08 23:49:29 +08:00
8c6bcef987 Stopped "keyword" match from taking \n (#255) 2023-12-07 20:14:16 +08:00
bec5460395 Update README.md 2023-12-06 03:14:14 +08:00
4e899dcf21 Fix: Arabic to RTL 2023-12-05 16:57:10 +08:00
8296c7b18f Update to 1.3.2 2023-12-05 03:01:46 +08:00
607c908f2d Fix #236 2023-12-05 03:01:06 +08:00
bd5dd3c3ad Update to 1.3.1 2023-12-05 02:47:05 +08:00
6eca6dc59f Fix #234 (#235) 2023-12-05 02:41:25 +08:00
15 changed files with 1409 additions and 477 deletions

View File

@ -131,7 +131,7 @@ Be sure to read the [guide](https://github.com/louislam/dockge/blob/master/CONTR
#### "Dockge"? #### "Dockge"?
"Dockge" is a coinage word which is created by myself. I hope it sounds like `Dodge`. "Dockge" is a coinage word which is created by myself. I originally hoped it sounds like `Dodge`, but apparently many people called it `Dockage`, it is also acceptable.
The naming idea came from Twitch emotes like `sadge`, `bedge` or `wokege`. They all end in `-ge`. The naming idea came from Twitch emotes like `sadge`, `bedge` or `wokege`. They all end in `-ge`.
@ -148,13 +148,13 @@ Yes, you can. However, you need to move your compose file into the stacks direct
3. In Dockge, click the " Scan Stacks Folder" button in the top-right corner's dropdown menu 3. In Dockge, click the " Scan Stacks Folder" button in the top-right corner's dropdown menu
4. Now you should see your stack in the list 4. Now you should see your stack in the list
#### Is Dockge a Portainer replcement? #### Is Dockge a Portainer replacement?
Yes or no. Portainer provides a lot of Docker features. While Dockge is currently only focusing on docker-compose with a better user interface and better user experience. Yes or no. Portainer provides a lot of Docker features. While Dockge is currently only focusing on docker-compose with a better user interface and better user experience.
If you want to manage your container with docker-compose only, the answer may be yes. If you want to manage your container with docker-compose only, the answer may be yes.
If you still need to manage something like docker networks, signle containers, the answer may be no. If you still need to manage something like docker networks, single containers, the answer may be no.
#### Can I install both Dockge and Portainer? #### Can I install both Dockge and Portainer?

View File

@ -3,69 +3,55 @@ import compareVersions from "compare-versions";
import packageJSON from "../package.json"; import packageJSON from "../package.json";
import { Settings } from "./settings"; import { Settings } from "./settings";
export const obj = {
version: packageJSON.version,
latestVersion: null,
};
export default obj;
// How much time in ms to wait between update checks // How much time in ms to wait between update checks
const UPDATE_CHECKER_INTERVAL_MS = 1000 * 60 * 60 * 48; const UPDATE_CHECKER_INTERVAL_MS = 1000 * 60 * 60 * 48;
const CHECK_URL = "https://dockge.kuma.pet/version"; const CHECK_URL = "https://dockge.kuma.pet/version";
let interval : NodeJS.Timeout; class CheckVersion {
version = packageJSON.version;
latestVersion? : string;
interval? : NodeJS.Timeout;
export function startInterval() { async startInterval() {
const check = async () => { const check = async () => {
if (await Settings.get("checkUpdate") === false) { if (await Settings.get("checkUpdate") === false) {
return; return;
}
log.debug("update-checker", "Retrieving latest versions");
try {
const res = await fetch(CHECK_URL);
const data = await res.json();
// For debug
if (process.env.TEST_CHECK_VERSION === "1") {
data.slow = "1000.0.0";
} }
const checkBeta = await Settings.get("checkBeta"); log.debug("update-checker", "Retrieving latest versions");
if (checkBeta && data.beta) { try {
if (compareVersions.compare(data.beta, data.slow, ">")) { const res = await fetch(CHECK_URL);
obj.latestVersion = data.beta; const data = await res.json();
return;
// For debug
if (process.env.TEST_CHECK_VERSION === "1") {
data.slow = "1000.0.0";
} }
const checkBeta = await Settings.get("checkBeta");
if (checkBeta && data.beta) {
if (compareVersions.compare(data.beta, data.slow, ">")) {
this.latestVersion = data.beta;
return;
}
}
if (data.slow) {
this.latestVersion = data.slow;
}
} catch (_) {
log.info("update-checker", "Failed to check for new versions");
} }
if (data.slow) { };
obj.latestVersion = data.slow;
}
} catch (_) { await check();
log.info("update-checker", "Failed to check for new versions"); this.interval = setInterval(check, UPDATE_CHECKER_INTERVAL_MS);
}
};
check();
interval = setInterval(check, UPDATE_CHECKER_INTERVAL_MS);
}
/**
* Enable the check update feature
* @param value Should the check update feature be enabled?
* @returns
*/
export async function enableCheckUpdate(value : boolean) {
await Settings.set("checkUpdate", value);
clearInterval(interval);
if (value) {
startInterval();
} }
} }
const checkVersion = new CheckVersion();
export default checkVersion;

View File

@ -1,3 +0,0 @@
export class Docker {
}

View File

@ -308,6 +308,7 @@ export class DockgeServer {
this.sendStackList(true); this.sendStackList(true);
}); });
checkVersion.startInterval();
}); });
gracefulShutdown(this.httpServer, { gracefulShutdown(this.httpServer, {

View File

@ -234,7 +234,7 @@ export class DockerSocketHandler extends SocketHandler {
socket.on("getDockerNetworkList", async (callback) => { socket.on("getDockerNetworkList", async (callback) => {
try { try {
checkLogin(socket); checkLogin(socket);
const dockerNetworkList = server.getDockerNetworkList(); const dockerNetworkList = await server.getDockerNetworkList();
callback({ callback({
ok: true, ok: true,
dockerNetworkList, dockerNetworkList,

View File

@ -297,7 +297,12 @@ export class Stack {
let res = await childProcessAsync.spawn("docker", [ "compose", "ls", "--all", "--format", "json" ], { let res = await childProcessAsync.spawn("docker", [ "compose", "ls", "--all", "--format", "json" ], {
encoding: "utf-8", encoding: "utf-8",
}); });
let composeList = JSON.parse(res.toString());
if (!res.stdout) {
return statusList;
}
let composeList = JSON.parse(res.stdout.toString());
for (let composeStack of composeList) { for (let composeStack of composeList) {
statusList.set(composeStack.Name, this.statusConvert(composeStack.Status)); statusList.set(composeStack.Name, this.statusConvert(composeStack.Status));

View File

@ -1,13 +1,17 @@
/* /*
* Common utilities for backend and frontend * Common utilities for backend and frontend
*/ */
import { Document } from "yaml"; import yaml, { Document, Pair, Scalar } from "yaml";
import { DotenvParseOutput } from "dotenv";
// Init dayjs // Init dayjs
import dayjs from "dayjs"; 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 relativeTime from "dayjs/plugin/relativeTime"; import relativeTime from "dayjs/plugin/relativeTime";
// @ts-ignore
import { replaceVariablesSync } from "@inventage/envsubst";
dayjs.extend(utc); dayjs.extend(utc);
dayjs.extend(timezone); dayjs.extend(timezone);
dayjs.extend(relativeTime); dayjs.extend(relativeTime);
@ -340,3 +344,48 @@ export function parseDockerPort(input : string, defaultHostname : string = "loca
display: display, display: display,
}; };
} }
export function envsubst(string : string, variables : LooseObject) : string {
return replaceVariablesSync(string, variables)[0];
}
/**
* Traverse all values in the yaml and for each value, if there are template variables, replace it environment variables
* Emulates the behavior of how docker-compose handles environment variables in yaml files
* @param content Yaml string
* @param env Environment variables
* @returns string Yaml string with environment variables replaced
*/
export function envsubstYAML(content : string, env : DotenvParseOutput) : string {
const doc = yaml.parseDocument(content);
if (doc.contents) {
// @ts-ignore
for (const item of doc.contents.items) {
traverseYAML(item, env);
}
}
return doc.toString();
}
/**
* Used for envsubstYAML(...)
* @param pair
* @param env
*/
function traverseYAML(pair : Pair, env : DotenvParseOutput) : void {
// @ts-ignore
if (pair.value && pair.value.items) {
// @ts-ignore
for (const item of pair.value.items) {
if (item instanceof Pair) {
traverseYAML(item, env);
} else if (item instanceof Scalar) {
item.value = envsubst(item.value, env);
}
}
// @ts-ignore
} else if (pair.value && typeof(pair.value.value) === "string") {
// @ts-ignore
pair.value.value = envsubst(pair.value.value, env);
}
}

92
extra/build-windows.ts Normal file
View File

@ -0,0 +1,92 @@
import fsAsync from "fs/promises";
import unzipper from "unzipper";
import stream from "node:stream";
import { fileExists } from "../backend/util-server";
const version = process.env.VERSION;
if (!version) {
console.error("VERSION env not set");
process.exit(1);
}
const output = `./private/build/dockgen-${version}-win-x64.zip`;
if (await fileExists(output)) {
console.error(`${output} already exists`);
process.exit(1);
}
console.log(`Building ${output}`);
const nodeVersion = "18.17.1";
const buildPath = "./private/build/windows";
const nodePath = `${buildPath}/node`;
const nodeTempPath = `${buildPath}/node-v${nodeVersion}-win-x64`;
const corePath = `${buildPath}/core`;
// Clear
await fsAsync.rm(`${buildPath}/dockge-${version}`, {
recursive: true,
force: true
});
await fsAsync.rm(corePath, {
recursive: true,
force: true
});
// mkdir
await fsAsync.mkdir(buildPath, {
recursive: true
});
// Download Node.js if not exists
// Download,pipe to unzipper and extract to nodePath
if (!await fileExists(nodePath)) {
console.log(`Downloading Node.js ${nodeVersion}`);
try {
await download(`https://nodejs.org/dist/v${nodeVersion}/node-v${nodeVersion}-win-x64.zip`);
// Rename folder
await fsAsync.rename(nodeTempPath, nodePath);
} catch (e) {
if (e instanceof Error) {
console.error(e.message);
}
process.exit(1);
}
} else {
console.log(`Node.js ${nodeVersion} already exists, skipping download`);
}
// Download Dockge from GitHub
console.log(`Downloading Dockge ${version} from GitHub`);
try {
await download(`https://github.com/louislam/dockge/archive/refs/tags/${version}.zip`);
// Rename folder
await fsAsync.rename(`${buildPath}/dockge-${version}`, corePath);
} catch (e) {
if (e instanceof Error) {
console.error(e.message);
}
process.exit(1);
}
function download(url : string) {
return new Promise((resolve, reject) => {
fetch(url).then((res) => {
if (res.body) {
// @ts-ignore
stream.Readable.fromWeb(res.body)
.pipe(unzipper.Extract({
path: buildPath,
}))
.on("close", resolve);
} else {
reject(new Error(`Unable to download ${url}`));
}
});
});
}

View File

@ -9,7 +9,7 @@
<div v-if="!isEditMode"> <div v-if="!isEditMode">
<span class="badge me-1" :class="bgStyle">{{ status }}</span> <span class="badge me-1" :class="bgStyle">{{ status }}</span>
<a v-for="port in service.ports" :key="port" :href="parsePort(port).url" target="_blank"> <a v-for="port in envsubstService.ports" :key="port" :href="parsePort(port).url" target="_blank">
<span class="badge me-1 bg-secondary">{{ parsePort(port).display }}</span> <span class="badge me-1 bg-secondary">{{ parsePort(port).display }}</span>
</a> </a>
</div> </div>
@ -213,16 +213,29 @@ export default defineComponent({
jsonObject() { jsonObject() {
return this.$parent.$parent.jsonConfig; return this.$parent.$parent.jsonConfig;
}, },
envsubstJSONConfig() {
return this.$parent.$parent.envsubstJSONConfig;
},
envsubstService() {
if (!this.envsubstJSONConfig.services[this.name]) {
return {};
}
return this.envsubstJSONConfig.services[this.name];
},
imageName() { imageName() {
if (this.service.image) { if (this.envsubstService.image) {
return this.service.image.split(":")[0]; return this.envsubstService.image.split(":")[0];
} else { } else {
return ""; return "";
} }
}, },
imageTag() { imageTag() {
if (this.service.image) { if (this.envsubstService.image) {
let tag = this.service.image.split(":")[1]; let tag = this.envsubstService.image.split(":")[1];
if (tag) { if (tag) {
return tag; return tag;

View File

@ -39,7 +39,7 @@ for (let lang in languageList) {
}; };
} }
const rtlLangs = [ "fa", "ar-SY", "ur" ]; const rtlLangs = [ "fa", "ar-SY", "ur", "ar" ];
export const currentLocale = () => localStorage.locale export const currentLocale = () => localStorage.locale
|| languageList[navigator.language] && navigator.language || languageList[navigator.language] && navigator.language

View File

@ -98,5 +98,6 @@
"reconnecting...": "Reconnecting…", "reconnecting...": "Reconnecting…",
"connecting...": "Connecting to the socket server…", "connecting...": "Connecting to the socket server…",
"url": "URL | URLs", "url": "URL | URLs",
"extra": "Extra" "extra": "Extra",
"newUpdate": "New Update"
} }

View File

@ -16,8 +16,8 @@
<span class="fs-4 title">Dockge</span> <span class="fs-4 title">Dockge</span>
</router-link> </router-link>
<a v-if="hasNewVersion" target="_blank" href="https://github.com/louislam/dockge/releases" class="btn btn-info me-3"> <a v-if="hasNewVersion" target="_blank" href="https://github.com/louislam/dockge/releases" class="btn btn-warning me-3">
<font-awesome-icon icon="arrow-alt-circle-up" /> {{ $t("New Update") }} <font-awesome-icon icon="arrow-alt-circle-up" /> {{ $t("newUpdate") }}
</a> </a>
<ul class="nav nav-pills"> <ul class="nav nav-pills">

View File

@ -231,7 +231,7 @@ import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
import { import {
COMBINED_TERMINAL_COLS, COMBINED_TERMINAL_COLS,
COMBINED_TERMINAL_ROWS, COMBINED_TERMINAL_ROWS,
copyYAMLComments, copyYAMLComments, envsubstYAML,
getCombinedTerminalName, getCombinedTerminalName,
getComposeTerminalName, getComposeTerminalName,
PROGRESS_TERMINAL_ROWS, PROGRESS_TERMINAL_ROWS,
@ -239,6 +239,7 @@ import {
} from "../../../backend/util-common"; } from "../../../backend/util-common";
import { BModal } from "bootstrap-vue-next"; import { BModal } from "bootstrap-vue-next";
import NetworkInput from "../components/NetworkInput.vue"; import NetworkInput from "../components/NetworkInput.vue";
import dotenv from "dotenv";
const template = `version: "3.8" const template = `version: "3.8"
services: services:
@ -277,6 +278,7 @@ export default {
return { return {
editorFocus: false, editorFocus: false,
jsonConfig: {}, jsonConfig: {},
envsubstJSONConfig: {},
yamlError: "", yamlError: "",
processing: true, processing: true,
showProgressTerminal: false, showProgressTerminal: false,
@ -297,12 +299,12 @@ export default {
computed: { computed: {
urls() { urls() {
if (!this.jsonConfig["x-dockge"] || !this.jsonConfig["x-dockge"].urls || !Array.isArray(this.jsonConfig["x-dockge"].urls)) { if (!this.envsubstJSONConfig["x-dockge"] || !this.envsubstJSONConfig["x-dockge"].urls || !Array.isArray(this.envsubstJSONConfig["x-dockge"].urls)) {
return []; return [];
} }
let urls = []; let urls = [];
for (const url of this.jsonConfig["x-dockge"].urls) { for (const url of this.envsubstJSONConfig["x-dockge"].urls) {
let display; let display;
try { try {
let obj = new URL(url); let obj = new URL(url);
@ -372,6 +374,17 @@ export default {
}, },
deep: true, deep: true,
}, },
"stack.composeENV": {
handler() {
if (this.editorFocus) {
console.debug("env code changed");
this.yamlCodeChange();
}
},
deep: true,
},
jsonConfig: { jsonConfig: {
handler() { handler() {
if (!this.editorFocus) { if (!this.editorFocus) {
@ -622,7 +635,7 @@ export default {
greedy: true greedy: true
}, },
"keyword": { "keyword": {
pattern: /^[^ :=]*(?=[:=])/m, pattern: /^\w*(?=[:=])/m,
greedy: true greedy: true
}, },
"value": { "value": {
@ -645,28 +658,41 @@ export default {
return highlight(code, languages.docker_env); return highlight(code, languages.docker_env);
}, },
yamlToJSON(yaml) {
let doc = parseDocument(yaml);
if (doc.errors.length > 0) {
throw doc.errors[0];
}
const config = doc.toJS() ?? {};
// Check data types
// "services" must be an object
if (!config.services) {
config.services = {};
}
if (Array.isArray(config.services) || typeof config.services !== "object") {
throw new Error("Services must be an object");
}
return {
config,
doc,
};
},
yamlCodeChange() { yamlCodeChange() {
try { try {
let doc = parseDocument(this.stack.composeYAML); let { config, doc } = this.yamlToJSON(this.stack.composeYAML);
if (doc.errors.length > 0) {
throw doc.errors[0];
}
const config = doc.toJS() ?? {};
// Check data types
// "services" must be an object
if (!config.services) {
config.services = {};
}
if (Array.isArray(config.services) || typeof config.services !== "object") {
throw new Error("Services must be an object");
}
this.yamlDoc = doc; this.yamlDoc = doc;
this.jsonConfig = config; this.jsonConfig = config;
let env = dotenv.parse(this.stack.composeENV);
let envYAML = envsubstYAML(this.stack.composeYAML, env);
this.envsubstJSONConfig = this.yamlToJSON(envYAML).config;
clearTimeout(yamlErrorTimeout); clearTimeout(yamlErrorTimeout);
this.yamlError = ""; this.yamlError = "";
} catch (e) { } catch (e) {

View File

@ -1,6 +1,6 @@
{ {
"name": "dockge", "name": "dockge",
"version": "1.3.0", "version": "1.3.2",
"type": "module", "type": "module",
"engines": { "engines": {
"node": ">= 18.0.0 && <= 18.17.1" "node": ">= 18.0.0 && <= 18.17.1"
@ -10,13 +10,15 @@
"lint": "eslint \"**/*.{ts,vue}\"", "lint": "eslint \"**/*.{ts,vue}\"",
"check-ts": "tsc --noEmit", "check-ts": "tsc --noEmit",
"start": "tsx ./backend/index.ts", "start": "tsx ./backend/index.ts",
"dev": "concurrently -k -r \"wait-on tcp:5000 && pnpm run dev:backend \" \"pnpm run dev:frontend\"",
"dev:backend": "cross-env NODE_ENV=development tsx watch --inspect ./backend/index.ts", "dev:backend": "cross-env NODE_ENV=development tsx watch --inspect ./backend/index.ts",
"dev:frontend": "cross-env NODE_ENV=development vite --host --config ./frontend/vite.config.ts", "dev:frontend": "cross-env NODE_ENV=development vite --host --config ./frontend/vite.config.ts",
"release-final": "tsx ./extra/test-docker.ts && tsx extra/update-version.ts && pnpm run build:frontend && npm run build:docker", "release-final": "tsx ./extra/test-docker.ts && tsx extra/update-version.ts && pnpm run build:frontend && pnpm run build:docker",
"build:frontend": "vite build --config ./frontend/vite.config.ts", "build:frontend": "vite build --config ./frontend/vite.config.ts",
"build:docker-base": "docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/dockge:base -f ./docker/Base.Dockerfile . --push", "build:docker-base": "docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/dockge:base -f ./docker/Base.Dockerfile . --push",
"build:docker": "node ./extra/env2arg.js docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/dockge:latest -t louislam/dockge:1 -t louislam/dockge:$VERSION --target release -f ./docker/Dockerfile . --push", "build:docker": "node ./extra/env2arg.js docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/dockge:latest -t louislam/dockge:1 -t louislam/dockge:$VERSION --target release -f ./docker/Dockerfile . --push",
"build:docker-nightly": "pnpm run build:frontend && docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/dockge:nightly --target nightly -f ./docker/Dockerfile . --push", "build:docker-nightly": "pnpm run build:frontend && docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/dockge:nightly --target nightly -f ./docker/Dockerfile . --push",
"build:windows": "tsx ./extra/build-windows.ts",
"build:healthcheck": "docker buildx build -f docker/BuildHealthCheck.Dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/dockge:build-healthcheck . --push", "build:healthcheck": "docker buildx build -f docker/BuildHealthCheck.Dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/dockge:build-healthcheck . --push",
"start-docker": "docker run --rm -p 5001:5001 --name dockge louislam/dockge:latest", "start-docker": "docker run --rm -p 5001:5001 --name dockge louislam/dockge:latest",
"mark-as-nightly": "tsx ./extra/mark-as-nightly.ts", "mark-as-nightly": "tsx ./extra/mark-as-nightly.ts",
@ -24,7 +26,8 @@
"reset-password": "tsx ./extra/reset-password.ts" "reset-password": "tsx ./extra/reset-password.ts"
}, },
"dependencies": { "dependencies": {
"@homebridge/node-pty-prebuilt-multiarch": "~0.11.11", "@homebridge/node-pty-prebuilt-multiarch": "~0.11.12",
"@inventage/envsubst": "^0.16.0",
"@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",
@ -41,34 +44,37 @@
"jwt-decode": "~3.1.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.3", "mysql2": "~3.6.5",
"node-windows": "1.0.0-beta.8",
"promisify-child-process": "~4.1.2", "promisify-child-process": "~4.1.2",
"redbean-node": "~0.3.3", "redbean-node": "~0.3.3",
"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": "~4.6.2",
"type-fest": "~4.3.3", "type-fest": "~4.3.3",
"yaml": "~2.3.4" "yaml": "~2.3.4"
}, },
"devDependencies": { "devDependencies": {
"@actions/github": "^6.0.0", "@actions/github": "^6.0.0",
"@fontsource/jetbrains-mono": "^5.0.17", "@fontsource/jetbrains-mono": "^5.0.18",
"@fortawesome/fontawesome-svg-core": "6.4.2", "@fortawesome/fontawesome-svg-core": "6.4.2",
"@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/bcryptjs": "^2.4.6", "@types/bcryptjs": "^2.4.6",
"@types/bootstrap": "~5.2.9", "@types/bootstrap": "~5.2.10",
"@types/command-exists": "~1.2.3", "@types/command-exists": "~1.2.3",
"@types/express": "~4.17.21", "@types/express": "~4.17.21",
"@types/jsonwebtoken": "~9.0.5", "@types/jsonwebtoken": "~9.0.5",
"@types/unzipper": "^0.10.9",
"@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.5.0", "@vitejs/plugin-vue": "~4.5.2",
"bootstrap": "5.3.2", "bootstrap": "5.3.2",
"bootstrap-vue-next": "~0.14.10", "bootstrap-vue-next": "~0.14.10",
"concurrently": "^8.2.2",
"cross-env": "~7.0.3", "cross-env": "~7.0.3",
"eslint": "~8.50.0", "eslint": "~8.50.0",
"eslint-plugin-jsdoc": "~46.8.2", "eslint-plugin-jsdoc": "~46.8.2",
@ -77,15 +83,17 @@
"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",
"vite": "~5.0.0", "unzipper": "^0.10.14",
"vite": "~5.0.7",
"vite-plugin-compression": "~0.5.1", "vite-plugin-compression": "~0.5.1",
"vue": "~3.3.8", "vue": "~3.3.11",
"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-prism-editor": "2.0.0-alpha.2",
"vue-qrcode": "~2.2.0", "vue-qrcode": "~2.2.0",
"vue-router": "~4.2.5", "vue-router": "~4.2.5",
"vue-toastification": "2.0.0-rc.5", "vue-toastification": "2.0.0-rc.5",
"wait-on": "^7.2.0",
"xterm": "5.4.0-beta.37", "xterm": "5.4.0-beta.37",
"xterm-addon-web-links": "~0.9.0" "xterm-addon-web-links": "~0.9.0"
} }

1508
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff