Compare commits

..

2 Commits

Author SHA1 Message Date
0aa4704b64 Fix check-version.ts 2023-12-10 00:53:45 +08:00
8fc23cc83e Fix check-version.ts 2023-12-10 00:48:35 +08:00
17 changed files with 457 additions and 1331 deletions

View File

@ -6,7 +6,7 @@
A fancy, easy-to-use and reactive self-hosted docker compose.yaml stack-oriented manager.
![GitHub Repo stars](https://img.shields.io/github/stars/louislam/dockge?logo=github) ![Docker Pulls](https://img.shields.io/docker/pulls/louislam/dockge?logo=docker) ![Docker Image Version (latest semver)](https://img.shields.io/docker/v/louislam/dockge/latest?label=docker%20image%20ver.) ![GitHub last commit (branch)](https://img.shields.io/github/last-commit/louislam/dockge/master?logo=github)
![GitHub Repo stars](https://img.shields.io/github/stars/louislam/dockge?logo=github) ![GitHub issues](https://img.shields.io/github/issues/louislam/dockge?logo=github) ![GitHub pull requests](https://img.shields.io/github/issues-pr/louislam/dockge?logo=github) ![Docker Pulls](https://img.shields.io/docker/pulls/louislam/dockge?logo=docker) ![Docker Image Version (latest semver)](https://img.shields.io/docker/v/louislam/dockge/latest?label=docker%20image%20ver.) ![GitHub last commit (branch)](https://img.shields.io/github/last-commit/louislam/dockge/master?logo=github) ![GitHub](https://img.shields.io/github/license/louislam/dockge?logo=github)
<img src="https://github.com/louislam/dockge/assets/1336778/26a583e1-ecb1-4a8d-aedf-76157d714ad7" width="900" alt="" />

3
backend/docker.ts Normal file
View File

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

View File

@ -194,39 +194,6 @@ export class DockgeServer {
// Create Socket.io
this.io = new socketIO.Server(this.httpServer, {
cors,
allowRequest: (req, callback) => {
let isOriginValid = true;
const bypass = isDev;
if (!bypass) {
let host = req.headers.host;
// If this is set, it means the request is from the browser
let origin = req.headers.origin;
// If this is from the browser, check if the origin is allowed
if (origin) {
try {
let originURL = new URL(origin);
if (host !== originURL.host) {
isOriginValid = false;
log.error("auth", `Origin (${origin}) does not match host (${host}), IP: ${req.socket.remoteAddress}`);
}
} catch (e) {
// Invalid origin url, probably not from browser
isOriginValid = false;
log.error("auth", `Invalid origin url (${origin}), IP: ${req.socket.remoteAddress}`);
}
} else {
log.info("auth", `Origin is not set, IP: ${req.socket.remoteAddress}`);
}
} else {
log.debug("auth", "Origin check is bypassed");
}
callback(null, isOriginValid);
}
});
this.io.on("connection", async (socket: Socket) => {
@ -611,35 +578,4 @@ export class DockgeServer {
finalFunction() {
log.info("server", "Graceful shutdown successful!");
}
/**
* Force connected sockets of a user to refresh and disconnect.
* Used for resetting password.
* @param {string} userID
* @param {string?} currentSocketID
*/
disconnectAllSocketClients(userID: number, currentSocketID? : string) {
for (const rawSocket of this.io.sockets.sockets.values()) {
let socket = rawSocket as DockgeSocket;
if (socket.userID === userID && socket.id !== currentSocketID) {
try {
socket.emit("refresh");
socket.disconnect();
} catch (e) {
}
}
}
}
isSSL() {
return this.config.sslKey && this.config.sslCert;
}
getLocalWebSocketURL() {
const protocol = this.isSSL() ? "wss" : "ws";
const host = this.config.hostname || "localhost";
return `${protocol}://${host}:${this.config.port}`;
}
}

View File

@ -211,8 +211,6 @@ export class MainSocketHandler extends SocketHandler {
let user = await doubleCheckPassword(socket, password.currentPassword);
await user.resetPassword(password.newPassword);
server.disconnectAllSocketClients(user.id, socket.id);
callback({
ok: true,
msg: "Password has been updated successfully.",
@ -282,18 +280,6 @@ export class MainSocketHandler extends SocketHandler {
}
}
});
// Disconnect all other socket clients of the user
socket.on("disconnectOtherSocketClients", async () => {
try {
checkLogin(socket);
server.disconnectAllSocketClients(socket.userID, socket.id);
} catch (e) {
if (e instanceof Error) {
log.warn("disconnectOtherSocketClients", e.message);
}
}
});
}
async login(username : string, password : string) : Promise<User | null> {

View File

@ -1,17 +1,13 @@
/*
* Common utilities for backend and frontend
*/
import yaml, { Document, Pair, Scalar } from "yaml";
import { DotenvParseOutput } from "dotenv";
import { Document } from "yaml";
// Init dayjs
import dayjs from "dayjs";
import timezone from "dayjs/plugin/timezone";
import utc from "dayjs/plugin/utc";
import relativeTime from "dayjs/plugin/relativeTime";
// @ts-ignore
import { replaceVariablesSync } from "@inventage/envsubst";
dayjs.extend(utc);
dayjs.extend(timezone);
dayjs.extend(relativeTime);
@ -21,11 +17,6 @@ export interface LooseObject {
[key: string]: any
}
export interface BaseRes {
ok: boolean;
msg?: string;
}
let randomBytes : (numBytes: number) => Uint8Array;
initRandomBytes();
@ -349,52 +340,3 @@ export function parseDockerPort(input : string, defaultHostname : string = "loca
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) {
let value = item.value as unknown;
if (typeof(value) === "string") {
item.value = envsubst(value, env);
}
}
}
// @ts-ignore
} else if (pair.value && typeof(pair.value.value) === "string") {
// @ts-ignore
pair.value.value = envsubst(pair.value.value, env);
}
}

View File

@ -4,24 +4,16 @@ const input = `
`;
const template = `
> [!WARNING]
>
### 🆕 New Features
### ⬆️ Improvements
### Improvements
### 🐛 Bug Fixes
### 🐞 Bug Fixes
### 🦎 Translation Contributions
### ⬆️ Security Fixes
### Others
- Other small changes, code refactoring and comment/doc updates in this repo:
Please let me know if your username is missing, if your pull request has been merged in this version, or your commit has been included in one of the pull requests.
`;
const lines = input.split("\n").filter((line) => line.trim() !== "");
@ -45,12 +37,6 @@ for (const line of lines) {
}
message = message.split("* ").pop();
let thanks = "";
if (username != "@louislam") {
thanks = `(Thanks ${username})`;
}
console.log(pullRequestID, message, thanks);
console.log("-", pullRequestID, message, `(Thanks ${username})`);
}
console.log(template);

View File

@ -4,8 +4,6 @@ import readline from "readline";
import { User } from "../backend/models/user";
import { DockgeServer } from "../backend/dockge-server";
import { log } from "../backend/log";
import { io } from "socket.io-client";
import { BaseRes } from "../backend/util-common";
console.log("== Dockge Reset Password Tool ==");
@ -14,10 +12,11 @@ const rl = readline.createInterface({
output: process.stdout
});
export const main = async () => {
const server = new DockgeServer();
export const main = async () => {
// Check if
console.log("Connecting the database");
try {
await Database.init(server);
@ -48,16 +47,12 @@ export const main = async () => {
// Reset all sessions by reset jwt secret
await server.initJWTSecret();
console.log("Password reset successfully.");
// Disconnect all other socket clients of the user
await disconnectAllSocketClients(user.username, password);
break;
} else {
console.log("Passwords do not match, please try again.");
}
}
console.log("Password reset successfully.");
}
} catch (e) {
if (e instanceof Error) {
@ -84,47 +79,6 @@ function question(question : string) : Promise<string> {
});
}
function disconnectAllSocketClients(username : string, password : string) : Promise<void> {
return new Promise((resolve) => {
const url = server.getLocalWebSocketURL();
console.log("Connecting to " + url + " to disconnect all other socket clients");
// Disconnect all socket connections
const socket = io(url, {
transports: [ "websocket" ],
reconnection: false,
timeout: 5000,
});
socket.on("connect", () => {
socket.emit("login", {
username,
password,
}, (res : BaseRes) => {
if (res.ok) {
console.log("Logged in.");
socket.emit("disconnectOtherSocketClients");
} else {
console.warn("Login failed.");
console.warn("Please restart the server to disconnect all sessions.");
}
socket.close();
});
});
socket.on("connect_error", function () {
// The localWebSocketURL is not guaranteed to be working for some complicated Uptime Kuma setup
// Ask the user to restart the server manually
console.warn("Failed to connect to " + url);
console.warn("Please restart the server to disconnect all sessions manually.");
resolve();
});
socket.on("disconnect", () => {
resolve();
});
});
}
if (!process.env.TEST_BACKEND) {
main();
}

View File

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

View File

@ -10,7 +10,7 @@
"home": "Startseite",
"console": "Konsole",
"registry": "Container Registry",
"compose": "Compose",
"compose": "",
"addFirstStackMsg": "Stelle deinen ersten Stack zusammen!",
"stackName": "Stack-Name",
"deployStack": "Deployen",
@ -72,15 +72,15 @@
"Check Update On GitHub": "Update auf GitHub überprüfen",
"Show update if available": "Update anzeigen, wenn verfügbar",
"Also check beta release": "Auch Beta-Version überprüfen",
"Remember me": "Angemeldet bleiben",
"Remember me": "Anmeldung beibehalten",
"Login": "Anmelden",
"Username": "Benutzername",
"Password": "Passwort",
"Settings": "Einstellungen",
"Logout": "Abmelden",
"Lowercase only": "Nur Kleinbuchstaben",
"Convert to Compose": "In Compose-Syntax umwandeln",
"Docker Run": "Docker Run",
"Convert to Compose": "In Compose Syntax umwandeln",
"Docker Run": "Docker ausführen",
"active": "aktiv",
"exited": "beendet",
"inactive": "inaktiv",

View File

@ -90,13 +90,5 @@
"Allowed commands:": "Comandos permitidos:",
"Internal Networks": "Redes internas",
"External Networks": "Redes externas",
"No External Networks": "Sem redes externas",
"reverseProxyMsg2": "Veja como configurar para WebSocket",
"downStack": "Parar & Encerrar",
"reverseProxyMsg1": "Utiliza proxy reverso?",
"Cannot connect to the socket server.": "Não é possível conectar ao socket server.",
"connecting...": "Conectando ao socket server…",
"url": "URL | URLs",
"extra": "Extra",
"reconnecting...": "Reconectando…"
"No External Networks": "Sem redes externas"
}

