From b8f5d8253fac819aac63bace07b4556eea74bf19 Mon Sep 17 00:00:00 2001 From: Dasemu Date: Thu, 8 Oct 2026 01:42:06 +0200 Subject: [PATCH] feat: SQLite-backed installations, REST API, admin auth and web UI Replaces the slice-2 hardcoded installation with a full install/deploy pipeline: - SQLite persistence (better-sqlite3) with a single `installations` table and a draft -> cloning -> building -> built -> starting -> running/error/stopped state machine; env vars and bearer tokens are encrypted at rest (AES-256-GCM, key derived from MASTER_KEY). - Build/run orchestration: git clone (+ .env.example key detection), `nixpacks build` as a logged subprocess, dockerode container run/stop with forced host/port env vars and a TCP-connect health check before flipping an installation to "running". - REST API (installations CRUD, build/deploy/redeploy, logs, token regeneration) behind single-operator session-cookie auth with a basic login lockout. - Vite+React admin SPA: login, installation list, install wizard, and a detail page to configure env vars, trigger build/deploy/redeploy, view the build log, and copy the authenticated /mcp/ URL. - Dockerfile (multi-stage, pulls in nixpacks + the Docker CLI) and docker-compose.yml for production deployment. Validated end-to-end against a real cloned repo: draft creation with env-key detection, build, the error path on a failed health check, and delete, all through the live API. Co-Authored-By: Claude Sonnet 5 --- .env.example | 16 + .gitignore | 1 + Dockerfile | 32 + README.md | 40 + apps/server/package.json | 4 + apps/server/src/api/auth.ts | 50 + apps/server/src/api/installations.ts | 136 ++ apps/server/src/build/git.ts | 46 + apps/server/src/build/nixpacks.ts | 36 + apps/server/src/config/env.ts | 11 + apps/server/src/crypto/secrets.ts | 31 + apps/server/src/db/index.ts | 32 + apps/server/src/db/installations.ts | 192 ++ apps/server/src/docker/containers.ts | 56 +- apps/server/src/index.ts | 36 + apps/server/src/installations.ts | 30 - apps/server/src/middleware/requireAuth.ts | 9 + apps/server/src/proxy/mcpProxy.ts | 10 +- apps/server/src/service/orchestrator.ts | 101 + apps/server/src/types/session.d.ts | 7 + apps/web/index.html | 12 + apps/web/package.json | 22 + apps/web/src/App.tsx | 28 + apps/web/src/api/client.ts | 60 + apps/web/src/main.tsx | 9 + apps/web/src/pages/InstallWizard.tsx | 65 + apps/web/src/pages/InstallationDetail.tsx | 211 +++ apps/web/src/pages/InstallationList.tsx | 61 + apps/web/src/pages/Login.tsx | 42 + apps/web/tsconfig.json | 14 + apps/web/vite.config.ts | 11 + docker-compose.yml | 24 + package-lock.json | 2109 +++++++++++++++++++++ 33 files changed, 3502 insertions(+), 42 deletions(-) create mode 100644 Dockerfile create mode 100644 README.md create mode 100644 apps/server/src/api/auth.ts create mode 100644 apps/server/src/api/installations.ts create mode 100644 apps/server/src/build/git.ts create mode 100644 apps/server/src/build/nixpacks.ts create mode 100644 apps/server/src/crypto/secrets.ts create mode 100644 apps/server/src/db/index.ts create mode 100644 apps/server/src/db/installations.ts delete mode 100644 apps/server/src/installations.ts create mode 100644 apps/server/src/middleware/requireAuth.ts create mode 100644 apps/server/src/service/orchestrator.ts create mode 100644 apps/server/src/types/session.d.ts create mode 100644 apps/web/index.html create mode 100644 apps/web/package.json create mode 100644 apps/web/src/App.tsx create mode 100644 apps/web/src/api/client.ts create mode 100644 apps/web/src/main.tsx create mode 100644 apps/web/src/pages/InstallWizard.tsx create mode 100644 apps/web/src/pages/InstallationDetail.tsx create mode 100644 apps/web/src/pages/InstallationList.tsx create mode 100644 apps/web/src/pages/Login.tsx create mode 100644 apps/web/tsconfig.json create mode 100644 apps/web/vite.config.ts create mode 100644 docker-compose.yml diff --git a/.env.example b/.env.example index d8b4b06..ecfeafd 100644 --- a/.env.example +++ b/.env.example @@ -8,3 +8,19 @@ DOCKER_SOCKET_PATH=/var/run/docker.sock # Name of the shared container network joining the manager and every installed MCP container MCP_NETWORK_NAME=mcp-net + +# Where the manager stores its SQLite DB, cloned repos and build logs +DATA_DIR=./data + +# Public base URL used to build the authenticated MCP links shown in the UI, e.g. https://mcp.example.com +PUBLIC_BASE_URL=http://localhost:3000 + +# Password for the single admin/operator account that can use the panel +ADMIN_PASSWORD=change-me + +# Random secret used to sign the admin session cookie +SESSION_SECRET=change-me-too + +# Random string (min 32 chars) used to derive the AES-256-GCM key that encrypts +# installation env vars and bearer tokens at rest +MASTER_KEY=change-me-to-a-random-string-of-at-least-32-characters diff --git a/.gitignore b/.gitignore index ddc0ba2..4212157 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ dist/ data/ .env *.log +*.tsbuildinfo diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..f20584c --- /dev/null +++ b/Dockerfile @@ -0,0 +1,32 @@ +FROM ghcr.io/railwayapp/nixpacks:1.41.0 AS nixpacks +FROM docker:27-cli AS dockercli + +FROM node:20-slim AS build +WORKDIR /repo +COPY package.json package-lock.json ./ +COPY apps/server/package.json apps/server/package.json +COPY apps/web/package.json apps/web/package.json +RUN npm ci +COPY apps/server apps/server +COPY apps/web apps/web +RUN npm run build --workspace apps/web +RUN npm run build --workspace apps/server + +FROM node:20-slim +RUN apt-get update && apt-get install -y --no-install-recommends git ca-certificates \ + && rm -rf /var/lib/apt/lists/* +COPY --from=nixpacks /usr/bin/nixpacks /usr/local/bin/nixpacks +COPY --from=dockercli /usr/local/bin/docker /usr/local/bin/docker + +WORKDIR /repo +COPY package.json package-lock.json ./ +COPY apps/server/package.json apps/server/package.json +RUN npm ci --omit=dev --workspace apps/server + +COPY --from=build /repo/apps/server/dist apps/server/dist +COPY --from=build /repo/apps/web/dist apps/web/dist + +WORKDIR /repo/apps/server +ENV NODE_ENV=production +EXPOSE 3000 +CMD ["node", "dist/index.js"] diff --git a/README.md b/README.md new file mode 100644 index 0000000..fae4b1c --- /dev/null +++ b/README.md @@ -0,0 +1,40 @@ +# remote-mcp-manager + +Self-hosted manager for MCP servers: install one from a GitHub repo URL, configure +its env vars, build it with [Nixpacks](https://nixpacks.com) (no Dockerfile needed +in the source repo), run it in a container, and get back an authenticated +`https:///mcp/` URL guarded by a bearer token. + +## Quick start (production, Docker) + +```sh +cp .env.example .env # fill in ADMIN_PASSWORD, SESSION_SECRET, MASTER_KEY, PUBLIC_BASE_URL +docker compose up -d --build +``` + +Put your own reverse proxy (nginx, etc.) in front of port 3000 for TLS and your public domain. + +## Local development (Podman) + +Requires `nixpacks` and a `docker` CLI pointed at your Podman socket (e.g. via the +`podman-docker` package) on your PATH. + +```sh +npm install +DOCKER_SOCKET_PATH=/run/user/$(id -u)/podman/podman.sock \ +DATA_DIR=./data MASTER_KEY=... SESSION_SECRET=... ADMIN_PASSWORD=... \ + npm run dev:server +``` + +Because rootless Podman isolates the host network namespace from the container bridge, +the manager itself must run as a container attached to `mcp-net` to reach the MCP +containers it manages — running it directly on the host only works for routes that +don't need to reach a sibling container (e.g. `/health`, `/api/auth`). + +## Known limitations + +- Nixpacks needs a dependency manifest (`requirements.txt`/`pyproject.toml` for Python, + `package.json` for Node) in the installed repo. Repos without one will fail to build; + the full build log is always visible in the UI to diagnose this. +- No per-container resource limits, image garbage collection, or multi-user support yet. +- Builds run one at a time. diff --git a/apps/server/package.json b/apps/server/package.json index 06a0cbd..a72672d 100644 --- a/apps/server/package.json +++ b/apps/server/package.json @@ -8,13 +8,17 @@ "start": "node dist/index.js" }, "dependencies": { + "better-sqlite3": "11.10.0", "dockerode": "4.0.9", "express": "4.21.2", + "express-session": "1.18.1", "http-proxy": "1.18.1" }, "devDependencies": { + "@types/better-sqlite3": "7.6.13", "@types/dockerode": "3.3.42", "@types/express": "4.17.23", + "@types/express-session": "1.18.2", "@types/http-proxy": "1.17.16", "@types/node": "24.9.1", "tsx": "4.20.6", diff --git a/apps/server/src/api/auth.ts b/apps/server/src/api/auth.ts new file mode 100644 index 0000000..cd13866 --- /dev/null +++ b/apps/server/src/api/auth.ts @@ -0,0 +1,50 @@ +import crypto from "node:crypto"; +import { Router } from "express"; +import { config } from "../config/env.js"; + +export const authRouter = Router(); + +function passwordsMatch(expected: string, provided: string): boolean { + const a = Buffer.from(expected); + const b = Buffer.from(provided); + if (a.length !== b.length) return false; + return crypto.timingSafeEqual(a, b); +} + +let failedAttempts = 0; +let lockoutUntil = 0; + +authRouter.post("/login", (req, res) => { + if (Date.now() < lockoutUntil) { + res.status(429).json({ error: "too many attempts, try again later" }); + return; + } + + const { password } = req.body ?? {}; + if (!config.adminPassword) { + res.status(500).json({ error: "ADMIN_PASSWORD is not configured" }); + return; + } + + if (typeof password !== "string" || !passwordsMatch(config.adminPassword, password)) { + failedAttempts += 1; + if (failedAttempts >= 5) { + lockoutUntil = Date.now() + 60_000; + failedAttempts = 0; + } + res.status(401).json({ error: "invalid password" }); + return; + } + + failedAttempts = 0; + req.session.authenticated = true; + res.json({ ok: true }); +}); + +authRouter.post("/logout", (req, res) => { + req.session.destroy(() => res.json({ ok: true })); +}); + +authRouter.get("/session", (req, res) => { + res.json({ authenticated: Boolean(req.session.authenticated) }); +}); diff --git a/apps/server/src/api/installations.ts b/apps/server/src/api/installations.ts new file mode 100644 index 0000000..024d25e --- /dev/null +++ b/apps/server/src/api/installations.ts @@ -0,0 +1,136 @@ +import { Router } from "express"; +import { config } from "../config/env.js"; +import { + type Installation, + listInstallations, + getInstallationById, + deleteInstallation, + updateInstallation, +} from "../db/installations.js"; +import { + createDraftInstallation, + configureInstallation, + buildInstallation, + deployInstallation, + redeployInstallation, + removeInstallationContainer, +} from "../service/orchestrator.js"; +import { buildLogPath } from "../build/nixpacks.js"; +import { generateBearerToken } from "../crypto/secrets.js"; +import fs from "node:fs"; + +export const installationsRouter = Router(); + +// Not masked: this API sits behind single-operator session auth (requireAuth), and the +// operator is the same person who owns these secrets — editing them requires seeing them. +function toPublicJson(installation: Installation) { + return { + ...installation, + publicUrl: `${config.publicBaseUrl}/mcp/${installation.slug}`, + }; +} + +installationsRouter.get("/", (_req, res) => { + res.json(listInstallations().map(toPublicJson)); +}); + +installationsRouter.get("/:id", (req, res) => { + const installation = getInstallationById(req.params.id); + if (!installation) { + res.status(404).json({ error: "not found" }); + return; + } + res.json(toPublicJson(installation)); +}); + +installationsRouter.post("/", async (req, res) => { + const { name, repoUrl, gitRef } = req.body ?? {}; + if (!name || !repoUrl) { + res.status(400).json({ error: "name and repoUrl are required" }); + return; + } + try { + const { installation, detectedEnvKeys } = await createDraftInstallation({ name, repoUrl, gitRef }); + res.status(201).json({ installation: toPublicJson(installation), detectedEnvKeys }); + } catch (err) { + res.status(500).json({ error: (err as Error).message }); + } +}); + +installationsRouter.patch("/:id/config", (req, res) => { + const { env, portEnvVar, hostEnvVar, httpPath, internalPort } = req.body ?? {}; + try { + const installation = configureInstallation(req.params.id, { env, portEnvVar, hostEnvVar, httpPath, internalPort }); + res.json(toPublicJson(installation)); + } catch (err) { + res.status(404).json({ error: (err as Error).message }); + } +}); + +installationsRouter.post("/:id/build", async (req, res) => { + try { + const installation = await buildInstallation(req.params.id); + res.json(toPublicJson(installation)); + } catch (err) { + res.status(500).json({ error: (err as Error).message }); + } +}); + +installationsRouter.post("/:id/deploy", async (req, res) => { + try { + const installation = await deployInstallation(req.params.id); + res.json(toPublicJson(installation)); + } catch (err) { + res.status(500).json({ error: (err as Error).message }); + } +}); + +installationsRouter.post("/:id/redeploy", async (req, res) => { + const rebuild = Boolean(req.body?.rebuild); + try { + const installation = await redeployInstallation(req.params.id, { rebuild }); + res.json(toPublicJson(installation)); + } catch (err) { + res.status(500).json({ error: (err as Error).message }); + } +}); + +installationsRouter.get("/:id/logs", (req, res) => { + const installation = getInstallationById(req.params.id); + if (!installation) { + res.status(404).json({ error: "not found" }); + return; + } + + const dir = `${config.logsDir}/${installation.slug}`; + if (!fs.existsSync(dir)) { + res.json({ log: "" }); + return; + } + const files = fs.readdirSync(dir).sort(); + const latest = files.at(-1); + if (!latest) { + res.json({ log: "" }); + return; + } + res.json({ log: fs.readFileSync(buildLogPath(installation.slug, latest.replace(/\.log$/, "")), "utf8") }); +}); + +installationsRouter.delete("/:id", async (req, res) => { + try { + await removeInstallationContainer(req.params.id); + deleteInstallation(req.params.id); + res.status(204).end(); + } catch (err) { + res.status(500).json({ error: (err as Error).message }); + } +}); + +installationsRouter.post("/:id/regenerate-token", (req, res) => { + try { + const installation = updateInstallation(req.params.id, { bearerToken: generateBearerToken() }); + res.json({ bearerToken: installation.bearerToken }); + } catch (err) { + res.status(404).json({ error: (err as Error).message }); + } +}); diff --git a/apps/server/src/build/git.ts b/apps/server/src/build/git.ts new file mode 100644 index 0000000..46acbde --- /dev/null +++ b/apps/server/src/build/git.ts @@ -0,0 +1,46 @@ +import fs from "node:fs"; +import { spawn } from "node:child_process"; +import path from "node:path"; +import { config } from "../config/env.js"; + +function run(cmd: string, args: string[], cwd?: string): Promise<{ code: number; output: string }> { + return new Promise((resolve, reject) => { + const child = spawn(cmd, args, { cwd }); + let output = ""; + child.stdout.on("data", (d) => (output += d.toString())); + child.stderr.on("data", (d) => (output += d.toString())); + child.on("error", reject); + child.on("close", (code) => resolve({ code: code ?? 1, output })); + }); +} + +export function repoDir(slug: string): string { + return path.join(config.reposDir, slug); +} + +export async function cloneOrUpdateRepo(slug: string, repoUrl: string, gitRef?: string | null): Promise { + const dir = repoDir(slug); + fs.mkdirSync(config.reposDir, { recursive: true }); + + if (fs.existsSync(path.join(dir, ".git"))) { + fs.rmSync(dir, { recursive: true, force: true }); + } + + const clone = await run("git", ["clone", "--depth", "1", ...(gitRef ? ["--branch", gitRef] : []), repoUrl, dir]); + if (clone.code !== 0) { + throw new Error(`git clone failed: ${clone.output}`); + } + + return dir; +} + +export function readEnvExampleKeys(dir: string): string[] { + const file = path.join(dir, ".env.example"); + if (!fs.existsSync(file)) return []; + const content = fs.readFileSync(file, "utf8"); + return content + .split("\n") + .map((line) => line.trim()) + .filter((line) => line && !line.startsWith("#") && line.includes("=")) + .map((line) => line.split("=")[0]!.trim()); +} diff --git a/apps/server/src/build/nixpacks.ts b/apps/server/src/build/nixpacks.ts new file mode 100644 index 0000000..b436db9 --- /dev/null +++ b/apps/server/src/build/nixpacks.ts @@ -0,0 +1,36 @@ +import fs from "node:fs"; +import path from "node:path"; +import { spawn } from "node:child_process"; +import { config } from "../config/env.js"; + +export function buildLogPath(slug: string, buildId: string): string { + return path.join(config.logsDir, slug, `${buildId}.log`); +} + +export interface BuildResult { + success: boolean; + logPath: string; +} + +export function runNixpacksBuild(slug: string, repoDir: string, imageTag: string): Promise { + return new Promise((resolve) => { + const buildId = Date.now().toString(); + const logPath = buildLogPath(slug, buildId); + fs.mkdirSync(path.dirname(logPath), { recursive: true }); + const logStream = fs.createWriteStream(logPath, { flags: "a" }); + + const child = spawn("nixpacks", ["build", repoDir, "--name", imageTag]); + child.stdout.pipe(logStream, { end: false }); + child.stderr.pipe(logStream, { end: false }); + + child.on("close", (code) => { + logStream.end(); + resolve({ success: code === 0, logPath }); + }); + child.on("error", (err) => { + logStream.write(`\nfailed to spawn nixpacks: ${err.message}\n`); + logStream.end(); + resolve({ success: false, logPath }); + }); + }); +} diff --git a/apps/server/src/config/env.ts b/apps/server/src/config/env.ts index 9bb2f3a..ccc3eed 100644 --- a/apps/server/src/config/env.ts +++ b/apps/server/src/config/env.ts @@ -1,6 +1,17 @@ +import path from "node:path"; + +const dataDir = process.env.DATA_DIR ?? path.resolve(process.cwd(), "../../data"); + export const config = { port: Number(process.env.PORT ?? 3000), // Docker in prod; a rootless Podman socket (e.g. /run/user//podman/podman.sock) in local dev. dockerSocketPath: process.env.DOCKER_SOCKET_PATH ?? "/var/run/docker.sock", mcpNetworkName: process.env.MCP_NETWORK_NAME ?? "mcp-net", + dataDir, + dbPath: process.env.DB_PATH ?? path.join(dataDir, "db.sqlite"), + reposDir: path.join(dataDir, "repos"), + logsDir: path.join(dataDir, "logs"), + publicBaseUrl: process.env.PUBLIC_BASE_URL ?? `http://localhost:${process.env.PORT ?? 3000}`, + adminPassword: process.env.ADMIN_PASSWORD, + sessionSecret: process.env.SESSION_SECRET, }; diff --git a/apps/server/src/crypto/secrets.ts b/apps/server/src/crypto/secrets.ts new file mode 100644 index 0000000..f5dfc17 --- /dev/null +++ b/apps/server/src/crypto/secrets.ts @@ -0,0 +1,31 @@ +import crypto from "node:crypto"; + +function masterKey(): Buffer { + const raw = process.env.MASTER_KEY; + if (!raw || raw.length < 32) { + throw new Error("MASTER_KEY env var must be set to a string of at least 32 characters"); + } + return crypto.createHash("sha256").update(raw).digest(); +} + +export function encryptSecret(plaintext: string): string { + const iv = crypto.randomBytes(12); + const cipher = crypto.createCipheriv("aes-256-gcm", masterKey(), iv); + const ciphertext = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()]); + const authTag = cipher.getAuthTag(); + return Buffer.concat([iv, authTag, ciphertext]).toString("base64"); +} + +export function decryptSecret(encoded: string): string { + const raw = Buffer.from(encoded, "base64"); + const iv = raw.subarray(0, 12); + const authTag = raw.subarray(12, 28); + const ciphertext = raw.subarray(28); + const decipher = crypto.createDecipheriv("aes-256-gcm", masterKey(), iv); + decipher.setAuthTag(authTag); + return Buffer.concat([decipher.update(ciphertext), decipher.final()]).toString("utf8"); +} + +export function generateBearerToken(): string { + return crypto.randomBytes(32).toString("base64url"); +} diff --git a/apps/server/src/db/index.ts b/apps/server/src/db/index.ts new file mode 100644 index 0000000..575cc17 --- /dev/null +++ b/apps/server/src/db/index.ts @@ -0,0 +1,32 @@ +import fs from "node:fs"; +import path from "node:path"; +import Database from "better-sqlite3"; +import { config } from "../config/env.js"; + +fs.mkdirSync(path.dirname(config.dbPath), { recursive: true }); + +export const db = new Database(config.dbPath); +db.pragma("journal_mode = WAL"); + +db.exec(` +CREATE TABLE IF NOT EXISTS installations ( + id TEXT PRIMARY KEY, + slug TEXT UNIQUE NOT NULL, + name TEXT NOT NULL, + repo_url TEXT NOT NULL, + git_ref TEXT, + status TEXT NOT NULL, + status_detail TEXT, + image_tag TEXT, + container_id TEXT, + container_name TEXT NOT NULL, + internal_port INTEGER NOT NULL DEFAULT 8080, + http_path TEXT NOT NULL DEFAULT '/mcp', + port_env_var TEXT NOT NULL DEFAULT 'PORT', + host_env_var TEXT NOT NULL DEFAULT 'HOST', + bearer_token_encrypted TEXT, + env_json_encrypted TEXT, + created_at TEXT NOT NULL, + updated_at TEXT NOT NULL +); +`); diff --git a/apps/server/src/db/installations.ts b/apps/server/src/db/installations.ts new file mode 100644 index 0000000..04280ff --- /dev/null +++ b/apps/server/src/db/installations.ts @@ -0,0 +1,192 @@ +import crypto from "node:crypto"; +import { db } from "./index.js"; +import { encryptSecret, decryptSecret } from "../crypto/secrets.js"; + +export type InstallationStatus = + | "draft" + | "cloning" + | "building" + | "built" + | "starting" + | "running" + | "stopped" + | "error"; + +export interface Installation { + id: string; + slug: string; + name: string; + repoUrl: string; + gitRef: string | null; + status: InstallationStatus; + statusDetail: string | null; + imageTag: string | null; + containerId: string | null; + containerName: string; + internalPort: number; + httpPath: string; + portEnvVar: string; + hostEnvVar: string; + env: Record; + bearerToken: string | null; + createdAt: string; + updatedAt: string; +} + +interface Row { + id: string; + slug: string; + name: string; + repo_url: string; + git_ref: string | null; + status: InstallationStatus; + status_detail: string | null; + image_tag: string | null; + container_id: string | null; + container_name: string; + internal_port: number; + http_path: string; + port_env_var: string; + host_env_var: string; + bearer_token_encrypted: string | null; + env_json_encrypted: string | null; + created_at: string; + updated_at: string; +} + +function rowToInstallation(row: Row): Installation { + return { + id: row.id, + slug: row.slug, + name: row.name, + repoUrl: row.repo_url, + gitRef: row.git_ref, + status: row.status, + statusDetail: row.status_detail, + imageTag: row.image_tag, + containerId: row.container_id, + containerName: row.container_name, + internalPort: row.internal_port, + httpPath: row.http_path, + portEnvVar: row.port_env_var, + hostEnvVar: row.host_env_var, + env: row.env_json_encrypted ? JSON.parse(decryptSecret(row.env_json_encrypted)) : {}, + bearerToken: row.bearer_token_encrypted ? decryptSecret(row.bearer_token_encrypted) : null, + createdAt: row.created_at, + updatedAt: row.updated_at, + }; +} + +export function slugify(name: string): string { + return name + .toLowerCase() + .normalize("NFKD") + .replace(/[̀-ͯ]/g, "") + .replace(/[^a-z0-9]+/g, "-") + .replace(/^-+|-+$/g, "") + .slice(0, 48); +} + +export function createInstallation(input: { name: string; slug: string; repoUrl: string; gitRef?: string }): Installation { + const id = crypto.randomUUID(); + const now = new Date().toISOString(); + db.prepare( + `INSERT INTO installations + (id, slug, name, repo_url, git_ref, status, container_name, created_at, updated_at) + VALUES (@id, @slug, @name, @repoUrl, @gitRef, 'draft', @containerName, @now, @now)`, + ).run({ + id, + slug: input.slug, + name: input.name, + repoUrl: input.repoUrl, + gitRef: input.gitRef ?? null, + containerName: `mcp-${input.slug}`, + now, + }); + return getInstallationById(id)!; +} + +export function getInstallationById(id: string): Installation | undefined { + const row = db.prepare("SELECT * FROM installations WHERE id = ?").get(id) as Row | undefined; + return row ? rowToInstallation(row) : undefined; +} + +export function getInstallationBySlug(slug: string): Installation | undefined { + const row = db.prepare("SELECT * FROM installations WHERE slug = ?").get(slug) as Row | undefined; + return row ? rowToInstallation(row) : undefined; +} + +export function listInstallations(): Installation[] { + const rows = db.prepare("SELECT * FROM installations ORDER BY created_at DESC").all() as Row[]; + return rows.map(rowToInstallation); +} + +export interface InstallationUpdate { + status?: InstallationStatus; + statusDetail?: string | null; + imageTag?: string | null; + containerId?: string | null; + internalPort?: number; + httpPath?: string; + portEnvVar?: string; + hostEnvVar?: string; + env?: Record; + bearerToken?: string | null; + gitRef?: string | null; +} + +export function updateInstallation(id: string, patch: InstallationUpdate): Installation { + const current = getInstallationById(id); + if (!current) throw new Error(`installation ${id} not found`); + + const next = { + status: patch.status ?? current.status, + statusDetail: patch.statusDetail !== undefined ? patch.statusDetail : current.statusDetail, + imageTag: patch.imageTag !== undefined ? patch.imageTag : current.imageTag, + containerId: patch.containerId !== undefined ? patch.containerId : current.containerId, + internalPort: patch.internalPort ?? current.internalPort, + httpPath: patch.httpPath ?? current.httpPath, + portEnvVar: patch.portEnvVar ?? current.portEnvVar, + hostEnvVar: patch.hostEnvVar ?? current.hostEnvVar, + env: patch.env ?? current.env, + bearerToken: patch.bearerToken !== undefined ? patch.bearerToken : current.bearerToken, + gitRef: patch.gitRef !== undefined ? patch.gitRef : current.gitRef, + }; + + db.prepare( + `UPDATE installations SET + status = @status, + status_detail = @statusDetail, + image_tag = @imageTag, + container_id = @containerId, + internal_port = @internalPort, + http_path = @httpPath, + port_env_var = @portEnvVar, + host_env_var = @hostEnvVar, + env_json_encrypted = @envJsonEncrypted, + bearer_token_encrypted = @bearerTokenEncrypted, + git_ref = @gitRef, + updated_at = @updatedAt + WHERE id = @id`, + ).run({ + id, + status: next.status, + statusDetail: next.statusDetail, + imageTag: next.imageTag, + containerId: next.containerId, + internalPort: next.internalPort, + httpPath: next.httpPath, + portEnvVar: next.portEnvVar, + hostEnvVar: next.hostEnvVar, + envJsonEncrypted: encryptSecret(JSON.stringify(next.env)), + bearerTokenEncrypted: next.bearerToken ? encryptSecret(next.bearerToken) : null, + gitRef: next.gitRef, + updatedAt: new Date().toISOString(), + }); + + return getInstallationById(id)!; +} + +export function deleteInstallation(id: string): void { + db.prepare("DELETE FROM installations WHERE id = ?").run(id); +} diff --git a/apps/server/src/docker/containers.ts b/apps/server/src/docker/containers.ts index ee49d41..e94ee6a 100644 --- a/apps/server/src/docker/containers.ts +++ b/apps/server/src/docker/containers.ts @@ -1,6 +1,7 @@ +import net from "node:net"; import Docker from "dockerode"; import { config } from "../config/env.js"; -import type { Installation } from "../installations.js"; +import type { Installation } from "../db/installations.js"; const docker = new Docker({ socketPath: config.dockerSocketPath }); @@ -11,15 +12,19 @@ export async function ensureNetwork(name: string): Promise { } } -export async function runInstallationContainer(installation: Installation): Promise { - await ensureNetwork(config.mcpNetworkName); - - const existing = docker.getContainer(installation.containerName); +export async function removeContainerIfExists(containerName: string): Promise { try { - await existing.remove({ force: true }); + await docker.getContainer(containerName).remove({ force: true }); } catch { - // container didn't exist yet, nothing to clean up + // didn't exist, nothing to clean up } +} + +export async function runInstallationContainer(installation: Installation): Promise { + if (!installation.imageTag) throw new Error("installation has no built image yet"); + + await ensureNetwork(config.mcpNetworkName); + await removeContainerIfExists(installation.containerName); const env = { ...installation.env, @@ -38,6 +43,11 @@ export async function runInstallationContainer(installation: Installation): Prom }); await container.start(); + return container.id; +} + +export async function stopInstallationContainer(containerName: string): Promise { + await removeContainerIfExists(containerName); } export async function getContainerIp(containerName: string): Promise { @@ -49,3 +59,35 @@ export async function getContainerIp(containerName: string): Promise { } return network.IPAddress; } + +export async function waitForContainerHealth( + installation: Installation, + timeoutMs = 20_000, +): Promise { + const deadline = Date.now() + timeoutMs; + let lastError: Error | undefined; + + while (Date.now() < deadline) { + try { + const ip = await getContainerIp(installation.containerName); + await new Promise((resolve, reject) => { + const socket = net.createConnection({ host: ip, port: installation.internalPort, timeout: 1000 }); + socket.once("connect", () => { + socket.end(); + resolve(); + }); + socket.once("error", reject); + socket.once("timeout", () => reject(new Error("connect timeout"))); + }); + return; + } catch (err) { + lastError = err as Error; + await new Promise((r) => setTimeout(r, 1000)); + } + } + + throw new Error( + `container did not start listening on ${installation.portEnvVar}=${installation.internalPort} within ${timeoutMs}ms` + + (lastError ? ` (last error: ${lastError.message})` : ""), + ); +} diff --git a/apps/server/src/index.ts b/apps/server/src/index.ts index 71b624a..3aa669c 100644 --- a/apps/server/src/index.ts +++ b/apps/server/src/index.ts @@ -1,13 +1,49 @@ +import path from "node:path"; import express from "express"; +import session from "express-session"; import { config } from "./config/env.js"; +import "./db/index.js"; import { handleMcpProxyRequest } from "./proxy/mcpProxy.js"; +import { authRouter } from "./api/auth.js"; +import { installationsRouter } from "./api/installations.js"; +import { requireAuth } from "./middleware/requireAuth.js"; + +if (!config.sessionSecret) { + throw new Error("SESSION_SECRET env var must be set"); +} const app = express(); +app.set("trust proxy", 1); +app.use(express.json()); +app.use( + session({ + secret: config.sessionSecret, + resave: false, + saveUninitialized: false, + cookie: { + httpOnly: true, + secure: process.env.NODE_ENV === "production", + maxAge: 1000 * 60 * 60 * 12, + }, + }), +); app.get("/health", (_req, res) => res.json({ ok: true })); +// Bearer-token-gated MCP reverse proxy, public (auth handled per-installation). app.all("/mcp/:slug", handleMcpProxyRequest); +app.use("/api/auth", authRouter); +app.use("/api/installations", requireAuth, installationsRouter); + +const webDist = path.resolve(import.meta.dirname, "../../web/dist"); +app.use(express.static(webDist)); +app.get(/^\/(?!api|mcp|health).*/, (_req, res) => { + res.sendFile(path.join(webDist, "index.html"), (err) => { + if (err) res.status(404).end(); + }); +}); + app.listen(config.port, () => { console.log(`remote-mcp-manager listening on http://localhost:${config.port}`); }); diff --git a/apps/server/src/installations.ts b/apps/server/src/installations.ts deleted file mode 100644 index 2f2e1b5..0000000 --- a/apps/server/src/installations.ts +++ /dev/null @@ -1,30 +0,0 @@ -export interface Installation { - slug: string; - imageTag: string; - containerName: string; - internalPort: number; - httpPath: string; - portEnvVar: string; - hostEnvVar: string; - env: Record; - bearerToken: string; -} - -// Slice 2: hardcoded registry, replaced by SQLite-backed storage in slice 3. -export const installations: Installation[] = [ - { - slug: "social", - imageTag: "localhost/mcpmgr-test-social:latest", - containerName: "mcp-social", - internalPort: 8080, - httpPath: "/mcp", - portEnvVar: "PORT", - hostEnvVar: "HOST", - env: {}, - bearerToken: "dev-slice2-token", - }, -]; - -export function findInstallationBySlug(slug: string): Installation | undefined { - return installations.find((i) => i.slug === slug); -} diff --git a/apps/server/src/middleware/requireAuth.ts b/apps/server/src/middleware/requireAuth.ts new file mode 100644 index 0000000..2925c75 --- /dev/null +++ b/apps/server/src/middleware/requireAuth.ts @@ -0,0 +1,9 @@ +import type { Request, Response, NextFunction } from "express"; + +export function requireAuth(req: Request, res: Response, next: NextFunction): void { + if (req.session.authenticated) { + next(); + return; + } + res.status(401).json({ error: "authentication required" }); +} diff --git a/apps/server/src/proxy/mcpProxy.ts b/apps/server/src/proxy/mcpProxy.ts index 19b47c0..90c60d9 100644 --- a/apps/server/src/proxy/mcpProxy.ts +++ b/apps/server/src/proxy/mcpProxy.ts @@ -1,12 +1,12 @@ import crypto from "node:crypto"; import type { Request, Response } from "express"; import httpProxy from "http-proxy"; -import { findInstallationBySlug } from "../installations.js"; +import { getInstallationBySlug } from "../db/installations.js"; import { getContainerIp } from "../docker/containers.js"; const proxy = httpProxy.createProxyServer(); proxy.on("error", (err, _req, res) => { - if (!res.headersSent) { + if (!(res as Response).headersSent) { (res as Response).status(502).json({ error: "proxy error", detail: err.message }); } }); @@ -20,9 +20,9 @@ function tokensMatch(expected: string, provided: string): boolean { export async function handleMcpProxyRequest(req: Request, res: Response): Promise { const { slug } = req.params; - const installation = findInstallationBySlug(slug); - if (!installation) { - res.status(404).json({ error: "unknown installation" }); + const installation = getInstallationBySlug(slug); + if (!installation || installation.status !== "running" || !installation.bearerToken) { + res.status(404).json({ error: "unknown or unavailable installation" }); return; } diff --git a/apps/server/src/service/orchestrator.ts b/apps/server/src/service/orchestrator.ts new file mode 100644 index 0000000..89ed802 --- /dev/null +++ b/apps/server/src/service/orchestrator.ts @@ -0,0 +1,101 @@ +import { + type Installation, + createInstallation, + updateInstallation, + getInstallationById, + getInstallationBySlug, + slugify, +} from "../db/installations.js"; +import { cloneOrUpdateRepo, readEnvExampleKeys, repoDir } from "../build/git.js"; +import { runNixpacksBuild } from "../build/nixpacks.js"; +import { runInstallationContainer, stopInstallationContainer, waitForContainerHealth } from "../docker/containers.js"; +import { generateBearerToken } from "../crypto/secrets.js"; + +export interface DraftResult { + installation: Installation; + detectedEnvKeys: string[]; +} + +export async function createDraftInstallation(input: { + name: string; + repoUrl: string; + gitRef?: string; +}): Promise { + const slug = slugify(input.name); + if (getInstallationBySlug(slug)) { + throw new Error(`an installation with slug "${slug}" already exists`); + } + const installation = createInstallation({ name: input.name, slug, repoUrl: input.repoUrl, gitRef: input.gitRef }); + + try { + updateInstallation(installation.id, { status: "cloning" }); + const dir = await cloneOrUpdateRepo(slug, input.repoUrl, input.gitRef); + const detectedEnvKeys = readEnvExampleKeys(dir); + const updated = updateInstallation(installation.id, { status: "draft", statusDetail: null }); + return { installation: updated, detectedEnvKeys }; + } catch (err) { + const updated = updateInstallation(installation.id, { status: "error", statusDetail: (err as Error).message }); + return { installation: updated, detectedEnvKeys: [] }; + } +} + +export function configureInstallation( + id: string, + patch: { + env?: Record; + portEnvVar?: string; + hostEnvVar?: string; + httpPath?: string; + internalPort?: number; + }, +): Installation { + return updateInstallation(id, patch); +} + +export async function buildInstallation(id: string): Promise { + const installation = getInstallationById(id); + if (!installation) throw new Error("installation not found"); + + updateInstallation(id, { status: "building", statusDetail: null }); + const imageTag = `mcpmgr/${installation.slug}:latest`; + const result = await runNixpacksBuild(installation.slug, repoDir(installation.slug), imageTag); + + if (!result.success) { + return updateInstallation(id, { + status: "error", + statusDetail: `build failed, see log at ${result.logPath}`, + }); + } + + return updateInstallation(id, { status: "built", statusDetail: null, imageTag }); +} + +export async function deployInstallation(id: string): Promise { + let installation = getInstallationById(id); + if (!installation) throw new Error("installation not found"); + + updateInstallation(id, { status: "starting", statusDetail: null }); + + try { + await runInstallationContainer(installation); + await waitForContainerHealth(installation); + + const bearerToken = installation.bearerToken ?? generateBearerToken(); + return updateInstallation(id, { status: "running", statusDetail: null, bearerToken }); + } catch (err) { + return updateInstallation(id, { status: "error", statusDetail: (err as Error).message }); + } +} + +export async function redeployInstallation(id: string, opts: { rebuild?: boolean } = {}): Promise { + if (opts.rebuild) { + await buildInstallation(id); + } + return deployInstallation(id); +} + +export async function removeInstallationContainer(id: string): Promise { + const installation = getInstallationById(id); + if (!installation) return; + await stopInstallationContainer(installation.containerName); +} diff --git a/apps/server/src/types/session.d.ts b/apps/server/src/types/session.d.ts new file mode 100644 index 0000000..bf0d894 --- /dev/null +++ b/apps/server/src/types/session.d.ts @@ -0,0 +1,7 @@ +import "express-session"; + +declare module "express-session" { + interface SessionData { + authenticated?: boolean; + } +} diff --git a/apps/web/index.html b/apps/web/index.html new file mode 100644 index 0000000..2a1192a --- /dev/null +++ b/apps/web/index.html @@ -0,0 +1,12 @@ + + + + + + remote-mcp-manager + + +
+ + + diff --git a/apps/web/package.json b/apps/web/package.json new file mode 100644 index 0000000..e01062e --- /dev/null +++ b/apps/web/package.json @@ -0,0 +1,22 @@ +{ + "name": "@remote-mcp-manager/web", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc -b && vite build", + "preview": "vite preview" + }, + "dependencies": { + "react": "18.3.1", + "react-dom": "18.3.1", + "react-router-dom": "6.30.1" + }, + "devDependencies": { + "@types/react": "18.3.26", + "@types/react-dom": "18.3.7", + "@vitejs/plugin-react": "4.3.4", + "typescript": "5.9.3", + "vite": "5.4.20" + } +} diff --git a/apps/web/src/App.tsx b/apps/web/src/App.tsx new file mode 100644 index 0000000..b84219a --- /dev/null +++ b/apps/web/src/App.tsx @@ -0,0 +1,28 @@ +import { useEffect, useState } from "react"; +import { BrowserRouter, Routes, Route } from "react-router-dom"; +import { api } from "./api/client"; +import { Login } from "./pages/Login"; +import { InstallationList } from "./pages/InstallationList"; +import { InstallationDetail } from "./pages/InstallationDetail"; + +export function App() { + const [authenticated, setAuthenticated] = useState(null); + + useEffect(() => { + api.session().then((s) => setAuthenticated(s.authenticated)); + }, []); + + if (authenticated === null) return null; + if (!authenticated) return setAuthenticated(true)} />; + + return ( + +
+ + } /> + } /> + +
+
+ ); +} diff --git a/apps/web/src/api/client.ts b/apps/web/src/api/client.ts new file mode 100644 index 0000000..9cceb2b --- /dev/null +++ b/apps/web/src/api/client.ts @@ -0,0 +1,60 @@ +export interface Installation { + id: string; + slug: string; + name: string; + repoUrl: string; + gitRef: string | null; + status: "draft" | "cloning" | "building" | "built" | "starting" | "running" | "stopped" | "error"; + statusDetail: string | null; + imageTag: string | null; + containerName: string; + internalPort: number; + httpPath: string; + portEnvVar: string; + hostEnvVar: string; + env: Record; + bearerToken: string | null; + publicUrl: string; + createdAt: string; + updatedAt: string; +} + +async function request(path: string, init?: RequestInit): Promise { + const res = await fetch(path, { + ...init, + credentials: "include", + headers: { "Content-Type": "application/json", ...(init?.headers ?? {}) }, + }); + if (!res.ok) { + const body = await res.json().catch(() => ({ error: res.statusText })); + throw new Error(body.error ?? `request failed with ${res.status}`); + } + if (res.status === 204) return undefined as T; + return res.json(); +} + +export const api = { + session: () => request<{ authenticated: boolean }>("/api/auth/session"), + login: (password: string) => request<{ ok: true }>("/api/auth/login", { method: "POST", body: JSON.stringify({ password }) }), + logout: () => request<{ ok: true }>("/api/auth/logout", { method: "POST" }), + + listInstallations: () => request("/api/installations"), + getInstallation: (id: string) => request(`/api/installations/${id}`), + createInstallation: (input: { name: string; repoUrl: string; gitRef?: string }) => + request<{ installation: Installation; detectedEnvKeys: string[] }>("/api/installations", { + method: "POST", + body: JSON.stringify(input), + }), + configureInstallation: ( + id: string, + patch: { env?: Record; portEnvVar?: string; hostEnvVar?: string; httpPath?: string }, + ) => request(`/api/installations/${id}/config`, { method: "PATCH", body: JSON.stringify(patch) }), + build: (id: string) => request(`/api/installations/${id}/build`, { method: "POST" }), + deploy: (id: string) => request(`/api/installations/${id}/deploy`, { method: "POST" }), + redeploy: (id: string, rebuild: boolean) => + request(`/api/installations/${id}/redeploy`, { method: "POST", body: JSON.stringify({ rebuild }) }), + regenerateToken: (id: string) => + request<{ bearerToken: string }>(`/api/installations/${id}/regenerate-token`, { method: "POST" }), + remove: (id: string) => request(`/api/installations/${id}`, { method: "DELETE" }), + logs: (id: string) => request<{ log: string }>(`/api/installations/${id}/logs`), +}; diff --git a/apps/web/src/main.tsx b/apps/web/src/main.tsx new file mode 100644 index 0000000..1c711a7 --- /dev/null +++ b/apps/web/src/main.tsx @@ -0,0 +1,9 @@ +import { StrictMode } from "react"; +import { createRoot } from "react-dom/client"; +import { App } from "./App"; + +createRoot(document.getElementById("root")!).render( + + + , +); diff --git a/apps/web/src/pages/InstallWizard.tsx b/apps/web/src/pages/InstallWizard.tsx new file mode 100644 index 0000000..1845429 --- /dev/null +++ b/apps/web/src/pages/InstallWizard.tsx @@ -0,0 +1,65 @@ +import { useState } from "react"; +import { api } from "../api/client"; + +export function InstallWizard({ + onCreated, + onCancel, +}: { + onCreated: (id: string, detectedEnvKeys: string[]) => void; + onCancel: () => void; +}) { + const [name, setName] = useState(""); + const [repoUrl, setRepoUrl] = useState(""); + const [gitRef, setGitRef] = useState(""); + const [error, setError] = useState(null); + const [loading, setLoading] = useState(false); + + async function submit(e: React.FormEvent) { + e.preventDefault(); + setLoading(true); + setError(null); + try { + const { installation, detectedEnvKeys } = await api.createInstallation({ name, repoUrl, gitRef: gitRef || undefined }); + onCreated(installation.id, detectedEnvKeys); + } catch (err) { + setError((err as Error).message); + } finally { + setLoading(false); + } + } + + return ( +
+

New installation

+
+ + + +
+ {" "} + +
+ {error &&

{error}

} +
+
+ ); +} diff --git a/apps/web/src/pages/InstallationDetail.tsx b/apps/web/src/pages/InstallationDetail.tsx new file mode 100644 index 0000000..d395fd1 --- /dev/null +++ b/apps/web/src/pages/InstallationDetail.tsx @@ -0,0 +1,211 @@ +import { useEffect, useState } from "react"; +import { useLocation, useNavigate, useParams } from "react-router-dom"; +import { api, type Installation } from "../api/client"; + +export function InstallationDetail() { + const { id } = useParams<{ id: string }>(); + const location = useLocation(); + const navigate = useNavigate(); + const [installation, setInstallation] = useState(null); + const [envRows, setEnvRows] = useState<[string, string][]>([]); + const [portEnvVar, setPortEnvVar] = useState("PORT"); + const [hostEnvVar, setHostEnvVar] = useState("HOST"); + const [httpPath, setHttpPath] = useState("/mcp"); + const [log, setLog] = useState(""); + const [busy, setBusy] = useState(null); + const [error, setError] = useState(null); + + async function refresh() { + if (!id) return; + const data = await api.getInstallation(id); + setInstallation(data); + setPortEnvVar(data.portEnvVar); + setHostEnvVar(data.hostEnvVar); + setHttpPath(data.httpPath); + + const detectedEnvKeys = (location.state as { detectedEnvKeys?: string[] } | null)?.detectedEnvKeys ?? []; + const keys = new Set([...Object.keys(data.env), ...detectedEnvKeys]); + setEnvRows([...keys].map((k) => [k, data.env[k] ?? ""])); + } + + useEffect(() => { + refresh(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [id]); + + async function withBusy(label: string, fn: () => Promise) { + setBusy(label); + setError(null); + try { + await fn(); + } catch (err) { + setError((err as Error).message); + } finally { + setBusy(null); + } + } + + if (!installation) return

Loading…

; + + return ( +
+ +

{installation.name}

+

+ Status: {installation.status} + {installation.statusDetail && — {installation.statusDetail}} +

+ + {installation.status === "running" && installation.bearerToken && ( +
+ Public URL: {installation.publicUrl} +
+ Bearer token: {installation.bearerToken}{" "} + +
+ )} + +

Configuration

+ + + {envRows.map(([k, v], idx) => ( + + + + + + ))} + +
+ { + const next = [...envRows]; + next[idx] = [e.target.value, v]; + setEnvRows(next); + }} + /> + + { + const next = [...envRows]; + next[idx] = [k, e.target.value]; + setEnvRows(next); + }} + /> + + +
+ + +
+ + + +
+ +
+ +
+ +

Actions

+ {" "} + {" "} + {" "} + {" "} + + + {error &&

{error}

} + +

Build log

+ +
{log}
+
+ ); +} diff --git a/apps/web/src/pages/InstallationList.tsx b/apps/web/src/pages/InstallationList.tsx new file mode 100644 index 0000000..416dbe1 --- /dev/null +++ b/apps/web/src/pages/InstallationList.tsx @@ -0,0 +1,61 @@ +import { useEffect, useState } from "react"; +import { Link, useNavigate } from "react-router-dom"; +import { api, type Installation } from "../api/client"; +import { InstallWizard } from "./InstallWizard"; + +export function InstallationList() { + const [installations, setInstallations] = useState([]); + const [showWizard, setShowWizard] = useState(false); + const navigate = useNavigate(); + + async function refresh() { + setInstallations(await api.listInstallations()); + } + + useEffect(() => { + refresh(); + }, []); + + return ( +
+
+

Installations

+ +
+ + {showWizard && ( + setShowWizard(false)} + onCreated={(id, detectedEnvKeys) => { + setShowWizard(false); + navigate(`/installations/${id}`, { state: { detectedEnvKeys } }); + }} + /> + )} + + + + + + + + + + + + {installations.map((i) => ( + + + + + + + ))} + +
NameSlugStatusRepo
+ {i.name} + {i.slug}{i.status}{i.repoUrl}
+ {installations.length === 0 && !showWizard &&

No installations yet.

} +
+ ); +} diff --git a/apps/web/src/pages/Login.tsx b/apps/web/src/pages/Login.tsx new file mode 100644 index 0000000..e1d4b5f --- /dev/null +++ b/apps/web/src/pages/Login.tsx @@ -0,0 +1,42 @@ +import { useState } from "react"; +import { api } from "../api/client"; + +export function Login({ onLoggedIn }: { onLoggedIn: () => void }) { + const [password, setPassword] = useState(""); + const [error, setError] = useState(null); + const [loading, setLoading] = useState(false); + + async function submit(e: React.FormEvent) { + e.preventDefault(); + setLoading(true); + setError(null); + try { + await api.login(password); + onLoggedIn(); + } catch (err) { + setError((err as Error).message); + } finally { + setLoading(false); + } + } + + return ( +
+

remote-mcp-manager

+
+ setPassword(e.target.value)} + autoFocus + style={{ width: "100%", padding: 8, marginBottom: 8 }} + /> + + {error &&

{error}

} +
+
+ ); +} diff --git a/apps/web/tsconfig.json b/apps/web/tsconfig.json new file mode 100644 index 0000000..5708551 --- /dev/null +++ b/apps/web/tsconfig.json @@ -0,0 +1,14 @@ +{ + "compilerOptions": { + "target": "ES2022", + "useDefineForClassFields": true, + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "module": "ESNext", + "moduleResolution": "Bundler", + "jsx": "react-jsx", + "strict": true, + "skipLibCheck": true, + "noEmit": true + }, + "include": ["src"] +} diff --git a/apps/web/vite.config.ts b/apps/web/vite.config.ts new file mode 100644 index 0000000..d444349 --- /dev/null +++ b/apps/web/vite.config.ts @@ -0,0 +1,11 @@ +import { defineConfig } from "vite"; +import react from "@vitejs/plugin-react"; + +export default defineConfig({ + plugins: [react()], + server: { + proxy: { + "/api": "http://localhost:3000", + }, + }, +}); diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..e26f27f --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,24 @@ +services: + manager: + build: . + restart: unless-stopped + ports: + - "127.0.0.1:3000:3000" + environment: + PORT: 3000 + DOCKER_SOCKET_PATH: /var/run/docker.sock + MCP_NETWORK_NAME: mcp-net + DATA_DIR: /repo/data + PUBLIC_BASE_URL: ${PUBLIC_BASE_URL} + ADMIN_PASSWORD: ${ADMIN_PASSWORD} + SESSION_SECRET: ${SESSION_SECRET} + MASTER_KEY: ${MASTER_KEY} + volumes: + - /var/run/docker.sock:/var/run/docker.sock + - ./data:/repo/data + networks: + - mcp-net + +networks: + mcp-net: + name: mcp-net diff --git a/package-lock.json b/package-lock.json index d34f0a1..49a4561 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,19 +12,340 @@ "apps/server": { "name": "@remote-mcp-manager/server", "dependencies": { + "better-sqlite3": "11.10.0", "dockerode": "4.0.9", "express": "4.21.2", + "express-session": "1.18.1", "http-proxy": "1.18.1" }, "devDependencies": { + "@types/better-sqlite3": "7.6.13", "@types/dockerode": "3.3.42", "@types/express": "4.17.23", + "@types/express-session": "1.18.2", "@types/http-proxy": "1.17.16", "@types/node": "24.9.1", "tsx": "4.20.6", "typescript": "5.9.3" } }, + "apps/web": { + "name": "@remote-mcp-manager/web", + "dependencies": { + "react": "18.3.1", + "react-dom": "18.3.1", + "react-router-dom": "6.30.1" + }, + "devDependencies": { + "@types/react": "18.3.26", + "@types/react-dom": "18.3.7", + "@vitejs/plugin-react": "4.3.4", + "typescript": "5.9.3", + "vite": "5.4.20" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helpers": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.8.tgz", + "integrity": "sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.8", + "@babel/types": "^7.29.8", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz", + "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.10", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.10.tgz", + "integrity": "sha512-EMCx3AGZ3WfAl6ht7AyQoPm2Lh+BGYEMdc4Q6Hf7uSCDOZFuLq44qNwL+iaim8VlyYhRAo4X43+weadK8ZB4Yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.8" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.9", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.9.tgz", + "integrity": "sha512-CjXrNHTnvqBVqHgdBysY3vk2T8tpJHb5/RMeHJBTyVa9xgugCB0CJTx/3oO8RV2QRQP391RWpB7D6hLjm8V9uA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.8" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.29.7.tgz", + "integrity": "sha512-TL0hMc9xzy86VD31nUiwzd5otRAcyEPcsegCxolO0PvcXuH1v0kECe/UIznYFihpkvU5wg/jk4v0TTEFfm53fw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.29.7.tgz", + "integrity": "sha512-06IyK09H3wi4cGbhDBwp5gUGo0IKtnYa8tyTiephirPCK6fbobVGiXMMI5zLQ4aKEYP3wZ3ArU44o+8KMrSG/Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.10", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.10.tgz", + "integrity": "sha512-B3lYAs2nkKskbgQ1nfy8Ma9ao9Q1rntTAQHrzzyDuj+TSO+X3le6PfWwqDvodsb+OumE10eMFfuMCTQSQ5iuIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.8", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.9", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.8", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz", + "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@balena/dockerignore": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/@balena/dockerignore/-/dockerignore-1.0.2.tgz", @@ -522,6 +843,56 @@ "node": ">=6" } }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz", + "integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, "node_modules/@js-sdsl/ordered-map": { "version": "4.4.2", "resolved": "https://registry.npmjs.org/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz", @@ -532,6 +903,26 @@ "url": "https://opencollective.com/js-sdsl" } }, + "node_modules/@napi-rs/lzma-linux-x64-gnu": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-x64-gnu/-/lzma-linux-x64-gnu-1.5.1.tgz", + "integrity": "sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^22.20 || ^24.12 || >=25" + } + }, "node_modules/@protobufjs/aspromise": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", @@ -589,10 +980,467 @@ "integrity": "sha512-b1UQwcEZ4yCnMCD8DAL1VlbvBJE9/IX4FTIp7BG1xYpf29SLazLSrqUkj4w7Y5y7cCVP6E5tcqqcI0xemPkHug==", "license": "BSD-3-Clause" }, + "node_modules/@remix-run/router": { + "version": "1.23.0", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.0.tgz", + "integrity": "sha512-O3rHJzAQKamUz1fvE0Qaw0xSFqsA/yafi2iqeE0pvdFtCO1viYx8QL6f3Ln/aCCTLxs68SLf0KPM9eSeM8yBnA==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/@remote-mcp-manager/server": { "resolved": "apps/server", "link": true }, + "node_modules/@remote-mcp-manager/web": { + "resolved": "apps/web", + "link": true + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.64.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.64.2.tgz", + "integrity": "sha512-gjNDUwMswogkw3fNqV00u54uZlgmInuw5EU/1TeqQ6K0bEagoGvqjPdKzy112lKyhe7PkGudZ/kSQHsc9qroIg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.64.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.64.2.tgz", + "integrity": "sha512-YkAW8q7p5SCLSaGWaUkmsRnBEb3MGA1pB5nI/8k0lAK5fZsQKRBqkdR1h4UU9p8u3c7ZGdUF2Zcx7mK+E/pUKg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.64.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.64.2.tgz", + "integrity": "sha512-hNmuIHv+LnrP/+q/44vsmIMWfoh7X2YXOp5FLtjGuvqN52VdtFpOpnshTc1k28tWv1wE/0D5gLjsozBUowEGbQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.64.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.64.2.tgz", + "integrity": "sha512-B0t5CoxgF2KL7R5FHAsyN8yVEQCSIkh4XVs8MCDjuBLNjEa8l3zd5tVKaFMv53LfUtHs9p2rnqeavPTOunV+Ww==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.64.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.64.2.tgz", + "integrity": "sha512-QQdYnfwrPmc1DoPlddjFvQPzUHFbStKXsHrG6EBYsWHhBVUCjsCRdcfGGXGAvhDpDtTzKK4vy1UrGFJ45rQaiw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.64.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.64.2.tgz", + "integrity": "sha512-1xRxtGWif6BB58VSfMavi0Yfs2M4x6CtRp4o7ScY4W7AGlgvf6GSPKdDNdw7TX3tMiQwZY5W/mvyrOJTq/7AKA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.64.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.64.2.tgz", + "integrity": "sha512-CXFLEt384BUwwOLR9DURWNHl1bQOVLd1ibOee+yKBJm/fZtvrSAOZ0JbxjAxszVMIovYMwFMS+oP+Q1aPcx8Eg==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.64.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.64.2.tgz", + "integrity": "sha512-T1dorY0WHm5mMNpMHFl5rYRZ02gLSZcr6J45r66tYb3S3R3DyIRGiGWVdLtF+zOQjratNJaObl9jYumueRiL1A==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.64.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.64.2.tgz", + "integrity": "sha512-ia6bfaCIc0ldp6bvs9oEkFrJVJKNUMoiDF0+LBZsQAZg+vE0znXnzGqH+ew/FYswE30cruAWPTO3dPw7keaVkQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.64.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.64.2.tgz", + "integrity": "sha512-mB/gwfBfXtT0EBpbSssqKCqMFuX8b21Zy2TGoIhehSJEssZ5ksybxymwp1BkEM5Nczh/AAxpmh5q+MkhtundGg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.64.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.64.2.tgz", + "integrity": "sha512-VPd0a+XlHoNRI9jIPRHww2UKhDkD6XjbtcKYXZijAnhJS1jLLbiNtVl6a5LyEfNN+eyiyWcJOZXPzBo69oTsew==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.64.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.64.2.tgz", + "integrity": "sha512-10N1I6oBpERqzGtHKcCLrEpHlbXSBbMK9TURHvqgWbIFZuU3oJROxJ0H2nHfq96TVI0XJHQ9qzolJMFd2tz0LA==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.64.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.64.2.tgz", + "integrity": "sha512-dhJrLVZ3wNNhwCmy9/VTg6Pq6J8a8eZ0n15dVRlbSWcDb+8V/1mDfbnvis+vt8W6Pbxzv6gUbTEl4piOT3bbUQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.64.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.64.2.tgz", + "integrity": "sha512-tPd2QuwxkTxgju5eARyb3RdgBWWi/O9dn/NFH0VkvGbLzoQeXrH6qMABtW8IODb5lUeBzB3sy2U7qmI4MtQ72g==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.64.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.64.2.tgz", + "integrity": "sha512-njtDdPvSQWI9CDhIPPxsiEMeHayCfu7wwcbSQ7c2kYPhGVviL+WGVhq6Jfj/b+VuhSTkFuQ0z4ZFbFjkWwBFmg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.64.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.64.2.tgz", + "integrity": "sha512-EbOBo3dqCj9ecEV9Z7zvulB6AieMP2ztnKQYGVOSN0pzIyOu+3JH2YJZnLmbPeVvfn6yP6Qi7NbBM27EzGbj2w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.64.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.64.2.tgz", + "integrity": "sha512-8wLt2Q5n4qmfiE2Gk8V6KNLSf+oJuBX7Kdw5joFDtnZIZvirS0hLzBc79OiJjV71oYDmBUZLNy6pDgvylo4qiQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.64.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.64.2.tgz", + "integrity": "sha512-aIChbkhC4d33bxkVP0LSi2/2lUSGRXE6k4Qa0G7tgpgTOK7kxHTixmx5qC3SKUJ6yUMznHBtQoWaG4QMqLUnhw==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.64.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.64.2.tgz", + "integrity": "sha512-gQ+TkVZRfjZVcl/UOAxxfWw+sHjzIXoieNwxEyJlD3tRBmBhR8ZDq11w+QEgQDcpslEB+fhN/OEpaTsDoIIHhQ==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.64.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.64.2.tgz", + "integrity": "sha512-sUaJpMKS2JBCvUk8fYimG7aMq2jHVT4kw/uVHxPolKLrMmkKy0KXirmZyZehdNpL7PffqAlPAWUELQcTiD8ChA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.64.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.64.2.tgz", + "integrity": "sha512-j1RdJWb0CrlSuwt12OPjLucPlwfLtA/KMOKVvh5rXSJEtl/WDoIGR3CSykK/jLGTo9RqNfW5Ub6kY5Z5ccYl9w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.64.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.64.2.tgz", + "integrity": "sha512-i3r04aHCIY3eggTBLUp/4IxiBjDpAgQaK89Ue2nMJ2MKuIXX5JfoeZjHw+kPjGGNVDlhi8FT8xzuGMJZe3aehg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.64.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.64.2.tgz", + "integrity": "sha512-kCQ15h/uwNMGdN8gjXBZGf+aGE+ODzHfbi90/cAm4+Q89WoMiLODNoj8HoGZmYx87HPxfu5CBNPsZ+ZG4uIAoA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.64.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.64.2.tgz", + "integrity": "sha512-yYAuySSs+0X2gSMJPw/Q4jHsHZHyMq1dpAE+XnN790Jg1WuReUAD/+j+e4vuDvS4PXJ4PA4NCzTR4OYtDf18/g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.64.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.64.2.tgz", + "integrity": "sha512-JY1rQdWOJrKutORqKO1uzrHXV86jFiQzfLFKx2s8S1CjLXL9fC5LOIvk0q7Z7PXJvsByt+sdoE1Q2a9gBr4gLQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/better-sqlite3": { + "version": "7.6.13", + "resolved": "https://registry.npmjs.org/@types/better-sqlite3/-/better-sqlite3-7.6.13.tgz", + "integrity": "sha512-NMv9ASNARoKksWtsq/SHakpYAYnhBrQgGD8zkLYk/jaK8jUGn08CfEdTRgYhMypUQAfzSP8W6gNLe0q19/t4VA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/body-parser": { "version": "1.19.6", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", @@ -637,6 +1485,13 @@ "@types/ssh2": "*" } }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/express": { "version": "4.17.23", "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.23.tgz", @@ -663,6 +1518,16 @@ "@types/send": "*" } }, + "node_modules/@types/express-session": { + "version": "1.18.2", + "resolved": "https://registry.npmjs.org/@types/express-session/-/express-session-1.18.2.tgz", + "integrity": "sha512-k+I0BxwVXsnEU2hV77cCobC08kIsn4y44C3gC0b46uxZVMaXA04lSPgRLR/bSL2w0t0ShJiG8o4jPzRG/nscFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/express": "*" + } + }, "node_modules/@types/http-errors": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", @@ -689,6 +1554,13 @@ "undici-types": "~7.16.0" } }, + "node_modules/@types/prop-types": { + "version": "15.7.15", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", + "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/qs": { "version": "6.15.1", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.15.1.tgz", @@ -703,6 +1575,27 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/react": { + "version": "18.3.26", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.26.tgz", + "integrity": "sha512-RFA/bURkcKzx/X9oumPG9Vp3D3JUgus/d0b67KB0t5S/raciymilkOa66olh78MUI92QLbEJevO7rvqU/kjwKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-dom": { + "version": "18.3.7", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", + "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^18.0.0" + } + }, "node_modules/@types/send": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz", @@ -751,6 +1644,26 @@ "dev": true, "license": "MIT" }, + "node_modules/@vitejs/plugin-react": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.3.4.tgz", + "integrity": "sha512-SCCPBJtYLdE8PX/7ZQAs1QAZ8Jqwih+0VBLum1EGqmCCQal+MIUqLCzj3ZUy8ufbC0cAM4LRlSTm7IQJwWT4ug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.26.0", + "@babel/plugin-transform-react-jsx-self": "^7.25.9", + "@babel/plugin-transform-react-jsx-source": "^7.25.9", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.14.2" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0" + } + }, "node_modules/accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -823,6 +1736,19 @@ ], "license": "MIT" }, + "node_modules/baseline-browser-mapping": { + "version": "2.11.27", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.27.tgz", + "integrity": "sha512-ElY12DaROGuan+lMmZ8Cvo/ZUbXPe7Enc/9VU/b1T3Kp4dwytRcNdR8DoSJN5SNJT/CuvcCA0DHDVmMOCePdRQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/bcrypt-pbkdf": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", @@ -832,6 +1758,26 @@ "tweetnacl": "^0.14.3" } }, + "node_modules/better-sqlite3": { + "version": "11.10.0", + "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-11.10.0.tgz", + "integrity": "sha512-EwhOpyXiOEL/lKzHz9AW1msWFNzGc/z+LzeB3/jnFJpxu+th2yqvzsSWas1v9jgs9+xiXJcD5A8CJxAG2TaghQ==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "bindings": "^1.5.0", + "prebuild-install": "^7.1.1" + } + }, + "node_modules/bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "license": "MIT", + "dependencies": { + "file-uri-to-path": "1.0.0" + } + }, "node_modules/bl": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", @@ -882,6 +1828,40 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "license": "MIT" }, + "node_modules/browserslist": { + "version": "4.29.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.29.3.tgz", + "integrity": "sha512-1R4kiYKXGViqEN0CnoDrXc1StD9niAwu+j2dukWzrD4bJgsD4lDmEp0CRbc6E/vYJIfTHwPmwyaKtVSudICdPA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.11.26", + "caniuse-lite": "^1.0.30001813", + "electron-to-chromium": "^1.5.439", + "node-releases": "^2.0.57", + "update-browserslist-db": "^1.3.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, "node_modules/buffer": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", @@ -953,6 +1933,27 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/caniuse-lite": { + "version": "1.0.30001815", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001815.tgz", + "integrity": "sha512-uDybUEyfgbyJJ93J1vikTylBX31AkdVYeVPeffMQpf8XN7hSfr0oZOn4VFOIMNvtU5+rHvC5Udg2TgtJKHm23Q==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, "node_modules/chownr": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", @@ -1012,6 +2013,13 @@ "node": ">= 0.6" } }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, "node_modules/cookie": { "version": "0.7.1", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", @@ -1041,6 +2049,13 @@ "node": ">=10.0.0" } }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "dev": true, + "license": "MIT" + }, "node_modules/debug": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", @@ -1058,6 +2073,30 @@ } } }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "license": "MIT", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -1077,6 +2116,15 @@ "npm": "1.2.8000 || >= 1.4.16" } }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, "node_modules/docker-modem": { "version": "5.0.7", "resolved": "https://registry.npmjs.org/docker-modem/-/docker-modem-5.0.7.tgz", @@ -1130,6 +2178,13 @@ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", "license": "MIT" }, + "node_modules/electron-to-chromium": { + "version": "1.5.451", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.451.tgz", + "integrity": "sha512-vUNKPrUzw8ByEMOxloq0a71iD/mPuPxVkbDm3G5YhnxTzOw/I6qAg2MUS0cYj/c267lWmw26yW4Vv7foxTxPLg==", + "dev": true, + "license": "ISC" + }, "node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", @@ -1256,6 +2311,15 @@ "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", "license": "MIT" }, + "node_modules/expand-template": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", + "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", + "license": "(MIT OR WTFPL)", + "engines": { + "node": ">=6" + } + }, "node_modules/express": { "version": "4.21.2", "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", @@ -1302,6 +2366,55 @@ "url": "https://opencollective.com/express" } }, + "node_modules/express-session": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.18.1.tgz", + "integrity": "sha512-a5mtTqEaZvBCL9A9aqkrtfz+3SMDhOVUnjafjo+s7A9Txkq+SVX2DLvSp1Zrv4uCXa3lMSK3viWnh9Gg07PBUA==", + "license": "MIT", + "dependencies": { + "cookie": "0.7.2", + "cookie-signature": "1.0.7", + "debug": "2.6.9", + "depd": "~2.0.0", + "on-headers": "~1.0.2", + "parseurl": "~1.3.3", + "safe-buffer": "5.2.1", + "uid-safe": "~2.1.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/express-session/node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express-session/node_modules/cookie-signature": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz", + "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==", + "license": "MIT" + }, + "node_modules/express-session/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/express-session/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, "node_modules/express/node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -1317,6 +2430,12 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "license": "MIT" }, + "node_modules/file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", + "license": "MIT" + }, "node_modules/finalhandler": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", @@ -1418,6 +2537,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", @@ -1477,6 +2606,12 @@ "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" } }, + "node_modules/github-from-package": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", + "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", + "license": "MIT" + }, "node_modules/gopd": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", @@ -1581,6 +2716,12 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "license": "ISC" }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "license": "ISC" + }, "node_modules/ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", @@ -1599,6 +2740,38 @@ "node": ">=8" } }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/lodash.camelcase": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", @@ -1611,6 +2784,28 @@ "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", "license": "Apache-2.0" }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -1680,6 +2875,27 @@ "node": ">= 0.6" } }, + "node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/mkdirp-classic": { "version": "0.5.3", "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", @@ -1699,6 +2915,31 @@ "license": "MIT", "optional": true }, + "node_modules/nanoid": { + "version": "3.3.20", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.20.tgz", + "integrity": "sha512-uKdg2G3GNCKQn9byYOpxbGqrT2fGO5KRt5J/8b3pok8rT6qxGWF6hxMyJiEYtAf+FVyYuD9hRaDqX5uPFYJ4ZQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/napi-build-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz", + "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==", + "license": "MIT" + }, "node_modules/negotiator": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", @@ -1708,6 +2949,28 @@ "node": ">= 0.6" } }, + "node_modules/node-abi": { + "version": "3.97.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.97.0.tgz", + "integrity": "sha512-6Pu8F0eXGZlyz0EepPJrWgWDcBI8rKt2Wj8exOJU1tjVbu/VyZy5ZaLYS5N+Jp/Y3IP3sIRKcIOElTu7uC6KKA==", + "license": "MIT", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/node-releases": { + "version": "2.0.57", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.57.tgz", + "integrity": "sha512-kQK9LGGFiHtrWiNhZtA7Qbw17AQz+dmsEKODRIVTXA9+e5MS/2gZEBhYJt13GrAz5/IOZKddH/0Z3TP/Zgo+yw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/object-inspect": { "version": "1.13.4", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", @@ -1732,6 +2995,15 @@ "node": ">= 0.8" } }, + "node_modules/on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -1756,6 +3028,69 @@ "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", "license": "MIT" }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/postcss": { + "version": "8.5.29", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.29.tgz", + "integrity": "sha512-49cGhUbXj8Qenv0iTMxA1cFBzxXoctpC9Ujd77t1WcbJIr6nF/eI7g/8MgxrYldFRuAXvja7xQRwavoW7kgrxQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.19", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prebuild-install": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", + "integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==", + "deprecated": "No longer maintained. Please contact the author of the relevant native addon; alternatives are available.", + "license": "MIT", + "dependencies": { + "detect-libc": "^2.0.0", + "expand-template": "^2.0.3", + "github-from-package": "0.0.0", + "minimist": "^1.2.3", + "mkdirp-classic": "^0.5.3", + "napi-build-utils": "^2.0.0", + "node-abi": "^3.3.0", + "pump": "^3.0.0", + "rc": "^1.2.7", + "simple-get": "^4.0.0", + "tar-fs": "^2.0.0", + "tunnel-agent": "^0.6.0" + }, + "bin": { + "prebuild-install": "bin.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/protobufjs": { "version": "7.6.6", "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.6.6.tgz", @@ -1821,6 +3156,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/random-bytes": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", + "integrity": "sha512-iv7LhNVO047HzYR3InF6pUcUsPQiHTM1Qal51DcGSuZFBil1aBBWG5eHPNek7bvILMaYJ/8RU1e8w1AMdHmLQQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", @@ -1845,6 +3189,88 @@ "node": ">= 0.8" } }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/react-refresh": { + "version": "0.14.2", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz", + "integrity": "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-router": { + "version": "6.30.1", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.1.tgz", + "integrity": "sha512-X1m21aEmxGXqENEPG3T6u0Th7g0aS4ZmoNynhbs+Cn+q+QGTLt+d5IQ2bHAXKzKcxGJjxACpVbnYQSCRcfxHlQ==", + "license": "MIT", + "dependencies": { + "@remix-run/router": "1.23.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8" + } + }, + "node_modules/react-router-dom": { + "version": "6.30.1", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.30.1.tgz", + "integrity": "sha512-llKsgOkZdbPU1Eg3zK8lCn+sjD9wMRZZPuzmdWWX5SUs8OFkN5HnFVC0u5KMeMaC9aoancFI/KoLuKPqN+hxHw==", + "license": "MIT", + "dependencies": { + "@remix-run/router": "1.23.0", + "react-router": "6.30.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, "node_modules/readable-stream": { "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", @@ -1884,6 +3310,52 @@ "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" } }, + "node_modules/rollup": { + "version": "4.64.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.64.2.tgz", + "integrity": "sha512-RxXm0XVVV1hFI3dYe12ruaOgsYQ725lUcUWeMSQqkF9tTxVErY+fy2Ets6vWhItL9MZMSNZGbvZFc7rH/VHp2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.9" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@napi-rs/lzma-linux-x64-gnu": "1.5.1", + "@rollup/rollup-android-arm-eabi": "4.64.2", + "@rollup/rollup-android-arm64": "4.64.2", + "@rollup/rollup-darwin-arm64": "4.64.2", + "@rollup/rollup-darwin-x64": "4.64.2", + "@rollup/rollup-freebsd-arm64": "4.64.2", + "@rollup/rollup-freebsd-x64": "4.64.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.64.2", + "@rollup/rollup-linux-arm-musleabihf": "4.64.2", + "@rollup/rollup-linux-arm64-gnu": "4.64.2", + "@rollup/rollup-linux-arm64-musl": "4.64.2", + "@rollup/rollup-linux-loong64-gnu": "4.64.2", + "@rollup/rollup-linux-loong64-musl": "4.64.2", + "@rollup/rollup-linux-ppc64-gnu": "4.64.2", + "@rollup/rollup-linux-ppc64-musl": "4.64.2", + "@rollup/rollup-linux-riscv64-gnu": "4.64.2", + "@rollup/rollup-linux-riscv64-musl": "4.64.2", + "@rollup/rollup-linux-s390x-gnu": "4.64.2", + "@rollup/rollup-linux-x64-gnu": "4.64.2", + "@rollup/rollup-linux-x64-musl": "4.64.2", + "@rollup/rollup-openbsd-x64": "4.64.2", + "@rollup/rollup-openharmony-arm64": "4.64.2", + "@rollup/rollup-win32-arm64-msvc": "4.64.2", + "@rollup/rollup-win32-ia32-msvc": "4.64.2", + "@rollup/rollup-win32-x64-gnu": "4.64.2", + "@rollup/rollup-win32-x64-msvc": "4.64.2", + "fsevents": "~2.3.2" + } + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -1910,6 +3382,27 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "license": "MIT" }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/send": { "version": "0.19.0", "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", @@ -2051,6 +3544,61 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/simple-get": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", + "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "decompress-response": "^6.0.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.2.tgz", + "integrity": "sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/split-ca": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/split-ca/-/split-ca-1.0.1.tgz", @@ -2118,6 +3666,15 @@ "node": ">=8" } }, + "node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/tar-fs": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.5.tgz", @@ -2175,6 +3732,18 @@ "fsevents": "~2.3.3" } }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, "node_modules/tweetnacl": { "version": "0.14.5", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", @@ -2208,6 +3777,18 @@ "node": ">=14.17" } }, + "node_modules/uid-safe": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz", + "integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==", + "license": "MIT", + "dependencies": { + "random-bytes": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/undici-types": { "version": "7.16.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", @@ -2223,6 +3804,37 @@ "node": ">= 0.8" } }, + "node_modules/update-browserslist-db": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.3.4.tgz", + "integrity": "sha512-t8wonIOdQyOjtPd2VktlBu6QjTVTks6v8bSi7kd0r2Vll8TK1JYDZtRRdbmCHOAMphkZgrzK+L+vXK4pyZ8t4A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -2261,6 +3873,496 @@ "node": ">= 0.8" } }, + "node_modules/vite": { + "version": "5.4.20", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.20.tgz", + "integrity": "sha512-j3lYzGC3P+B5Yfy/pfKNgVEg4+UtcIJcVRt2cDjIOmhLourAqPqf8P7acgxeiSgUB7E3p2P8/3gNIgDLpwzs4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vite/node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, "node_modules/wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", @@ -2293,6 +4395,13 @@ "node": ">=10" } }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, "node_modules/yargs": { "version": "17.7.3", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.3.tgz",