View File

@ -5,12 +5,12 @@
"PasswordsDoNotMatch": "Пароль не совпадает.",
"Repeat Password": "Повторите пароль",
"Create": "Создать",
"signedInDisp": "Авторизован как {0}",
"signedInDisp": "Авторизован как",
"signedInDispDisabled": "Авторизация выключена.",
"home": "Главная",
"console": "Консоль",
"registry": "Реестр (Registry)",
"compose": "Составить (Compose)",
"registry": "Registry",
"compose": "Compose",
"addFirstStackMsg": "Создайте свой первый стек!",
"stackName": "Имя стека",
"deployStack": "Развернуть",
@ -90,13 +90,5 @@
"Allowed commands:": "Разрешенные команды:",
"Internal Networks": "Внутренние сети",
"External Networks": "Внешние сети",
"No External Networks": "Нет внешних сетей",
"downStack": "Остановить и выключить",
"reverseProxyMsg1": "Использовать Реверс Прокси?",
"reconnecting...": "Переподключение…",
"Cannot connect to the socket server.": "Не удается подключиться к серверу сокетов.",
"url": "URL адрес(а)",
"extra": "Дополнительно",
"reverseProxyMsg2": "Проверьте, как настроить его для WebSocket",
"connecting...": "Подключение к серверу сокетов…"
"No External Networks": "Нет внешних сетей"
}

View File

@ -1,6 +1,6 @@
{
"languageName": "Svenska",
"Create your admin account": "Skapa ditt Admin-konto",
"Create your admin account": "Skapa ditt Admin-konto.",
"authIncorrectCreds": "Fel användarnamn eller lösenord.",
"PasswordsDoNotMatch": "Lösenorden matchar inte.",
"Repeat Password": "Repetera lösenord",
@ -15,11 +15,11 @@
"stackName" : "Stacknamn",
"deployStack": "Distribuera",
"deleteStack": "Radera",
"stopStack": "Stoppa",
"stopStack": "Stop",
"restartStack": "Starta om",
"updateStack": "Uppdatera",
"startStack": "Starta",
"downStack": "Stoppa & Ner",
"downStack": "Stop & Ner",
"editStack": "Redigera",
"discardStack": "Kasta",
"saveStackDraft": "Spara",
@ -29,11 +29,11 @@
"primaryHostname": "Primärt värdnamn",
"general": "Allmän",
"container": "Container | Containrar",
"scanFolder": "Skanna Stackmapp",
"dockerImage": "Avbild",
"restartPolicyUnlessStopped": "Om inte stoppad",
"scanFolder": "Scanna Stackfolder",
"dockerImage": "Bild",
"restartPolicyUnlessStopped": "Om inte stoppas",
"restartPolicyAlways": "Alltid",
"restartPolicyOnFailure": "Vid misslyckande",
"restartPolicyOnFailure": "Vid Misslyckande",
"restartPolicyNo": "Nej",
"environmentVariable": "Miljövariabel | Miljövariabler",
"restartPolicy": "Omstartspolicy",
@ -44,12 +44,12 @@
"dependsOn": "Containerberoende | Containerberoenden",
"addListItem": "Lägg till {0}",
"deleteContainer": "Radera",
"addContainer": "Lägg till container",
"addNetwork": "Lägg till nätverk",
"addContainer": "Lägg till Container",
"addNetwork": "Lägg till Nätverk",
"disableauth.message1": "Är du säker på att du vill <strong>inaktivera autentisering</strong>?",
"disableauth.message2": "Det är designat för scenarion <strong>där du ska implementera tredjepartsautentisering</strong> framför Dockge som Cloudflare Access, Authelia eller andra autentiseringsmekanismer.",
"passwordNotMatchMsg": "Det upprepade lösenordet matchar inte.",
"autoGet": "Auto-hämta",
"disableauth.message2": "Det är designat för senarion <stong>när du ska implementera tredjeparts autentisering</strong> framör Dockge som Cloudflare Access, Authelia eller andra autentiseringsmekanismer.",
"passwordNotMatchMsg": "Det upprepade lösenordet matchar inte",
"autoGet": "Auto Hämta",
"add": "Lägg till",
"Edit": "Redigera",
"applyToYAML": "Lägg till i YAML",
@ -57,8 +57,8 @@
"addInternalNetwork": "Lägg till",
"Save": "Spara",
"Language": "Språk",
"Current User": "Nuvarande användare",
"Change Password": "Ändra lösenord",
"Current User": "Nuvarande användaren",
"Change Password": "Byt lösenord",
"Current Password": "Nuvarande lösenord",
"New Password": "Nytt lösenord",
"Repeat New Password": "Upprepa nytt lösenord",
@ -70,9 +70,9 @@
"I understand, please disable": "Jag förstår, vänligen inaktivera",
"Leave": "Lämna",
"Frontend Version": "Frontendversion",
"Check Update On GitHub": "Kontrollera uppdatering på GitHub",
"Check Update On GitHub": "Kontrollera Uppdatering på GitHub",
"Show update if available": "Visa uppdatering om tillgänglig",
"Also check beta release": "Kontrollera även betaversioner",
"Also check beta release": "Kontrollera även betaversionen",
"Remember me": "Kom ihåg mig",
"Login": "Logga in",
"Username": "Användarnamn",
@ -80,8 +80,8 @@
"Settings": "Inställningar",
"Logout": "Logga ut",
"Lowercase only": "Endast små tecken",
"Convert to Compose": "Omvandla till compose",
"Docker Run": "Docker kör",
"Convert to Compose": "Omvandla till Compose",
"Docker Run": "Docker Run",
"active": "aktiv",
"exited": "avslutad",
"inactive": "inaktiv",
@ -89,14 +89,7 @@
"Security": "Säkerhet",
"About": "Om",
"Allowed commands:": "Tillåtna kommandon:",
"Internal Networks": "Interna nätverk",
"External Networks": "Externa nätverk",
"No External Networks": "Inga externa nätverk",
"reverseProxyMsg1": "Används omvänd proxy?",
"connecting...": "Ansluter till socketserver…",
"Cannot connect to the socket server.": "Kan inte ansluta till socketservern.",
"reverseProxyMsg2": "Kontrollera hur man konfigurerar webbsocket",
"url": "URL | URLer",
"extra": "Extra",
"reconnecting...": "Återansluter…"
"Internal Networks": "Interna Nätverk",
"External Networks": "Externa Nätverk",
"No External Networks": "Inga Externa Nätverk"
}

View File

@ -92,7 +92,7 @@
"External Networks": "外部網路",
"No External Networks": "無外部網路",
"downStack": "停止",
"reverseProxyMsg1": "在使用反向代理",
"reverseProxyMsg1": "在使用反向代理",
"reverseProxyMsg2": "點擊這裡了解如何為 WebSocket 配置反向代理",
"Cannot connect to the socket server.": "無法連接到 Socket 伺服器。",
"reconnecting...": "重新連線中…",

View File

@ -202,10 +202,6 @@ export default defineComponent({
}
}
});
socket.on("refresh", () => {
location.reload();
});
},
/**

View File

@ -231,7 +231,7 @@ import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
import {
COMBINED_TERMINAL_COLS,
COMBINED_TERMINAL_ROWS,
copyYAMLComments, envsubstYAML,
copyYAMLComments,
getCombinedTerminalName,
getComposeTerminalName,
PROGRESS_TERMINAL_ROWS,
@ -239,7 +239,6 @@ import {
} from "../../../backend/util-common";
import { BModal } from "bootstrap-vue-next";
import NetworkInput from "../components/NetworkInput.vue";
import dotenv from "dotenv";
const template = `version: "3.8"
services:
@ -278,7 +277,6 @@ export default {
return {
editorFocus: false,
jsonConfig: {},
envsubstJSONConfig: {},
yamlError: "",
processing: true,
showProgressTerminal: false,
@ -299,12 +297,12 @@ export default {
computed: {
urls() {
if (!this.envsubstJSONConfig["x-dockge"] || !this.envsubstJSONConfig["x-dockge"].urls || !Array.isArray(this.envsubstJSONConfig["x-dockge"].urls)) {
if (!this.jsonConfig["x-dockge"] || !this.jsonConfig["x-dockge"].urls || !Array.isArray(this.jsonConfig["x-dockge"].urls)) {
return [];
}
let urls = [];
for (const url of this.envsubstJSONConfig["x-dockge"].urls) {
for (const url of this.jsonConfig["x-dockge"].urls) {
let display;
try {
let obj = new URL(url);
@ -374,17 +372,6 @@ export default {
},
deep: true,
},
"stack.composeENV": {
handler() {
if (this.editorFocus) {
console.debug("env code changed");
this.yamlCodeChange();
}
},
deep: true,
},
jsonConfig: {
handler() {
if (!this.editorFocus) {
@ -658,8 +645,9 @@ export default {
return highlight(code, languages.docker_env);
},
yamlToJSON(yaml) {
let doc = parseDocument(yaml);
yamlCodeChange() {
try {
let doc = parseDocument(this.stack.composeYAML);
if (doc.errors.length > 0) {
throw doc.errors[0];
}
@ -676,23 +664,9 @@ export default {
throw new Error("Services must be an object");
}
return {
config,
doc,
};
},
yamlCodeChange() {
try {
let { config, doc } = this.yamlToJSON(this.stack.composeYAML);
this.yamlDoc = doc;
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);
this.yamlError = "";
} catch (e) {

View File

@ -1,6 +1,6 @@
{
"name": "dockge",
"version": "1.3.3",
"version": "1.3.2",
"type": "module",
"engines": {
"node": ">= 18.0.0 && <= 18.17.1"
@ -10,7 +10,6 @@
"lint": "eslint \"**/*.{ts,vue}\"",
"check-ts": "tsc --noEmit",
"start": "tsx ./backend/index.ts",
"dev": "concurrently -k -r \"wait-on tcp:5000 && pnpm run dev:backend \" \"pnpm run dev:frontend\"",
"dev:backend": "cross-env NODE_ENV=development tsx watch --inspect ./backend/index.ts",
"dev:frontend": "cross-env NODE_ENV=development vite --host --config ./frontend/vite.config.ts",
"release-final": "tsx ./extra/test-docker.ts && tsx extra/update-version.ts && pnpm run build:frontend && npm run build:docker",
@ -25,8 +24,7 @@
"reset-password": "tsx ./extra/reset-password.ts"
},
"dependencies": {
"@homebridge/node-pty-prebuilt-multiarch": "~0.11.12",
"@inventage/envsubst": "^0.16.0",
"@homebridge/node-pty-prebuilt-multiarch": "~0.11.11",
"@louislam/sqlite3": "~15.1.6",
"bcryptjs": "~2.4.3",
"check-password-strength": "~2.0.7",
@ -43,34 +41,32 @@
"jwt-decode": "~3.1.2",
"knex": "~2.5.1",
"limiter-es6-compat": "~2.1.2",
"mysql2": "~3.6.5",
"mysql2": "~3.6.3",
"promisify-child-process": "~4.1.2",
"redbean-node": "~0.3.3",
"socket.io": "~4.7.2",
"socket.io-client": "~4.7.2",
"timezones-list": "~3.0.2",
"ts-command-line-args": "~2.5.1",
"tsx": "~4.6.2",
"tsx": "~3.14.0",
"type-fest": "~4.3.3",
"yaml": "~2.3.4"
},
"devDependencies": {
"concurrently": "^8.2.2",
"wait-on": "^7.2.0",
"@actions/github": "^6.0.0",
"@fontsource/jetbrains-mono": "^5.0.18",
"@fontsource/jetbrains-mono": "^5.0.17",
"@fortawesome/fontawesome-svg-core": "6.4.2",
"@fortawesome/free-regular-svg-icons": "6.4.2",
"@fortawesome/free-solid-svg-icons": "6.4.2",
"@fortawesome/vue-fontawesome": "3.0.3",
"@types/bcryptjs": "^2.4.6",
"@types/bootstrap": "~5.2.10",
"@types/bootstrap": "~5.2.9",
"@types/command-exists": "~1.2.3",
"@types/express": "~4.17.21",
"@types/jsonwebtoken": "~9.0.5",
"@typescript-eslint/eslint-plugin": "~6.8.0",
"@typescript-eslint/parser": "~6.8.0",
"@vitejs/plugin-vue": "~4.5.2",
"@vitejs/plugin-vue": "~4.5.0",
"bootstrap": "5.3.2",
"bootstrap-vue-next": "~0.14.10",
"cross-env": "~7.0.3",
@ -81,9 +77,9 @@
"sass": "~1.68.0",
"typescript": "~5.2.2",
"unplugin-vue-components": "~0.25.2",
"vite": "~5.0.7",
"vite": "~5.0.0",
"vite-plugin-compression": "~0.5.1",
"vue": "~3.3.11",
"vue": "~3.3.8",
"vue-eslint-parser": "~9.3.2",
"vue-i18n": "~9.5.0",
"vue-prism-editor": "2.0.0-alpha.2",

1365
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff