diff --git a/CLAUDE.md b/CLAUDE.md index 771beb38..ca568f1c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -17,7 +17,7 @@ Monorepo on **Yarn 4 (Berry, via Corepack) + Turbo**. Workspaces declared in roo | `packages/sidequest` | `sidequest` | Umbrella package end users install. Exposes `Sidequest`, re-exports the rest. Source is intentionally thin — mostly the `Sidequest` static class and operations facade. | | `packages/engine` | `@sidequest/engine` | Orchestration. Owns the `Engine`, `Dispatcher`, `QueueManager`, `ExecutorManager`, `JobBuilder`, `JobTransitioner`, cron registry, routines (cleanup, stale recovery), shared runner pool. | | `packages/core` | `@sidequest/core` | Shared primitives: `Job` base class, schema/types (`JobData`, `QueueConfig`, etc.), state transitions, logger (Winston), uniqueness, tools. | -| `packages/dashboard` | `@sidequest/dashboard` | Express + EJS + HTMX + Tailwind/DaisyUI web UI. Can run standalone via `SidequestDashboard`. | +| `packages/web` | `@sidequest/web` | Web layer (v2 rewrite, React + Vite). Currently ships the `/ui` design-system component library; the OSS dashboard app, the Hono management API, and a boot façade are being added. | | `packages/cli` | `@sidequest/cli` | `sidequest` / `sq` CLI for `config`, `migrate`, `rollback`. | | `packages/docs` | (private) | VitePress site → docs.sidequestjs.com. | | `packages/backends/backend` | `@sidequest/backend` | Backend interface + `SQLBackend` base (Knex-based). | @@ -96,7 +96,7 @@ Node ≥ 22.6.0 required. TypeScript jobs run natively on Node ≥ 23.6.0. - `Sidequest.job` — `.get`, `.list`, `.count`, `.cancel`, `.run`, `.snooze`, `.findStale`, `.deleteFinished`. - `Sidequest.queue` — `.get`, `.list`, `.create`, `.pause`, `.activate`, `.toggle`, `.setConcurrency`, `.setPriority`. - `Job` class (`@sidequest/core`) with `async run(...args)`. Runtime metadata (`this.id`, `this.attempt`, etc.) is injected **after construction**, only available inside `run`. Convenience methods inside `run`: `return this.complete(result)` / `this.fail(reason)` / `this.retry(reason, delay?)` / `this.snooze(delay)`. **You must `return` them** — calling without returning is a no-op. -- `SidequestDashboard` (`@sidequest/dashboard`) — standalone dashboard against a shared backend. +- Dashboard: under v2 rewrite. The old Express/EJS `SidequestDashboard` and the `Sidequest.start({ dashboard })` option have been removed; `@sidequest/web` currently exposes only the `/ui` component library. Booting a dashboard will return via the `@sidequest/web` façade. ## Behavioral nuances that bite diff --git a/eslint.config.js b/eslint.config.js index f403d0e5..60aac0f6 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -20,9 +20,9 @@ export default tseslint.config( "**/migrations/**", "**/storybook-static/**", "packages/docs/.vitepress/cache/**", - "packages/dashboard/vite.lib.config.ts", - "packages/dashboard/vitest.setup.ts", - "packages/dashboard/.storybook/**", + "packages/web/vite.lib.config.ts", + "packages/web/vitest.setup.ts", + "packages/web/.storybook/**", ], }, { @@ -51,13 +51,13 @@ export default tseslint.config( }, { // React UI library lives in its own tsconfig (JSX, DOM libs, bundler resolution). - files: ["packages/dashboard/src/ui/**/*.{ts,tsx}"], + files: ["packages/web/src/ui/**/*.{ts,tsx}"], plugins: { "react-hooks": reactHooks, "jsx-a11y": jsxA11y }, languageOptions: { globals: { ...globals.browser }, parserOptions: { projectService: false, - project: ["./packages/dashboard/tsconfig.ui.json"], + project: ["./packages/web/tsconfig.ui.json"], tsconfigRootDir: import.meta.dirname, sourceType: "module", }, diff --git a/examples/06-dashboard-base-path/index.ts b/examples/06-dashboard-base-path/index.ts deleted file mode 100644 index 4daebebc..00000000 --- a/examples/06-dashboard-base-path/index.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { logger, Sidequest } from "sidequest"; -import { TestJob } from "./test-job.js"; - -async function main() { - logger().info("Starting Sidequest with dashboard base path..."); - - await Sidequest.start({ - backend: { - driver: "@sidequest/sqlite-backend", - config: "./sidequest.sqlite", - }, - dashboard: { - enabled: true, - port: 8678, - basePath: "/admin/sidequest", - }, - queues: [{ name: "default", concurrency: 1 }], - }); - - logger().info("\n✅ Sidequest started!"); - logger().info("🌐 Dashboard should be accessible at: http://localhost:8678/admin/sidequest"); - logger().info("\n📝 Testing URLs:"); - logger().info(" - Dashboard: http://localhost:8678/admin/sidequest/"); - logger().info(" - Jobs: http://localhost:8678/admin/sidequest/jobs"); - logger().info(" - Queues: http://localhost:8678/admin/sidequest/queues"); - logger().info(" - Logo: http://localhost:8678/admin/sidequest/public/img/logo.png"); - logger().info(" - Styles: http://localhost:8678/admin/sidequest/public/css/styles.css"); - - // Enqueue some test jobs - logger().info("\n📦 Enqueueing test jobs..."); - for (let i = 1; i <= 5; i++) { - await Sidequest.build(TestJob).enqueue(); - logger().info(` ✓ Job ${i} enqueued`); - } - - logger().info("\n⚠️ Note: The dashboard should NOT be accessible at http://localhost:8678/ (without base path)"); - logger().info("💡 Try accessing the dashboard and verify:"); - logger().info(" 1. All assets load correctly (logo, styles, scripts)"); - logger().info(" 2. Navigation links work (Dashboard, Jobs, Queues)"); - logger().info(" 3. Job actions work (run, cancel, rerun)"); - logger().info(" 4. HTMX polling/updates work correctly"); - logger().info("\n🛑 Press Ctrl+C to stop\n"); -} - -// eslint-disable-next-line no-console -main().catch(console.error); diff --git a/examples/06-dashboard-base-path/test-job.ts b/examples/06-dashboard-base-path/test-job.ts deleted file mode 100644 index c4e02fce..00000000 --- a/examples/06-dashboard-base-path/test-job.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Job, logger } from "sidequest"; - -export class TestJob extends Job { - async run() { - logger().info(`Processing test job: ${this.id}`); - await new Promise((resolve) => setTimeout(resolve, 1000)); - return { message: "Job completed successfully" }; - } -} diff --git a/package.json b/package.json index dd379e1d..580f4512 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ "test:all:ci": "turbo run test:ci", "test:all:debug": "yarn vitest --test-timeout=0 run --exclude tests/integration", "test:all:coverage": "vitest run --coverage --exclude tests/integration", - "test:ui:coverage": "yarn vitest run --config packages/dashboard/vitest.config.js --coverage", + "test:ui:coverage": "yarn vitest run --config packages/web/vitest.config.js --coverage", "format": "prettier --write \"**/*.{ts,js,mts,tsx,jsx,json,md}\"", "format:check": "prettier --check \"**/*.{ts,js,mts,tsx,jsx}\"", "lint": "eslint . --ext .ts,.mts,.js,.tsx,.jsx", diff --git a/packages/dashboard/README.md b/packages/dashboard/README.md deleted file mode 100644 index 17fd6e6d..00000000 --- a/packages/dashboard/README.md +++ /dev/null @@ -1,113 +0,0 @@ -# @sidequest/dashboard - -Web-based monitoring and management dashboard for the [Sidequest](https://github.com/sidequestjs/sidequest) job processing system. - -## Summary - -This package provides a beautiful, responsive web dashboard for monitoring and managing your Sidequest job queues. Built with Express.js, EJS templating, and HTMX for dynamic interactions, the dashboard offers real-time insights into your job processing system without requiring external dependencies or complex setup. - -The dashboard includes: - -- **Real-time Job Statistics** - Live counters and charts showing job states and performance -- **Job Management** - View, filter, search, retry, and cancel jobs -- **Queue Operations** - Monitor queue status, pause/resume processing, and manage priorities -- **Performance Analytics** - Charts and metrics for job throughput and processing times -- **Responsive Design** - Modern, mobile-friendly interface using DaisyUI and TailwindCSS -- **Optional Authentication** - Basic auth protection for production deployments -- **HTMX Integration** - Dynamic updates without full page reloads - -The dashboard is designed to work seamlessly with all Sidequest backends (PostgreSQL, MySQL, SQLite, MongoDB) and automatically connects to your existing job queue infrastructure. - -## Documentation - -For complete setup instructions, configuration options, and usage guides, visit: - -**[https://docs.sidequestjs.com/dashboard](https://docs.sidequestjs.com/dashboard)** - -The documentation covers: - -- **Getting Started** - Quick setup and first run -- **Configuration Options** - Backend setup, authentication, and customization -- **Feature Overview** - Detailed walkthrough of all dashboard features -- **Production Deployment** - Security considerations and deployment best practices -- **Troubleshooting** - Common issues and solutions -- **API Reference** - Integration options and programmatic access - -### Quick Start - -The dashboard is available through the Sidequest regular configuration and is started alongside the Sidequest engine: - -```typescript -import { Sidequest } from "sidequest"; - -await Sidequest.start({ - backend: { - driver: "@sidequest/postgres-backend", - config: "postgresql://localhost:5432/sidequest", - }, - dashboard: { - enabled: true, - port: 8678, - auth: { - user: "admin", - password: "secure-password", - }, - }, - queues: [{ name: "default", priority: 10, workers: 2 }], -}); - -// Dashboard available at http://localhost:8678 -``` - -If you prefer to use only the dashboard without starting the Sidequest engine, you can do so by importing and configuring it directly: - -```typescript -import { SidequestDashboard } from "@sidequest/dashboard"; - -const dashboard = new SidequestDashboard(); - -await dashboard.start({ - enabled: true, - port: 8678, - backendConfig: { - driver: "@sidequest/postgres-backend", - config: "postgresql://localhost:5432/sidequest", - }, - auth: { - user: "admin", - password: "secure-password", - }, -}); - -// Dashboard available at http://localhost:8678 -``` - -### Reverse Proxy Setup - -When deploying behind a reverse proxy, use the `basePath` option: - -```typescript -await Sidequest.start({ - dashboard: { - port: 8678, - basePath: "/admin/sidequest", // Serve at /admin/sidequest - auth: { - user: "admin", - password: "secure-password", - }, - }, -}); -``` - -Then configure your reverse proxy to forward requests: - -```nginx -# Nginx example -location /admin/sidequest/ { - proxy_pass http://localhost:8678/admin/sidequest/; -} -``` - -## License - -LGPL-3.0-or-later diff --git a/packages/dashboard/rollup.config.js b/packages/dashboard/rollup.config.js deleted file mode 100644 index a65e62db..00000000 --- a/packages/dashboard/rollup.config.js +++ /dev/null @@ -1,71 +0,0 @@ -import createConfig from "../../rollup.config.base.js"; -import pkg from "./package.json" with { type: "json" }; - -import copy from "rollup-plugin-copy-watch"; -import del from "rollup-plugin-delete"; -import postcss from "rollup-plugin-postcss"; - -const isWatch = process.env.ROLLUP_WATCH === "true"; - -const configs = createConfig(pkg, "src/index.ts", [ - copy({ - verbose: true, - // These are copied only once in dev mode - copyOnce: true, - targets: [ - { - src: "../../node_modules/htmx.org/dist/htmx.min.js", - dest: "dist/public/js", - rename: "htmx.js", - }, - { - src: "../../node_modules/feather-icons/dist/feather.min.js", - dest: "dist/public/js", - rename: "feather-icons.js", - }, - { - src: "../../node_modules/@highlightjs/cdn-assets/highlight.min.js", - dest: "dist/public/js", - rename: "highlight.js", - }, - { - src: "../../node_modules/chart.js/dist/chart.umd.min.js", - dest: "dist/public/js", - rename: "chart.js", - }, - ], - }), -]); - -configs.push( - // Build CSS - { - input: "src/public/css/styles.css", - output: [{ file: "dist/_styles.css" }], - plugins: [ - postcss({ - extract: "public/css/styles.css", - minimize: true, - }), - // Need to specify "writeBundle" to delete the file after it has been created - del({ targets: "./dist/_styles.css", verbose: true, hook: "writeBundle" }), - // We copy those here because then, in dev mode, we re-copy these files if something - // changes inside those dirs. - copy({ - verbose: true, - ...(isWatch - ? { - watch: ["src/views/**/*", "src/public/**/*"], - } - : {}), - targets: [ - { src: "src/views", dest: "dist" }, - { src: "src/public/img", dest: "dist/public" }, - { src: "src/public/js", dest: "dist/public" }, - ], - }), - ], - }, -); - -export default configs; diff --git a/packages/dashboard/src/config.ts b/packages/dashboard/src/config.ts deleted file mode 100644 index d48cc570..00000000 --- a/packages/dashboard/src/config.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { BackendConfig } from "@sidequest/backend"; - -/** - * Configuration interface for the Sidequest dashboard. - * - * Defines the available options for configuring the dashboard including - * backend connectivity, server settings, authentication, and routing. - * - * @interface DashboardConfig - * @example - * ```typescript - * const config: DashboardConfig = { - * enabled: true, - * port: 3000, - * basePath: "/admin/sidequest", - * auth: { - * user: "admin", - * password: "secure-password" - * } - * }; - * ``` - */ -export interface DashboardConfig { - /** - * Configuration for connecting to the Sidequest backend. - * This includes the driver and any necessary connection options. - */ - backendConfig?: BackendConfig; - /** - * Indicates whether the dashboard is enabled. - * If set to false, the dashboard server will not start. - * - * @default false - */ - enabled?: boolean; - /** - * Port number on which the dashboard server will listen for incoming requests. - * - * @default 8678 - */ - port?: number; - /** - * Base path for the dashboard when served behind a reverse proxy. - * For example, if you want to serve the dashboard at `/admin/sidequest`, - * set this to `/admin/sidequest`. - * - * @example "/admin/sidequest" - * @default "" - */ - basePath?: string; - /** - * Optional basic authentication configuration. - * If provided, the dashboard will require users to authenticate - * using the specified username and password. - * - * @example - * ```typescript - * auth: { - * user: 'admin', - * password: 'secure-password' - * } - * ``` - * @default undefined - */ - auth?: { - user: string; - password: string; - }; -} diff --git a/packages/dashboard/src/index.ts b/packages/dashboard/src/index.ts deleted file mode 100644 index c49b7190..00000000 --- a/packages/dashboard/src/index.ts +++ /dev/null @@ -1,289 +0,0 @@ -import { Backend, createBackendFromDriver } from "@sidequest/backend"; -import { logger } from "@sidequest/core"; -import express from "express"; -import basicAuth from "express-basic-auth"; -import expressLayouts from "express-ejs-layouts"; -import morgan from "morgan"; -import { Server } from "node:http"; -import path from "node:path"; -import { DashboardConfig } from "./config"; -import { createDashboardRouter } from "./resources/dashboard"; -import { createJobsRouter } from "./resources/jobs"; -import { createQueuesRouter } from "./resources/queues"; - -/** - * A dashboard server for monitoring and managing Sidequest jobs and queues. - * - * The SidequestDashboard class provides a web-based interface for viewing job status, - * queue information, and other Sidequest-related data. It sets up an Express.js server - * with EJS templating, optional basic authentication, and connects to a configurable backend. - * - * @example - * ```typescript - * const dashboard = new SidequestDashboard(); - * await dashboard.start({ - * port: 3000, - * enabled: true, - * auth: { - * user: 'admin', - * password: 'secret' - * }, - * backendConfig: { - * driver: '@sidequest/sqlite-backend' - * } - * }); - * ``` - */ -export class SidequestDashboard { - /** - * The Express application instance used by the dashboard server. - * This property is optional and may be undefined if the server has not been initialized. - */ - private app?: express.Express; - /** - * Optional dashboard configuration object that defines the behavior and appearance - * of the dashboard component. If not provided, default configuration will be used. - */ - private config?: DashboardConfig; - /** - * The HTTP server instance used by the dashboard application. - * This server handles incoming requests and serves the dashboard interface. - * Will be undefined until the server is started. - */ - private server?: Server; - /** - * Backend instance used for server communication and data operations. - * When undefined, indicates that no backend connection has been established. - */ - private backend?: Backend; - - constructor() { - this.app = express(); - } - - /** - * Starts the dashboard server with the provided configuration. - * - * @param config - Optional dashboard configuration object. If not provided, default values will be used. - * @returns A promise that resolves when the server setup is complete. - * - * @remarks - * - If the server is already running, a warning is logged and the method returns early - * - If the dashboard is disabled in config, a debug message is logged and the method returns early - * - The method sets up the Express app, backend driver, middlewares, authentication, EJS templating, routes, and starts listening - * - * @example - * ```typescript - * await dashboard.start({ - * enabled: true, - * port: 3000, - * backendConfig: { driver: "@sidequest/sqlite-backend" } - * }); - * ``` - */ - async start(config?: DashboardConfig) { - if (this.server?.listening) { - logger("Dashboard").warn("Dashboard is already running. Please stop it before starting again."); - return; - } - - this.config = { - enabled: true, - backendConfig: { - driver: "@sidequest/sqlite-backend", - }, - port: 8678, - ...config, - }; - - // Normalize basePath: remove trailing slash, ensure leading slash - if (this.config.basePath) { - this.config.basePath = this.config.basePath.replace(/\/$/, ""); - if (!this.config.basePath.startsWith("/")) { - this.config.basePath = "/" + this.config.basePath; - } - } else { - this.config.basePath = ""; - } - - if (!this.config.enabled) { - logger("Dashboard").debug(`Dashboard is disabled`); - return; - } - - this.app ??= express(); - this.backend = await createBackendFromDriver(this.config.backendConfig!); - - this.setupMiddlewares(); - this.setupAuth(); - this.setupEJS(); - this.setupRoutes(); - - this.listen(); - } - - /** - * Sets up middleware for the Express application. - * - * Configures HTTP request logging using Morgan middleware when debug logging is enabled. - * The middleware uses the "combined" format for comprehensive request logging. - * - * @remarks - * - Only adds Morgan logging middleware when debug mode is active - * - Uses Apache combined log format for detailed request information - * - Logs are handled through the Dashboard logger instance - */ - setupMiddlewares() { - logger("Dashboard").debug(`Setting up Middlewares`); - if (logger().isDebugEnabled()) { - this.app?.use(morgan("combined")); - } - - // Make basePath available to all templates - this.app?.use((req, res, next) => { - res.locals.basePath = this.config!.basePath ?? ""; - next(); - }); - - // Fixes a HTMX bug where restoring the dashboard tab from cache causes it to show an unstyled page - // https://github.com/bigskysoftware/htmx/issues/497 - this.app?.use((req, res, next) => { - if (req.headers["hx-request"]) { - res.setHeader("Cache-Control", "no-store, max-age=0"); - } - next(); - }); - } - - /** - * Sets up basic authentication for the dashboard application. - * - * If authentication configuration is provided, this method configures - * HTTP Basic Authentication middleware using the specified username and password. - * The middleware will challenge unauthorized requests with a 401 response. - * - * @remarks - * - Only sets up authentication if `this.config.auth` is defined - * - Uses a single user/password combination from the configuration - * - Enables challenge mode to prompt for credentials in browsers - * - * @example - * ```typescript - * // Assuming config.auth = { user: "admin", password: "secret" } - * dashboard.setupAuth(); // Sets up basic auth for user "admin" - * ``` - */ - setupAuth() { - if (this.config!.auth) { - const auth = this.config!.auth; - logger("Dashboard").debug(`Basic auth setup with User: ${auth.user}`); - const users = {}; - users[auth.user] = auth.password; - this.app!.use( - basicAuth({ - users: users, - challenge: true, - }), - ); - } - } - - /** - * Sets up EJS templating engine for the dashboard application. - * This method configures the Express application to use EJS as the view engine, - * sets the views directory, and specifies the layout file. - * - * @remarks - * - Uses `express-ejs-layouts` for layout support - * - Sets the views directory to the `views` folder within the package - * - Serves static files from the `public` directory - * - Ensures that the EJS engine is ready to render views with layouts - */ - setupEJS() { - logger("Dashboard").debug(`Setting up EJS`); - this.app!.use(expressLayouts); - this.app!.set("view engine", "ejs"); - this.app!.set("views", path.join(import.meta.dirname, "views")); - this.app!.set("layout", path.join(import.meta.dirname, "views", "layout")); - const publicPath = this.config!.basePath ? `${this.config!.basePath}/public` : "/public"; - this.app!.use(publicPath, express.static(path.join(import.meta.dirname, "public"))); - } - - /** - * Sets up the main application routes for the dashboard. - * - * This method initializes and attaches the dashboard, jobs, and queues routers - * to the Express application instance. It also logs the setup process for debugging purposes. - * - * @remarks - * - Assumes that `this.app` and `this.backend` are initialized. - * - Uses the routers created by `createDashboardRouter`, `createJobsRouter`, and `createQueuesRouter`. - */ - setupRoutes() { - logger("Dashboard").debug(`Setting up routes`); - const basePath = this.config!.basePath ?? ""; - const [dashboardPath, dashboardRouter] = createDashboardRouter(this.backend!); - const [jobsPath, jobsRouter] = createJobsRouter(this.backend!); - const [queuesPath, queuesRouter] = createQueuesRouter(this.backend!); - - this.app!.use(basePath + dashboardPath, dashboardRouter); - this.app!.use(basePath + jobsPath, jobsRouter); - this.app!.use(basePath + queuesPath, queuesRouter); - } - - /** - * Starts the dashboard server on the configured port. - * Logs the startup process and handles any errors that occur during server initialization. - * - * @remarks - * If no port is specified in the configuration, the default port 8678 is used. - * - * @returns void - */ - listen() { - const port = this.config!.port ?? 8678; - logger("Dashboard").debug(`Starting Dashboard with port ${port}`); - this.server = this.app!.listen(port, (error) => { - if (error) { - logger("Dashboard").error("Failed to start Sidequest Dashboard!", error); - } else { - logger("Dashboard").info(`Server running on http://localhost:${port}`); - } - }); - } - - /** - * Closes the dashboard by shutting down the backend and server, - * and cleaning up associated resources. - * - * - Awaits the closure of the backend if it exists. - * - Closes the server and logs a message when stopped. - * - Resets backend, server, config, and app properties to `undefined`. - * - Logs a debug message indicating resources have been cleaned up. - * - * @returns {Promise} Resolves when all resources have been closed and cleaned up. - */ - async close() { - try { - await this.backend?.close(); - } catch (error) { - logger("Dashboard").error("Failed to close backend", error); - } - if (this.server) { - await new Promise((resolve) => { - this.server!.close(() => { - logger("Dashboard").info("Sidequest Dashboard stopped"); - resolve(); - }); - }); - } - - this.backend = undefined; - this.server = undefined; - this.config = undefined; - this.app = undefined; - logger("Dashboard").debug("Dashboard resources cleaned up"); - } -} - -export * from "./config"; diff --git a/packages/dashboard/src/public/css/highlight-theme.css b/packages/dashboard/src/public/css/highlight-theme.css deleted file mode 100644 index 0fa21892..00000000 --- a/packages/dashboard/src/public/css/highlight-theme.css +++ /dev/null @@ -1,179 +0,0 @@ -pre code.hljs { - display: block; - overflow-x: auto; - padding: 0; -} - -code.hljs { - padding: 0; -} - -.hljs { - background: transparent; - color: #cdd5e0; -} - -.hljs-keyword { - color: #bb9af7; - font-style: italic; -} - -.hljs-built_in { - color: #9ece6a; - font-style: italic; -} - -.hljs-type { - color: #7aa2f7; -} - -.hljs-literal { - color: #f7768e; -} - -.hljs-number { - color: #7dcfff; -} - -.hljs-regexp { - color: #7aa2f7; -} - -.hljs-string { - color: #9ece6a; -} - -.hljs-subst { - color: #c0caf5; -} - -.hljs-symbol { - color: #7aa2f7; -} - -.hljs-class { - color: #e0af68; -} - -.hljs-function { - color: #7aa2f7; -} - -.hljs-title { - color: #e0af68; - font-style: italic; -} - -.hljs-params { - color: #7dcfff; -} - -.hljs-comment { - color: #565f89; - font-style: italic; -} - -.hljs-doctag { - color: #9ece6a; -} - -.hljs-meta { - color: #7aa2f7; -} - -.hljs-meta .hljs-keyword { - color: #7aa2f7; -} - -.hljs-meta .hljs-string { - color: #9ece6a; -} - -.hljs-section { - color: #7dcfff; -} - -.hljs-tag, -.hljs-name { - color: #9ece6a; -} - -.hljs-attr { - color: #9ece6a; -} - -.hljs-attribute { - color: #9ece6a; -} - -.hljs-variable { - color: #7dcfff; -} - -.hljs-bullet { - color: #9ece6a; -} - -.hljs-code { - color: #cdd5e0; -} - -.hljs-emphasis { - color: #bb9af7; - font-style: italic; -} - -.hljs-strong { - color: #9ece6a; - font-weight: bold; -} - -.hljs-formula { - color: #bb9af7; -} - -.hljs-link { - color: #f7768e; -} - -.hljs-quote { - color: #565f89; - font-style: italic; -} - -.hljs-selector-tag { - color: #f7768e; -} - -.hljs-selector-id { - color: #e0af68; -} - -.hljs-selector-class { - color: #9ece6a; - font-style: italic; -} - -.hljs-selector-attr, -.hljs-selector-pseudo { - color: #bb9af7; - font-style: italic; -} - -.hljs-template-tag { - color: #bb9af7; -} - -.hljs-template-variable { - color: #9ece6a; -} - -.hljs-addition { - color: #9ece6a; - font-style: italic; -} - -.hljs-deletion { - color: #f7768e; - font-style: italic; -} diff --git a/packages/dashboard/src/public/css/styles.css b/packages/dashboard/src/public/css/styles.css deleted file mode 100644 index 7ab76e46..00000000 --- a/packages/dashboard/src/public/css/styles.css +++ /dev/null @@ -1,67 +0,0 @@ -@import "tailwindcss"; -@plugin "daisyui"; - -@plugin "daisyui/theme" { - name: "sidequest-dark"; - default: false; - prefersdark: true; - color-scheme: "dark"; - - --color-base-100: #0c1226; - --color-base-200: #182038; - --color-base-300: #2a3b5f; - --color-base-content: #cdd5e0; - - --color-primary: #2b7cd3; - --color-primary-content: #ffffff; - - --color-secondary: #d1d5db; - --color-secondary-content: #374151; - - --color-accent: #4c5faf; - --color-accent-content: #cdd5e0; - - --color-neutral: #2c3448; - --color-neutral-content: #cdd5e0; - - --color-info: #3d7dd8; - --color-info-content: #0c1226; - - --color-success: #4faf75; - --color-success-content: #0c1226; - - --color-warning: #d2a94c; - --color-warning-content: #0c1226; - - --color-error: #b75252; - --color-error-content: #0c1226; - - --radius-selector: 0.5rem; - --radius-field: 0.25rem; - --radius-box: 0.5rem; - - --size-selector: 0.25rem; - --size-field: 0.25rem; - - --border: 1px; - --depth: 1; - --noise: 0; -} - -pretty-json { - --key-color: #7aa2f7; - --arrow-color: #6c6c6c; - --brace-color: #4c566a; - --bracket-color: #4c566a; - --string-color: #9ece6a; - --number-color: #7dcfff; - --null-color: #a9a9a9; - --boolean-color: #bb9af7; - --comma-color: #5e5e5e; - --ellipsis-color: #7e7e7e; - --indent: 2rem; - --font-family: monospace; - --font-size: 0.875rem; -} - -@import "./highlight-theme.css"; diff --git a/packages/dashboard/src/public/img/logo.png b/packages/dashboard/src/public/img/logo.png deleted file mode 100644 index c934737c..00000000 Binary files a/packages/dashboard/src/public/img/logo.png and /dev/null differ diff --git a/packages/dashboard/src/public/js/dashboard.js b/packages/dashboard/src/public/js/dashboard.js deleted file mode 100644 index a4be925d..00000000 --- a/packages/dashboard/src/public/js/dashboard.js +++ /dev/null @@ -1,133 +0,0 @@ -let currentRange = "12m"; - -const now = new Date(); -const labels = []; -for (let i = 11; i >= 0; i--) { - const time = new Date(now.getTime() - i * 60000); - labels.push(time.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" })); -} - -const ctx = document.getElementById("jobsTimeline").getContext("2d"); - -const jobsTimeline = new Chart(ctx, { - type: "line", - data: { - labels: [], // will be set later - datasets: [ - { - label: "Completed", - data: [], - borderColor: "rgb(34, 197, 94)", - backgroundColor: "rgba(34, 197, 94, 0.1)", - tension: 0.4, - fill: true, - }, - { - label: "Failed", - data: [], - borderColor: "rgb(239, 68, 68)", - backgroundColor: "rgba(239, 68, 68, 0.1)", - tension: 0.4, - fill: true, - }, - ], - }, - options: { - responsive: true, - maintainAspectRatio: false, - scales: { - x: { - ticks: { color: "#ccc" }, - grid: { color: "#333" }, - }, - y: { - beginAtZero: true, - ticks: { color: "#ccc" }, - grid: { color: "#333" }, - }, - }, - plugins: { - legend: { - labels: { color: "#ccc" }, - }, - tooltip: { - mode: "index", - intersect: false, - }, - }, - }, -}); - -async function refreshGraph() { - const basePath = window.SIDEQUEST_BASE_PATH || ""; - const res = await fetch(`${basePath}/dashboard/graph-data?range=${currentRange}`); - const graph = await res.json(); - const timestamps = graph.map((entry) => entry.timestamp); - - const newLabels = []; - for (const timestamp of timestamps) { - const bucketTime = new Date(timestamp); - let label; - - if (currentRange === "12d") { - label = bucketTime.toLocaleDateString([], { month: "short", day: "numeric" }); - } else { - label = bucketTime.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }); - } - - newLabels.push(label); - } - - const newCompleted = graph.map((entry) => entry.completed); - const newFailed = graph.map((entry) => entry.failed); - - // Check if we have existing data and this is a time progression - if (jobsTimeline.data.labels.length > 0 && newLabels.length === jobsTimeline.data.labels.length) { - // Check if this is just a time shift by comparing labels - const isTimeShift = - jobsTimeline.data.labels[jobsTimeline.data.labels.length - 1] !== newLabels[newLabels.length - 1]; - - if (isTimeShift) { - // Shift the timeline: remove first elements and add new ones at the end - jobsTimeline.data.labels.shift(); - jobsTimeline.data.labels.push(newLabels[newLabels.length - 1]); - - jobsTimeline.data.datasets[0].data.shift(); - jobsTimeline.data.datasets[0].data.push(newCompleted[newCompleted.length - 1]); - jobsTimeline.data.datasets[0].data[newCompleted.length - 2] = newCompleted[newCompleted.length - 2]; - - jobsTimeline.data.datasets[1].data.shift(); - jobsTimeline.data.datasets[1].data.push(newFailed[newFailed.length - 1]); - jobsTimeline.data.datasets[1].data[newFailed.length - 2] = newFailed[newFailed.length - 2]; - - jobsTimeline.update("default"); // Use default animation for smooth left shift - return; - } - } - - // For initial load or when data structure changes, replace everything - jobsTimeline.data.labels = newLabels; - jobsTimeline.data.datasets[0].data = newCompleted; - jobsTimeline.data.datasets[1].data = newFailed; - jobsTimeline.update("default"); -} - -// Helper function to compare arrays -function arraysEqual(a, b) { - if (a.length !== b.length) return false; - for (let i = 0; i < a.length; i++) { - if (a[i] !== b[i]) return false; - } - return true; -} -refreshGraph(); - -const selectElement = document.getElementById("graph-range"); -selectElement.addEventListener("change", (event) => { - currentRange = event.target.value ?? "12m"; - refreshGraph(); - // Trigger HTMX to refresh stats - htmx.trigger("#dashboard-stats", "refresh"); -}); - -setInterval(refreshGraph, 1000); diff --git a/packages/dashboard/src/public/js/job.js b/packages/dashboard/src/public/js/job.js deleted file mode 100644 index 40a40ccc..00000000 --- a/packages/dashboard/src/public/js/job.js +++ /dev/null @@ -1,52 +0,0 @@ -// Global variable to store details state -let globalDetailsStates = {}; -let globalStackTraces = {}; - -function applySeeMore() { - document.querySelectorAll(".sq-code").forEach((container) => { - const content = container.querySelector(".code-content"); - const btn = container.querySelector(".toggle-btn"); - - const maxHeight = 240; - - if (content.scrollHeight <= maxHeight) { - btn.classList.add("hidden"); - } - - function toggleDetails(expanded) { - if (expanded) { - content.classList.remove("max-h-[15rem]", "overflow-hidden"); - btn.textContent = "See less"; - globalDetailsStates[container.id] = true; // Save state as expanded - } else { - content.classList.add("max-h-[15rem]", "overflow-hidden"); - btn.textContent = "See more"; - globalDetailsStates[container.id] = false; // Save state as not expanded - } - } - - toggleDetails(globalDetailsStates[container.id] !== undefined ? globalDetailsStates[container.id] : false); - - btn.addEventListener("click", () => { - toggleDetails(globalDetailsStates[container.id] !== undefined ? !globalDetailsStates[container.id] : true); - }); - }); -} - -document.addEventListener("DOMContentLoaded", applySeeMore); -document.addEventListener("htmx:afterSwap", applySeeMore); - -// Save and restore stacktrace open/closed -document.addEventListener("htmx:beforeSwap", () => { - const elements = document.getElementsByTagName("details"); - for (const details of elements) { - globalStackTraces[details.id] = details.open; - } -}); - -document.addEventListener("htmx:afterSwap", () => { - const elements = document.getElementsByTagName("details"); - for (const details of elements) { - details.open = globalStackTraces[details.id] !== undefined ? globalStackTraces[details.id] : false; - } -}); diff --git a/packages/dashboard/src/public/js/jobs.js b/packages/dashboard/src/public/js/jobs.js deleted file mode 100644 index 4430111b..00000000 --- a/packages/dashboard/src/public/js/jobs.js +++ /dev/null @@ -1,30 +0,0 @@ -document.addEventListener("DOMContentLoaded", () => { - const timeRange = document.getElementById("time-range"); - const customRange = document.getElementById("custom-range"); - - timeRange.addEventListener("change", (e) => { - if (e.target.value === "custom") { - customRange.classList.remove("hidden"); - } else { - customRange.classList.add("hidden"); - } - }); -}); - -document.addEventListener("htmx:configRequest", (evt) => { - const form = evt.target.closest("form"); - if (!form) return; - - const startInput = form.querySelector('input[name="start"]'); - const endInput = form.querySelector('input[name="end"]'); - - if (startInput?.value) { - const startDate = new Date(startInput.value); - evt.detail.parameters.start = startDate.toISOString(); - } - - if (endInput?.value) { - const endDate = new Date(endInput.value); - evt.detail.parameters.end = endDate.toISOString(); - } -}); diff --git a/packages/dashboard/src/public/js/scroll.js b/packages/dashboard/src/public/js/scroll.js deleted file mode 100644 index 4a2a067e..00000000 --- a/packages/dashboard/src/public/js/scroll.js +++ /dev/null @@ -1,12 +0,0 @@ -let savedScrollY = 0; - -// Save and restore scroll position around HTMX swaps -document.addEventListener("DOMContentLoaded", () => { - document.addEventListener("htmx:beforeSwap", () => { - savedScrollY = document.getElementById("main-section").scrollTop; - }); - - document.addEventListener("htmx:afterSwap", () => { - document.getElementById("main-section").scrollTo({ top: savedScrollY }); - }); -}); diff --git a/packages/dashboard/src/public/js/selection.js b/packages/dashboard/src/public/js/selection.js deleted file mode 100644 index ce8b3bfb..00000000 --- a/packages/dashboard/src/public/js/selection.js +++ /dev/null @@ -1,85 +0,0 @@ -// DEV: This file was generated by Claude Sonnet 4.5 and it works :) -// Honestly, I didn't want to write this myself because it's boring af - -let savedSelection = null; - -// Function to save selection -window.saveSelection = function saveSelection() { - const selection = window.getSelection(); - if (selection.rangeCount > 0 && selection.toString().length > 0) { - const range = selection.getRangeAt(0); - - savedSelection = { - text: selection.toString(), - startOffset: range.startOffset, - endOffset: range.endOffset, - startContainerPath: getNodePath(range.startContainer), - endContainerPath: getNodePath(range.endContainer), - }; - } -}; - -// Function to get path to a node for later reconstruction -function getNodePath(node) { - const path = []; - let current = node; - const root = document.body; // Use body as the root instead of job-details - - while (current && current !== root && current !== document.documentElement) { - const parent = current.parentNode; - if (!parent) break; - - const siblings = Array.from(parent.childNodes); - const index = siblings.indexOf(current); - path.unshift({ index, nodeType: current.nodeType }); - current = parent; - } - - return path; -} - -// Function to get node from path -function getNodeFromPath(path) { - const root = document.body; // Use body as the root instead of job-details - if (!root || !path) return null; - - let current = root; - for (const step of path) { - const children = Array.from(current.childNodes); - if (step.index >= children.length) return null; - current = children[step.index]; - if (current.nodeType !== step.nodeType) return null; - } - - return current; -} - -// Function to restore selection -window.restoreSelection = function restoreSelection() { - if (!savedSelection) return; - - try { - const startContainer = getNodeFromPath(savedSelection.startContainerPath); - const endContainer = getNodeFromPath(savedSelection.endContainerPath); - - if (startContainer && endContainer) { - const range = document.createRange(); - range.setStart(startContainer, savedSelection.startOffset); - range.setEnd(endContainer, savedSelection.endOffset); - - const selection = window.getSelection(); - selection.removeAllRanges(); - selection.addRange(range); - } - } catch (e) { - // Selection restoration failed, try fallback with text search - console.debug("Could not restore selection:", e); - } - - savedSelection = null; -}; - -// We don't do a htmx:afterSwap restore here because we do it in layout.ejs after hljs.highlightAll() -document.addEventListener("htmx:beforeSwap", () => { - window.saveSelection(); -}); diff --git a/packages/dashboard/src/resources/dashboard.ts b/packages/dashboard/src/resources/dashboard.ts deleted file mode 100644 index b77e9cec..00000000 --- a/packages/dashboard/src/resources/dashboard.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { Backend } from "@sidequest/backend"; -import { Router } from "express"; - -export function createDashboardRouter(backend: Backend) { - const dashboardRouter = Router(); - - function rangeToMs(range: string | undefined) { - let rangeMs: number; - switch (range) { - case "12h": - rangeMs = 12 * 60 * 60 * 1000; - break; - case "12d": - rangeMs = 12 * 24 * 60 * 60 * 1000; - break; - case "12m": - default: - // Defaults to 12m - rangeMs = 12 * 60 * 1000; - break; - } - - return rangeMs; - } - - dashboardRouter.get("/", async (req, res) => { - const { range = "12m" } = req.query; - const from = new Date(Date.now() - rangeToMs(range as string)); - const jobs = await backend.countJobs({ from }); - const jobsNoTimeLimit = await backend.countJobs(); - - res.render("pages/index", { - title: "Sidequest Dashboard", - stats: { - ...jobs, - waiting: jobsNoTimeLimit.waiting, - running: jobsNoTimeLimit.running, - }, - }); - }); - - dashboardRouter.get("/dashboard/stats", async (req, res) => { - const { range = "12m" } = req.query; - const from = new Date(Date.now() - rangeToMs(range as string)); - const jobs = await backend.countJobs({ from }); - - res.render("partials/dashboard-stats", { - stats: jobs, - layout: false, - }); - }); - - dashboardRouter.get("/dashboard/graph-data", async (req, res) => { - const { range = "12m" } = req.query; - - const jobs = await backend.countJobsOverTime(range as string); - - res.json(jobs).end(); - }); - - return ["/", dashboardRouter] as const; -} diff --git a/packages/dashboard/src/resources/jobs.ts b/packages/dashboard/src/resources/jobs.ts deleted file mode 100644 index 3413c96a..00000000 --- a/packages/dashboard/src/resources/jobs.ts +++ /dev/null @@ -1,184 +0,0 @@ -import { Backend } from "@sidequest/backend"; -import { CancelTransition, JobState, RerunTransition, SnoozeTransition } from "@sidequest/core"; -import { JobTransitioner } from "@sidequest/engine"; -import { Router } from "express"; - -export function createJobsRouter(backend: Backend) { - const jobsRouter = Router(); - - jobsRouter.get("/", async (req, res) => { - const { status, start, end, queue, class: jobClass } = req.query; - - const time = typeof req.query.time === "string" && req.query.time.trim() ? req.query.time : "any"; - - const pageSize = req.query.pageSize ? parseInt(req.query.pageSize as string, 10) : 30; - const page = req.query.page ? Math.max(parseInt(req.query.page as string, 10), 1) : 1; - const offset = (page - 1) * pageSize; - - const filters: { - queue?: string; - jobClass?: string; - state?: JobState; - limit?: number; - offset?: number; - args?: unknown[]; - timeRange?: { - from?: Date; - to?: Date; - }; - } = { - limit: pageSize, - offset: offset, - queue: typeof queue === "string" && queue.trim() ? queue : undefined, - jobClass: typeof jobClass === "string" && jobClass.trim() ? "%" + jobClass + "%" : undefined, - state: status as JobState, - }; - - filters.timeRange = computeTimeRange(time, start, end); - - const timeRangeStrings = filters.timeRange - ? { - from: filters.timeRange.from!.toISOString(), - to: (filters.timeRange.to ?? new Date()).toISOString(), - } - : undefined; - - const [jobs, queues, nextPageJobs] = await Promise.all([ - backend?.listJobs(filters), - backend?.getQueuesFromJobs(), - backend?.listJobs({ ...filters, limit: 1, offset: page * pageSize }), - ]); - - const isHtmx = req.get("hx-request"); - - if (isHtmx) { - res.render("partials/jobs-table", { - jobs, - pagination: { - page, - pageSize, - hasNextPage: nextPageJobs.length > 0, - }, - layout: false, - }); - } else { - res.render("pages/jobs", { - title: "Jobs", - jobs, - queues, - filters: { - status: status ?? "", - time: time ?? "", - queue: queue ?? "", - class: jobClass ?? "", - start: start ?? timeRangeStrings?.from ?? "", - end: end ?? timeRangeStrings?.to ?? "", - }, - pagination: { - page, - pageSize, - hasNextPage: nextPageJobs.length > 0, - }, - }); - } - }); - - jobsRouter.get("/:id", async (req, res) => { - const jobId = parseInt(req.params.id); - const job = await backend?.getJob(jobId); - - const isHtmx = req.get("hx-request"); - - if (job) { - if (isHtmx) { - res.render("partials/job-view", { - title: `Job #${job.id}`, - job, - layout: false, - }); - } else { - res.render("pages/job", { - title: `Job #${job.id}`, - job, - }); - } - } else { - res.status(404).send("Job not found!"); - } - }); - - jobsRouter.patch("/:id/run", async (req, res) => { - const jobId = parseInt(req.params.id); - const job = await backend?.getJob(jobId); - - if (job) { - if (job.state === "canceled") { - await JobTransitioner.apply(backend, job, new RerunTransition()); - } else { - await JobTransitioner.apply(backend, job, new SnoozeTransition(0)); - } - res.header("HX-Trigger", "jobChanged").status(200).end(); - } else { - res.status(404).end(); - } - }); - - jobsRouter.patch("/:id/cancel", async (req, res) => { - const jobId = parseInt(req.params.id); - const job = await backend?.getJob(jobId); - - if (job) { - await JobTransitioner.apply(backend, job, new CancelTransition()); - res.header("HX-Trigger", "jobChanged").status(200).end(); - } else { - res.status(404).end(); - } - }); - - jobsRouter.patch("/:id/rerun", async (req, res) => { - const jobId = parseInt(req.params.id); - const job = await backend?.getJob(jobId); - - if (job) { - await JobTransitioner.apply(backend, job, new RerunTransition()); - res.header("HX-Trigger", "jobChanged").status(200).end(); - } else { - res.status(404).end(); - } - }); - - function computeTimeRange(time?: unknown, start?: unknown, end?: unknown) { - if (typeof time !== "string") return undefined; - - const now = Date.now(); - - const minutesMap: Record = { - "5m": 5, - "15m": 15, - "30m": 30, - "1h": 60, - "4h": 240, - "12h": 720, - "24h": 1440, - "2d": 2880, - "7d": 10080, - "30d": 43200, - }; - - if (minutesMap[time]) { - return { from: new Date(now - minutesMap[time] * 60_000) }; - } - - if (time === "custom" && typeof start === "string" && typeof end === "string") { - const fromDate = new Date(start); - const toDate = new Date(end); - if (!isNaN(fromDate.getTime()) && !isNaN(toDate.getTime())) { - return { from: fromDate, to: toDate }; - } - } - - return undefined; - } - - return ["/jobs", jobsRouter] as const; -} diff --git a/packages/dashboard/src/resources/queues.ts b/packages/dashboard/src/resources/queues.ts deleted file mode 100644 index dca792b1..00000000 --- a/packages/dashboard/src/resources/queues.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { Backend } from "@sidequest/backend"; -import { Request, Response, Router } from "express"; - -export async function renderQueuesTable(backend: Backend, req: Request, res: Response) { - const queues = await backend.listQueues({ column: "name", order: "asc" }); - const jobsFromQueues = await backend.countJobsByQueues(); - const parsedQueues = queues?.map((queue) => ({ - ...queue, - jobs: jobsFromQueues[queue.name], - })); - - const isHtmx = req.get("hx-request"); - if (isHtmx) { - res.render("partials/queues-table", { queues: parsedQueues, layout: false }); - } else { - res.render("pages/queues", { title: "Queues", queues: parsedQueues }); - } -} - -export function createQueuesRouter(backend: Backend) { - const queuesRouter = Router(); - - queuesRouter.get("/", async (req, res) => { - await renderQueuesTable(backend, req, res); - }); - - queuesRouter.patch("/:name/toggle", async (req, res) => { - const queue = await backend.getQueue(req.params.name); - if (queue) { - await backend.updateQueue({ ...queue, state: queue.state === "active" ? "paused" : "active" }); - res.header("HX-Trigger", "toggleQueue").status(200).end(); - } else { - res.status(404).end(); - } - }); - - return ["/queues", queuesRouter] as const; -} diff --git a/packages/dashboard/src/views/layout.ejs b/packages/dashboard/src/views/layout.ejs deleted file mode 100644 index 678be9e1..00000000 --- a/packages/dashboard/src/views/layout.ejs +++ /dev/null @@ -1,63 +0,0 @@ - - - - - <%= title || 'Sidequest Dashboard' %> - - - - - - - - - - -
- - - - -
<%- body %>
-
- - - - diff --git a/packages/dashboard/src/views/pages/index.ejs b/packages/dashboard/src/views/pages/index.ejs deleted file mode 100644 index 3c135862..00000000 --- a/packages/dashboard/src/views/pages/index.ejs +++ /dev/null @@ -1,25 +0,0 @@ -
-
- - -
- - - <%- include('../partials/dashboard-stats') %> - - -
-
- -
-
-
- - - diff --git a/packages/dashboard/src/views/pages/job.ejs b/packages/dashboard/src/views/pages/job.ejs deleted file mode 100644 index f72bfc3a..00000000 --- a/packages/dashboard/src/views/pages/job.ejs +++ /dev/null @@ -1,3 +0,0 @@ -<%- include('../partials/job-view', { job }) %> - - diff --git a/packages/dashboard/src/views/pages/jobs.ejs b/packages/dashboard/src/views/pages/jobs.ejs deleted file mode 100644 index e9b8ced2..00000000 --- a/packages/dashboard/src/views/pages/jobs.ejs +++ /dev/null @@ -1,93 +0,0 @@ -
-
-
-
- - -
- -
-
- - -
-
- -
- - -
- -
- - -
- -
-
- - -
-
- - -
-
-
- -
-
-
- <%- include('../partials/jobs-table', { jobs }) %> -
-
-
-
-
- - - - \ No newline at end of file diff --git a/packages/dashboard/src/views/pages/queues.ejs b/packages/dashboard/src/views/pages/queues.ejs deleted file mode 100644 index 1e422f7b..00000000 --- a/packages/dashboard/src/views/pages/queues.ejs +++ /dev/null @@ -1,9 +0,0 @@ -
-
-
-
- <%- include('../partials/queues-table', { queues }) %> -
-
-
-
\ No newline at end of file diff --git a/packages/dashboard/src/views/partials/dashboard-stats.ejs b/packages/dashboard/src/views/partials/dashboard-stats.ejs deleted file mode 100644 index a06e1e14..00000000 --- a/packages/dashboard/src/views/partials/dashboard-stats.ejs +++ /dev/null @@ -1,25 +0,0 @@ -
-
-
Running Jobs
-
<%= stats.running %>
-
-
-
Completed Jobs
-
<%= stats.completed %>
-
-
-
Failed Jobs
-
<%= stats.failed %>
-
-
-
Scheduled
-
<%= stats.waiting %>
-
-
diff --git a/packages/dashboard/src/views/partials/job-view.ejs b/packages/dashboard/src/views/partials/job-view.ejs deleted file mode 100644 index 81ac36ab..00000000 --- a/packages/dashboard/src/views/partials/job-view.ejs +++ /dev/null @@ -1,189 +0,0 @@ -
-
-

- #<%= job.id %> - <%= job.class %> - - <%= job.state.charAt(0).toUpperCase() + job.state.slice(1) %> - -

-
- <% if (!['canceled', 'failed', 'completed'].includes(job.state)){ %> - - <% } %> - <% if (job.available_at > new Date()) { %> - - <% } %> - <% if (['canceled', 'failed', 'completed'].includes(job.state)){ %> - - <% } %> -
-
-
-
-
    - -
  • - - - - Enqueued -
  • - -
  • - - - - Claimed -
  • - -
  • - - - - Running -
  • - - <% if (job.state === 'completed') { %> -
  • - - - - Completed -
  • - <% } else if (job.state === 'failed') { %> -
  • - - - - Failed -
  • - <% } else if (job.state === 'canceled') { %> -
  • - - - - Canceled -
  • - <% } else { %> -
  • - - - - Completed -
  • - <% } %> -
-
-
- - -
-
-

Job Details

- -
-
- Class: - <%= job.class %> -
- -
- Script: - <%= job.script %> -
- -
- Attempts: - <%= job.attempt %> / <%= job.max_attempts %> -
-
- -
-

Constructor Arguments

- - <% if (Array.isArray(job.constructor_args) && job.constructor_args.length > 0) { %> -
-
-
<%= JSON.stringify(job.constructor_args, null, 2) %>
-
-
- -
-
- <% } else { %> -

No arguments

- <% } %> -
- -
-

Run Arguments

- <% if (Array.isArray(job.args) && job.args.length > 0) { %> -
-
-
<%= JSON.stringify(job.args, null, 2) %>
-
-
- -
-
- <% } else { %> -

No arguments

- <% } %> -
- - - <% if (job.result) { %> -
-

Result

-
-
-
<%= JSON.stringify(job.result, null, 2) %>
-
-
- -
-
-
- <% } %> - - <% if (job.errors && job.errors.length > 0) { %> -
-

Errors

-
- <% job.errors.forEach((err, idx) => { %> -
-
- <%= err.message %> - at <%= err.timestamp %> -
-
- Attempt <%= err.attempt %> by <%= err.attempt_by %> -
- <% if (err.stack) { %> -
- Stack Trace -
-      <%- err.stack %>
-                    
-
- <% } %> -
- <% }) %> -
-
- <% } %> -
-
-
\ No newline at end of file diff --git a/packages/dashboard/src/views/partials/jobs-table.ejs b/packages/dashboard/src/views/partials/jobs-table.ejs deleted file mode 100644 index 804b4e0d..00000000 --- a/packages/dashboard/src/views/partials/jobs-table.ejs +++ /dev/null @@ -1,73 +0,0 @@ - - - - - - - - - - - - - - - - <% jobs.forEach(job => { %> - - - - - - - - - - - - <% }) %> - -
IDClassQueueStateAttemptsAvailable AtCompleted AtClaimed By
<%= job.class %><%= job.queue %> - - <%= job.state.charAt(0).toUpperCase() + job.state.slice(1) %> - - <%= job.attempt %> / <%= job.max_attempts %><%= job.available_at?.toISOString?.() || '-' %><%= job.completed_at?.toISOString?.() || '-' %><%= job.claimed_by || '-' %> - <% if (job.available_at > new Date()) { %> - - <% } %> - <% if (!['canceled', 'failed', 'completed'].includes(job.state)){ %> - - <% } %> - <% if (['canceled', 'failed', 'completed'].includes(job.state)){ %> - - <% } %> -
- -
- <% if (pagination.page > 1) { %> - - <% } else { %> - - <% } %> - - - - <% if (pagination.hasNextPage) { %> - - <% } else { %> - - <% } %> -
- - \ No newline at end of file diff --git a/packages/dashboard/src/views/partials/queues-table.ejs b/packages/dashboard/src/views/partials/queues-table.ejs deleted file mode 100644 index bef57f8d..00000000 --- a/packages/dashboard/src/views/partials/queues-table.ejs +++ /dev/null @@ -1,51 +0,0 @@ - - - - - - - - - - - - - - - - - <% queues.forEach(queue => { %> - - - - - - - - - - - - - <% }) %> - -
QueueStateConcurrencyPriorityJobsJobs WaitingJobs RunningJobs CompletedJobs Failed
<%= queue.name %> - - <%= queue.state %> - - <%= queue.concurrency %><%= queue.priority %><%= queue.jobs.total %><%= queue.jobs.waiting %><%= queue.jobs.running %><%= queue.jobs.completed %><%= queue.jobs.failed %> - -
\ No newline at end of file diff --git a/packages/dashboard/tsconfig.json b/packages/dashboard/tsconfig.json deleted file mode 100644 index 3613d0a8..00000000 --- a/packages/dashboard/tsconfig.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "extends": "../../tsconfig.base.json", - "compilerOptions": { - "rootDir": "src", - "outDir": "dist" - }, - "include": ["src/**/*.ts"], - "exclude": ["src/ui/**"] -} diff --git a/packages/sidequest/package.json b/packages/sidequest/package.json index 4d965972..0da1451b 100644 --- a/packages/sidequest/package.json +++ b/packages/sidequest/package.json @@ -61,7 +61,6 @@ "dependencies": { "@sidequest/backend": "workspace:*", "@sidequest/core": "workspace:*", - "@sidequest/dashboard": "workspace:*", "@sidequest/engine": "workspace:*" } } diff --git a/packages/sidequest/src/index.ts b/packages/sidequest/src/index.ts index 6b94f278..ca17a890 100644 --- a/packages/sidequest/src/index.ts +++ b/packages/sidequest/src/index.ts @@ -2,5 +2,4 @@ export * from "./operations"; export * from "@sidequest/backend"; export * from "@sidequest/core"; -export * from "@sidequest/dashboard"; export * from "@sidequest/engine"; diff --git a/packages/sidequest/src/operations/sidequest.test.ts b/packages/sidequest/src/operations/sidequest.test.ts index 486f6ef4..5a9f3782 100644 --- a/packages/sidequest/src/operations/sidequest.test.ts +++ b/packages/sidequest/src/operations/sidequest.test.ts @@ -6,17 +6,6 @@ import { Sidequest } from "./sidequest"; import { SidequestConfig } from "./types"; // Mock dependencies -const mockSidequestDashboard = vi.hoisted(() => ({ - start: vi.fn(), - close: vi.fn(), -})); - -vi.mock("@sidequest/dashboard", () => ({ - SidequestDashboard: vi.fn().mockImplementation(function () { - return mockSidequestDashboard; - }), -})); - const mockEngineInstance = vi.hoisted(() => ({ configure: vi.fn().mockResolvedValue({} as NonNullableEngineConfig), start: vi.fn(), @@ -77,8 +66,6 @@ describe("Sidequest", () => { vi.resetAllMocks(); // Restore original mock implementations - mockSidequestDashboard.start.mockImplementation(vi.fn()); - mockSidequestDashboard.close.mockImplementation(vi.fn()); mockEngineInstance.configure.mockResolvedValue({} as NonNullableEngineConfig); mockEngineInstance.start.mockImplementation(vi.fn()); mockEngineInstance.close.mockImplementation(vi.fn()); @@ -122,20 +109,12 @@ describe("Sidequest", () => { }); describe("start", () => { - it("should configure engine and start both engine and dashboard", async () => { - const config: SidequestConfig = { - ...mockEngineConfig, - dashboard: { port: 4000 }, - }; - vi.mocked(mockEngineInstance.configure).mockResolvedValue(config as NonNullableEngineConfig); - await Sidequest.start(config); - - expect(mockEngineInstance.configure).toHaveBeenCalledWith(config); + it("should configure and start the engine", async () => { + vi.mocked(mockEngineInstance.configure).mockResolvedValue(mockEngineConfig as NonNullableEngineConfig); + await Sidequest.start(mockEngineConfig); + + expect(mockEngineInstance.configure).toHaveBeenCalledWith(mockEngineConfig); expect(mockEngineInstance.start).toHaveBeenCalled(); - expect(mockSidequestDashboard.start).toHaveBeenCalledWith({ - port: 4000, - backendConfig: mockEngineConfig.backend, - }); }); it("should work without config", async () => { @@ -144,42 +123,6 @@ describe("Sidequest", () => { expect(mockEngineInstance.configure).toHaveBeenCalledWith(undefined); expect(mockEngineInstance.start).toHaveBeenCalled(); - expect(mockSidequestDashboard.start).toHaveBeenCalledWith({ - backendConfig: mockEngineConfig.backend, - }); - }); - - it("should work without dashboard config", async () => { - const config: SidequestConfig = { - ...mockEngineConfig, - }; - - vi.mocked(mockEngineInstance.configure).mockResolvedValue(config as NonNullableEngineConfig); - await Sidequest.start(config); - - expect(mockEngineInstance.configure).toHaveBeenCalledWith(config); - expect(mockEngineInstance.start).toHaveBeenCalled(); - expect(mockSidequestDashboard.start).toHaveBeenCalledWith({ - backendConfig: mockEngineConfig.backend, - }); - }); - - it("should pass through dashboard configuration while excluding backendConfig", async () => { - const config: SidequestConfig = { - ...mockEngineConfig, - dashboard: { - port: 5000, - }, - }; - - vi.spyOn(Sidequest, "configure").mockResolvedValue(config as NonNullableEngineConfig); - - await Sidequest.start(config); - - expect(mockSidequestDashboard.start).toHaveBeenCalledWith({ - port: 5000, - backendConfig: mockEngineConfig.backend, - }); }); it("should handle engine configuration errors", async () => { @@ -189,7 +132,7 @@ describe("Sidequest", () => { await expect(Sidequest.start(mockEngineConfig)).rejects.toThrow(); expect(mockEngineInstance.configure).toHaveBeenCalled(); - expect(mockSidequestDashboard.start).not.toHaveBeenCalled(); + expect(mockEngineInstance.start).not.toHaveBeenCalled(); }); it("should handle engine start errors", async () => { @@ -199,20 +142,6 @@ describe("Sidequest", () => { await expect(Sidequest.start(mockEngineConfig)).rejects.toThrow(); expect(mockEngineInstance.start).toHaveBeenCalled(); - expect(mockSidequestDashboard.start).toHaveBeenCalled(); - }); - - it("should handle dashboard start errors", async () => { - const error = new Error("Dashboard start failed"); - - mockSidequestDashboard.start.mockImplementation(() => { - throw error; - }); - - await expect(Sidequest.start(mockEngineConfig)).rejects.toThrow(); - - expect(mockEngineInstance.configure).toHaveBeenCalled(); - expect(mockEngineInstance.start).toHaveBeenCalled(); }); it("should call stop if engine start fails", async () => { @@ -229,31 +158,13 @@ describe("Sidequest", () => { stopSpy.mockRestore(); }); - - it("should call stop if dashboard start fails", async () => { - const error = new Error("Dashboard start failed"); - mockSidequestDashboard.start.mockRejectedValue(error); - - // Spy on the stop method - const stopSpy = vi.spyOn(Sidequest, "stop"); - - await expect(Sidequest.start(mockEngineConfig)).rejects.toThrow(); - - expect(mockEngineInstance.configure).toHaveBeenCalled(); - expect(mockEngineInstance.start).toHaveBeenCalled(); - expect(mockSidequestDashboard.start).toHaveBeenCalled(); - expect(stopSpy).toHaveBeenCalled(); - - stopSpy.mockRestore(); - }); }); describe("stop", () => { - it("should stop both engine and dashboard", async () => { + it("should stop the engine", async () => { await Sidequest.stop(); expect(mockEngineInstance.close).toHaveBeenCalled(); - expect(mockSidequestDashboard.close).toHaveBeenCalled(); }); }); }); diff --git a/packages/sidequest/src/operations/sidequest.ts b/packages/sidequest/src/operations/sidequest.ts index 179d4cb5..1c6834c5 100644 --- a/packages/sidequest/src/operations/sidequest.ts +++ b/packages/sidequest/src/operations/sidequest.ts @@ -1,5 +1,4 @@ import { JobClassType, logger } from "@sidequest/core"; -import { SidequestDashboard } from "@sidequest/dashboard"; import { Engine } from "@sidequest/engine"; import { JobOperations } from "./job"; import { QueueOperations } from "./queue"; @@ -9,17 +8,14 @@ import { KnownDrivers, SidequestConfig, SidequestEngineConfig } from "./types"; * Main entry point for the Sidequest job processing system. * * The Sidequest class provides static methods to configure, start, and build jobs - * within the Sidequest ecosystem. It serves as a high-level interface that coordinates - * the underlying Engine and Dashboard components. + * within the Sidequest ecosystem. It serves as a high-level interface over the + * underlying Engine. * * @example * ```typescript - * // Configure and start Sidequest with dashboard - * const engine = await Sidequest.start({ + * // Configure and start Sidequest + * await Sidequest.start({ * // engine configuration - * dashboard: { - * // dashboard configuration - * } * }); * * // Build and execute a job @@ -33,12 +29,6 @@ export class Sidequest { */ private static engine = new Engine(); - /** - * Static reference to the SidequestDashboard instance. - * This provides access to the dashboard for monitoring and managing jobs and queues. - */ - private static dashboard = new SidequestDashboard(); - /** * Provides access to the singleton QueueOperations instance for managing queues. * @@ -84,21 +74,15 @@ export class Sidequest { } /** - * Starts the Sidequest engine and dashboard with the provided configuration. + * Starts the Sidequest engine with the provided configuration. * - * @param config - Optional configuration object that includes engine settings and dashboard configuration - * @param config.dashboard - Dashboard-specific configuration, excluding backendConfig which is automatically provided - * based on the engine configuration. - * @returns A promise that resolves when the engine and dashboard are fully started. + * @param config - Optional configuration object with engine settings. + * @returns A promise that resolves when the engine is fully started. * * @example * ```typescript - * const engine = await Sidequest.start({ + * await Sidequest.start({ * // engine config... - * dashboard: { - * port: 3000, - * // other dashboard options... - * } * }); * ``` */ @@ -106,11 +90,7 @@ export class Sidequest { try { const engineConfig = await this.configure(config); - const engine = this.engine.start(engineConfig); - const dashboard = this.dashboard.start({ ...config?.dashboard, backendConfig: engineConfig.backend }); - - await engine; - await dashboard; + await this.engine.start(engineConfig); } catch (error) { logger().error("Failed to start Sidequest:", error); await this.stop(); // Ensure cleanup on error @@ -125,7 +105,6 @@ export class Sidequest { * - Closing the engine * - Clearing the job backend * - Clearing the queue backend - * - Closing the dashboard * * @returns A promise that resolves when all cleanup operations are complete */ @@ -133,7 +112,6 @@ export class Sidequest { await this.engine.close(); this.job.setBackend(undefined); this.queue.setBackend(undefined); - await this.dashboard.close(); } /** diff --git a/packages/sidequest/src/operations/types.ts b/packages/sidequest/src/operations/types.ts index b36d16a2..af54eee6 100644 --- a/packages/sidequest/src/operations/types.ts +++ b/packages/sidequest/src/operations/types.ts @@ -1,5 +1,4 @@ import { SQLDriverConfig } from "@sidequest/backend"; -import { DashboardConfig } from "@sidequest/dashboard"; import { EngineConfig } from "@sidequest/engine"; /** @@ -50,9 +49,10 @@ export type SidequestEngineConfig = Omit< }; /** - * Complete Sidequest configuration + * Complete Sidequest configuration. + * + * Currently an alias of {@link SidequestEngineConfig}. The dashboard/web layer is + * being rewritten and no longer wired into `Sidequest.start`; it will be reintroduced + * here once the `@sidequest/web` façade lands. */ -export type SidequestConfig = SidequestEngineConfig & { - /** Optional dashboard configuration */ - dashboard?: Omit; -}; +export type SidequestConfig = SidequestEngineConfig; diff --git a/packages/dashboard/.gitignore b/packages/web/.gitignore similarity index 100% rename from packages/dashboard/.gitignore rename to packages/web/.gitignore diff --git a/packages/dashboard/.storybook/main.ts b/packages/web/.storybook/main.ts similarity index 100% rename from packages/dashboard/.storybook/main.ts rename to packages/web/.storybook/main.ts diff --git a/packages/dashboard/.storybook/preview.tsx b/packages/web/.storybook/preview.tsx similarity index 100% rename from packages/dashboard/.storybook/preview.tsx rename to packages/web/.storybook/preview.tsx diff --git a/packages/web/README.md b/packages/web/README.md new file mode 100644 index 00000000..b0a37e9a --- /dev/null +++ b/packages/web/README.md @@ -0,0 +1,26 @@ +# @sidequest/web + +Web layer for the [Sidequest](https://github.com/sidequestjs/sidequest) job processing system. + +> **Rewrite in progress.** The dashboard is being rebuilt on React + Vite. This package +> currently ships only the UI component library. The OSS dashboard app, the management +> API, and the façade that boots them are landing in follow-up work. + +## Subpath exports + +- `@sidequest/web/ui` — the design-system component library (React). +- `@sidequest/web/ui/styles.css` — design tokens as plain CSS. + +Planned: `@sidequest/web/dashboard` (OSS React app), `@sidequest/web/api` (Hono management +API), and `@sidequest/web` (default façade that composes and serves everything). + +## Usage + +```tsx +import { Button, Table } from "@sidequest/web/ui"; +import "@sidequest/web/ui/styles.css"; +``` + +## License + +LGPL-3.0-or-later diff --git a/packages/dashboard/package.json b/packages/web/package.json similarity index 61% rename from packages/dashboard/package.json rename to packages/web/package.json index 40134b8a..f7933932 100644 --- a/packages/dashboard/package.json +++ b/packages/web/package.json @@ -1,7 +1,7 @@ { - "name": "@sidequest/dashboard", + "name": "@sidequest/web", "version": "1.16.0", - "description": "@sidequest/dashboard is the web dashboard for Sidequest, a distributed background job queue system.", + "description": "@sidequest/web is the web layer for Sidequest: dashboard UI components, the OSS dashboard app, and the management API.", "keywords": [ "nodejs", "javascript", @@ -19,20 +19,12 @@ "repository": { "type": "git", "url": "git+https://github.com/sidequestjs/sidequest.git", - "directory": "packages/dashboard" + "directory": "packages/web" }, "funding": "https://github.com/sponsors/sidequestjs", "packageManager": "yarn@4.9.2", "type": "module", - "main": "./dist/index.cjs", - "module": "./dist/index.js", - "types": "./dist/index.d.ts", "exports": { - ".": { - "types": "./dist/index.d.ts", - "import": "./dist/index.js", - "require": "./dist/index.cjs" - }, "./ui": { "types": "./dist/ui/index.d.ts", "import": "./dist/ui/index.js" @@ -43,40 +35,24 @@ "dist" ], "scripts": { - "build": "npx rollup -c && npx vite build --config vite.lib.config.ts && npx @tailwindcss/cli -i ./src/ui/styles.css -o ./dist/ui/styles.css --minify", - "dev": "npx rollup -c -w", + "build": "npx vite build --config vite.lib.config.ts && npx @tailwindcss/cli -i ./src/ui/styles.css -o ./dist/ui/styles.css --minify", + "dev": "npx vite build --watch --config vite.lib.config.ts", "test": "yarn vitest run", "storybook": "storybook dev -p 6006", "build-storybook": "storybook build" }, "license": "LGPL-3.0-or-later", - "dependencies": { - "@sidequest/backend": "workspace:*", - "@sidequest/core": "workspace:*", - "@sidequest/engine": "workspace:*", - "clsx": "^2.1.1", - "ejs": "^4.0.1", - "express": "^5.2.1", - "express-basic-auth": "^1.2.1", - "express-ejs-layouts": "^2.5.1", - "morgan": "^1.10.1" - }, "devDependencies": { - "@highlightjs/cdn-assets": "^11.11.1", "@storybook/react-vite": "^10.4.6", "@testing-library/dom": "^10.4.1", "@testing-library/jest-dom": "^6.9.1", "@testing-library/react": "^16.3.2", "@testing-library/user-event": "^14.6.1", - "@types/feather-icons": "^4.29.4", - "@types/morgan": "^1.9.10", "@types/react": "^19.2.17", "@types/react-dom": "^19.2.3", "@vitejs/plugin-react": "^6.0.3", - "chart.js": "^4.5.1", + "clsx": "^2.1.1", "daisyui": "5.1.7", - "feather-icons": "^4.29.2", - "htmx.org": "^2.0.8", "jsdom": "^29.1.1", "lucide-react": "^1.23.0", "react": "^19.2.7", diff --git a/packages/dashboard/postcss.config.js b/packages/web/postcss.config.js similarity index 100% rename from packages/dashboard/postcss.config.js rename to packages/web/postcss.config.js diff --git a/packages/dashboard/src/ui/Badge.stories.tsx b/packages/web/src/ui/Badge.stories.tsx similarity index 100% rename from packages/dashboard/src/ui/Badge.stories.tsx rename to packages/web/src/ui/Badge.stories.tsx diff --git a/packages/dashboard/src/ui/Badge.test.tsx b/packages/web/src/ui/Badge.test.tsx similarity index 100% rename from packages/dashboard/src/ui/Badge.test.tsx rename to packages/web/src/ui/Badge.test.tsx diff --git a/packages/dashboard/src/ui/Badge.tsx b/packages/web/src/ui/Badge.tsx similarity index 100% rename from packages/dashboard/src/ui/Badge.tsx rename to packages/web/src/ui/Badge.tsx diff --git a/packages/dashboard/src/ui/Button.stories.tsx b/packages/web/src/ui/Button.stories.tsx similarity index 100% rename from packages/dashboard/src/ui/Button.stories.tsx rename to packages/web/src/ui/Button.stories.tsx diff --git a/packages/dashboard/src/ui/Button.test.tsx b/packages/web/src/ui/Button.test.tsx similarity index 100% rename from packages/dashboard/src/ui/Button.test.tsx rename to packages/web/src/ui/Button.test.tsx diff --git a/packages/dashboard/src/ui/Button.tsx b/packages/web/src/ui/Button.tsx similarity index 100% rename from packages/dashboard/src/ui/Button.tsx rename to packages/web/src/ui/Button.tsx diff --git a/packages/dashboard/src/ui/Card.stories.tsx b/packages/web/src/ui/Card.stories.tsx similarity index 100% rename from packages/dashboard/src/ui/Card.stories.tsx rename to packages/web/src/ui/Card.stories.tsx diff --git a/packages/dashboard/src/ui/Card.test.tsx b/packages/web/src/ui/Card.test.tsx similarity index 100% rename from packages/dashboard/src/ui/Card.test.tsx rename to packages/web/src/ui/Card.test.tsx diff --git a/packages/dashboard/src/ui/Card.tsx b/packages/web/src/ui/Card.tsx similarity index 100% rename from packages/dashboard/src/ui/Card.tsx rename to packages/web/src/ui/Card.tsx diff --git a/packages/dashboard/src/ui/CodeBlock.stories.tsx b/packages/web/src/ui/CodeBlock.stories.tsx similarity index 100% rename from packages/dashboard/src/ui/CodeBlock.stories.tsx rename to packages/web/src/ui/CodeBlock.stories.tsx diff --git a/packages/dashboard/src/ui/CodeBlock.test.tsx b/packages/web/src/ui/CodeBlock.test.tsx similarity index 100% rename from packages/dashboard/src/ui/CodeBlock.test.tsx rename to packages/web/src/ui/CodeBlock.test.tsx diff --git a/packages/dashboard/src/ui/CodeBlock.tsx b/packages/web/src/ui/CodeBlock.tsx similarity index 100% rename from packages/dashboard/src/ui/CodeBlock.tsx rename to packages/web/src/ui/CodeBlock.tsx diff --git a/packages/dashboard/src/ui/FormField.stories.tsx b/packages/web/src/ui/FormField.stories.tsx similarity index 100% rename from packages/dashboard/src/ui/FormField.stories.tsx rename to packages/web/src/ui/FormField.stories.tsx diff --git a/packages/dashboard/src/ui/FormField.test.tsx b/packages/web/src/ui/FormField.test.tsx similarity index 100% rename from packages/dashboard/src/ui/FormField.test.tsx rename to packages/web/src/ui/FormField.test.tsx diff --git a/packages/dashboard/src/ui/FormField.tsx b/packages/web/src/ui/FormField.tsx similarity index 100% rename from packages/dashboard/src/ui/FormField.tsx rename to packages/web/src/ui/FormField.tsx diff --git a/packages/dashboard/src/ui/Icon.stories.tsx b/packages/web/src/ui/Icon.stories.tsx similarity index 100% rename from packages/dashboard/src/ui/Icon.stories.tsx rename to packages/web/src/ui/Icon.stories.tsx diff --git a/packages/dashboard/src/ui/Icon.test.tsx b/packages/web/src/ui/Icon.test.tsx similarity index 100% rename from packages/dashboard/src/ui/Icon.test.tsx rename to packages/web/src/ui/Icon.test.tsx diff --git a/packages/dashboard/src/ui/Icon.tsx b/packages/web/src/ui/Icon.tsx similarity index 100% rename from packages/dashboard/src/ui/Icon.tsx rename to packages/web/src/ui/Icon.tsx diff --git a/packages/dashboard/src/ui/Input.stories.tsx b/packages/web/src/ui/Input.stories.tsx similarity index 100% rename from packages/dashboard/src/ui/Input.stories.tsx rename to packages/web/src/ui/Input.stories.tsx diff --git a/packages/dashboard/src/ui/Input.test.tsx b/packages/web/src/ui/Input.test.tsx similarity index 100% rename from packages/dashboard/src/ui/Input.test.tsx rename to packages/web/src/ui/Input.test.tsx diff --git a/packages/dashboard/src/ui/Input.tsx b/packages/web/src/ui/Input.tsx similarity index 100% rename from packages/dashboard/src/ui/Input.tsx rename to packages/web/src/ui/Input.tsx diff --git a/packages/dashboard/src/ui/NavItem.stories.tsx b/packages/web/src/ui/NavItem.stories.tsx similarity index 100% rename from packages/dashboard/src/ui/NavItem.stories.tsx rename to packages/web/src/ui/NavItem.stories.tsx diff --git a/packages/dashboard/src/ui/NavItem.test.tsx b/packages/web/src/ui/NavItem.test.tsx similarity index 100% rename from packages/dashboard/src/ui/NavItem.test.tsx rename to packages/web/src/ui/NavItem.test.tsx diff --git a/packages/dashboard/src/ui/NavItem.tsx b/packages/web/src/ui/NavItem.tsx similarity index 100% rename from packages/dashboard/src/ui/NavItem.tsx rename to packages/web/src/ui/NavItem.tsx diff --git a/packages/dashboard/src/ui/Pagination.stories.tsx b/packages/web/src/ui/Pagination.stories.tsx similarity index 100% rename from packages/dashboard/src/ui/Pagination.stories.tsx rename to packages/web/src/ui/Pagination.stories.tsx diff --git a/packages/dashboard/src/ui/Pagination.test.tsx b/packages/web/src/ui/Pagination.test.tsx similarity index 100% rename from packages/dashboard/src/ui/Pagination.test.tsx rename to packages/web/src/ui/Pagination.test.tsx diff --git a/packages/dashboard/src/ui/Pagination.tsx b/packages/web/src/ui/Pagination.tsx similarity index 100% rename from packages/dashboard/src/ui/Pagination.tsx rename to packages/web/src/ui/Pagination.tsx diff --git a/packages/dashboard/src/ui/Select.stories.tsx b/packages/web/src/ui/Select.stories.tsx similarity index 100% rename from packages/dashboard/src/ui/Select.stories.tsx rename to packages/web/src/ui/Select.stories.tsx diff --git a/packages/dashboard/src/ui/Select.test.tsx b/packages/web/src/ui/Select.test.tsx similarity index 100% rename from packages/dashboard/src/ui/Select.test.tsx rename to packages/web/src/ui/Select.test.tsx diff --git a/packages/dashboard/src/ui/Select.tsx b/packages/web/src/ui/Select.tsx similarity index 100% rename from packages/dashboard/src/ui/Select.tsx rename to packages/web/src/ui/Select.tsx diff --git a/packages/dashboard/src/ui/Sidebar.stories.tsx b/packages/web/src/ui/Sidebar.stories.tsx similarity index 100% rename from packages/dashboard/src/ui/Sidebar.stories.tsx rename to packages/web/src/ui/Sidebar.stories.tsx diff --git a/packages/dashboard/src/ui/Sidebar.test.tsx b/packages/web/src/ui/Sidebar.test.tsx similarity index 100% rename from packages/dashboard/src/ui/Sidebar.test.tsx rename to packages/web/src/ui/Sidebar.test.tsx diff --git a/packages/dashboard/src/ui/Sidebar.tsx b/packages/web/src/ui/Sidebar.tsx similarity index 100% rename from packages/dashboard/src/ui/Sidebar.tsx rename to packages/web/src/ui/Sidebar.tsx diff --git a/packages/dashboard/src/ui/StatCard.stories.tsx b/packages/web/src/ui/StatCard.stories.tsx similarity index 100% rename from packages/dashboard/src/ui/StatCard.stories.tsx rename to packages/web/src/ui/StatCard.stories.tsx diff --git a/packages/dashboard/src/ui/StatCard.test.tsx b/packages/web/src/ui/StatCard.test.tsx similarity index 100% rename from packages/dashboard/src/ui/StatCard.test.tsx rename to packages/web/src/ui/StatCard.test.tsx diff --git a/packages/dashboard/src/ui/StatCard.tsx b/packages/web/src/ui/StatCard.tsx similarity index 100% rename from packages/dashboard/src/ui/StatCard.tsx rename to packages/web/src/ui/StatCard.tsx diff --git a/packages/dashboard/src/ui/StepProgress.stories.tsx b/packages/web/src/ui/StepProgress.stories.tsx similarity index 100% rename from packages/dashboard/src/ui/StepProgress.stories.tsx rename to packages/web/src/ui/StepProgress.stories.tsx diff --git a/packages/dashboard/src/ui/StepProgress.test.tsx b/packages/web/src/ui/StepProgress.test.tsx similarity index 100% rename from packages/dashboard/src/ui/StepProgress.test.tsx rename to packages/web/src/ui/StepProgress.test.tsx diff --git a/packages/dashboard/src/ui/StepProgress.tsx b/packages/web/src/ui/StepProgress.tsx similarity index 100% rename from packages/dashboard/src/ui/StepProgress.tsx rename to packages/web/src/ui/StepProgress.tsx diff --git a/packages/dashboard/src/ui/Table.stories.tsx b/packages/web/src/ui/Table.stories.tsx similarity index 100% rename from packages/dashboard/src/ui/Table.stories.tsx rename to packages/web/src/ui/Table.stories.tsx diff --git a/packages/dashboard/src/ui/Table.test.tsx b/packages/web/src/ui/Table.test.tsx similarity index 100% rename from packages/dashboard/src/ui/Table.test.tsx rename to packages/web/src/ui/Table.test.tsx diff --git a/packages/dashboard/src/ui/Table.tsx b/packages/web/src/ui/Table.tsx similarity index 100% rename from packages/dashboard/src/ui/Table.tsx rename to packages/web/src/ui/Table.tsx diff --git a/packages/dashboard/src/ui/ThemeToggle.stories.tsx b/packages/web/src/ui/ThemeToggle.stories.tsx similarity index 100% rename from packages/dashboard/src/ui/ThemeToggle.stories.tsx rename to packages/web/src/ui/ThemeToggle.stories.tsx diff --git a/packages/dashboard/src/ui/ThemeToggle.test.tsx b/packages/web/src/ui/ThemeToggle.test.tsx similarity index 100% rename from packages/dashboard/src/ui/ThemeToggle.test.tsx rename to packages/web/src/ui/ThemeToggle.test.tsx diff --git a/packages/dashboard/src/ui/ThemeToggle.tsx b/packages/web/src/ui/ThemeToggle.tsx similarity index 100% rename from packages/dashboard/src/ui/ThemeToggle.tsx rename to packages/web/src/ui/ThemeToggle.tsx diff --git a/packages/dashboard/src/ui/cn.ts b/packages/web/src/ui/cn.ts similarity index 100% rename from packages/dashboard/src/ui/cn.ts rename to packages/web/src/ui/cn.ts diff --git a/packages/dashboard/src/ui/index.ts b/packages/web/src/ui/index.ts similarity index 100% rename from packages/dashboard/src/ui/index.ts rename to packages/web/src/ui/index.ts diff --git a/packages/dashboard/src/ui/styles.css b/packages/web/src/ui/styles.css similarity index 100% rename from packages/dashboard/src/ui/styles.css rename to packages/web/src/ui/styles.css diff --git a/packages/dashboard/src/ui/tokens/base.css b/packages/web/src/ui/tokens/base.css similarity index 100% rename from packages/dashboard/src/ui/tokens/base.css rename to packages/web/src/ui/tokens/base.css diff --git a/packages/dashboard/src/ui/tokens/colors.css b/packages/web/src/ui/tokens/colors.css similarity index 100% rename from packages/dashboard/src/ui/tokens/colors.css rename to packages/web/src/ui/tokens/colors.css diff --git a/packages/dashboard/src/ui/tokens/fonts.css b/packages/web/src/ui/tokens/fonts.css similarity index 100% rename from packages/dashboard/src/ui/tokens/fonts.css rename to packages/web/src/ui/tokens/fonts.css diff --git a/packages/dashboard/src/ui/tokens/radii.css b/packages/web/src/ui/tokens/radii.css similarity index 100% rename from packages/dashboard/src/ui/tokens/radii.css rename to packages/web/src/ui/tokens/radii.css diff --git a/packages/dashboard/src/ui/tokens/spacing.css b/packages/web/src/ui/tokens/spacing.css similarity index 100% rename from packages/dashboard/src/ui/tokens/spacing.css rename to packages/web/src/ui/tokens/spacing.css diff --git a/packages/dashboard/src/ui/tokens/typography.css b/packages/web/src/ui/tokens/typography.css similarity index 100% rename from packages/dashboard/src/ui/tokens/typography.css rename to packages/web/src/ui/tokens/typography.css diff --git a/packages/dashboard/tailwind.config.js b/packages/web/tailwind.config.js similarity index 100% rename from packages/dashboard/tailwind.config.js rename to packages/web/tailwind.config.js diff --git a/packages/dashboard/tsconfig.ui.json b/packages/web/tsconfig.ui.json similarity index 100% rename from packages/dashboard/tsconfig.ui.json rename to packages/web/tsconfig.ui.json diff --git a/packages/dashboard/vite.lib.config.ts b/packages/web/vite.lib.config.ts similarity index 68% rename from packages/dashboard/vite.lib.config.ts rename to packages/web/vite.lib.config.ts index 03e15213..0178dc57 100644 --- a/packages/dashboard/vite.lib.config.ts +++ b/packages/web/vite.lib.config.ts @@ -5,11 +5,10 @@ import dts from "vite-plugin-dts"; const pkgDir = import.meta.dirname; -// Vite builds the `@sidequest/dashboard/ui` component library (JS only). The shipped -// stylesheet (`@sidequest/dashboard/ui/styles.css`) is compiled separately by the -// Tailwind CLI in the build script — it inlines the design tokens and the utilities -// the components use into a single self-contained file. The Express server keeps its -// own Rollup build (see rollup.config.js) untouched. +// Vite builds the `@sidequest/web/ui` component library (JS only). The shipped +// stylesheet (`@sidequest/web/ui/styles.css`) is compiled separately by the Tailwind +// CLI in the build script — it inlines the design tokens and the utilities the +// components use into a single self-contained file. export default defineConfig({ plugins: [ react(), diff --git a/packages/dashboard/vitest.config.js b/packages/web/vitest.config.js similarity index 100% rename from packages/dashboard/vitest.config.js rename to packages/web/vitest.config.js diff --git a/packages/dashboard/vitest.setup.ts b/packages/web/vitest.setup.ts similarity index 100% rename from packages/dashboard/vitest.setup.ts rename to packages/web/vitest.setup.ts diff --git a/release-please-config.json b/release-please-config.json index 54e37cbe..4c617a39 100644 --- a/release-please-config.json +++ b/release-please-config.json @@ -24,7 +24,7 @@ { "type": "json", "path": "packages/sidequest/package.json", "jsonpath": "$.version" }, { "type": "json", "path": "packages/engine/package.json", "jsonpath": "$.version" }, { "type": "json", "path": "packages/core/package.json", "jsonpath": "$.version" }, - { "type": "json", "path": "packages/dashboard/package.json", "jsonpath": "$.version" }, + { "type": "json", "path": "packages/web/package.json", "jsonpath": "$.version" }, { "type": "json", "path": "packages/cli/package.json", "jsonpath": "$.version" }, { "type": "json", "path": "packages/backends/backend/package.json", "jsonpath": "$.version" }, { "type": "json", "path": "packages/backends/backend-test/package.json", "jsonpath": "$.version" }, diff --git a/vitest.config.js b/vitest.config.js index 2f8450f4..8adcbad9 100644 --- a/vitest.config.js +++ b/vitest.config.js @@ -24,8 +24,8 @@ export default defineConfig({ name: "ui", globals: true, environment: "jsdom", - include: ["packages/dashboard/src/ui/**/*.test.tsx"], - setupFiles: ["./packages/dashboard/vitest.setup.ts"], + include: ["packages/web/src/ui/**/*.test.tsx"], + setupFiles: ["./packages/web/vitest.setup.ts"], }, }, ], diff --git a/yarn.lock b/yarn.lock index bb056b28..bfdd95eb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1651,13 +1651,6 @@ __metadata: languageName: node linkType: hard -"@highlightjs/cdn-assets@npm:^11.11.1": - version: 11.11.1 - resolution: "@highlightjs/cdn-assets@npm:11.11.1" - checksum: 10c0/2c7d3bef6bcd4942fdfe531a9b11f38a085ce4cd7ebfae0cf93541f79ad717d80b7a3629b09fa832838874508b80eecc989d16178a27158f037465b0ace63a4a - languageName: node - linkType: hard - "@humanfs/core@npm:^0.19.1": version: 0.19.1 resolution: "@humanfs/core@npm:0.19.1" @@ -2071,13 +2064,6 @@ __metadata: languageName: node linkType: hard -"@kurkle/color@npm:^0.3.0": - version: 0.3.4 - resolution: "@kurkle/color@npm:0.3.4" - checksum: 10c0/0e9fd55c614b005c5f0c4c755bca19ec0293bc7513b4ea3ec1725234f9c2fa81afbc78156baf555c8b9cb0d305619253c3f5bca016067daeebb3d00ebb4ea683 - languageName: node - linkType: hard - "@mongodb-js/saslprep@npm:^1.3.0": version: 1.4.5 resolution: "@mongodb-js/saslprep@npm:1.4.5" @@ -3982,55 +3968,6 @@ __metadata: languageName: unknown linkType: soft -"@sidequest/dashboard@workspace:*, @sidequest/dashboard@workspace:packages/dashboard": - version: 0.0.0-use.local - resolution: "@sidequest/dashboard@workspace:packages/dashboard" - dependencies: - "@highlightjs/cdn-assets": "npm:^11.11.1" - "@sidequest/backend": "workspace:*" - "@sidequest/core": "workspace:*" - "@sidequest/engine": "workspace:*" - "@storybook/react-vite": "npm:^10.4.6" - "@testing-library/dom": "npm:^10.4.1" - "@testing-library/jest-dom": "npm:^6.9.1" - "@testing-library/react": "npm:^16.3.2" - "@testing-library/user-event": "npm:^14.6.1" - "@types/feather-icons": "npm:^4.29.4" - "@types/morgan": "npm:^1.9.10" - "@types/react": "npm:^19.2.17" - "@types/react-dom": "npm:^19.2.3" - "@vitejs/plugin-react": "npm:^6.0.3" - chart.js: "npm:^4.5.1" - clsx: "npm:^2.1.1" - daisyui: "npm:5.1.7" - ejs: "npm:^4.0.1" - express: "npm:^5.2.1" - express-basic-auth: "npm:^1.2.1" - express-ejs-layouts: "npm:^2.5.1" - feather-icons: "npm:^4.29.2" - htmx.org: "npm:^2.0.8" - jsdom: "npm:^29.1.1" - lucide-react: "npm:^1.23.0" - morgan: "npm:^1.10.1" - react: "npm:^19.2.7" - react-dom: "npm:^19.2.7" - storybook: "npm:^10.4.6" - vite: "npm:^8.1.3" - vite-plugin-dts: "npm:^5.0.3" - peerDependencies: - lucide-react: "*" - react: "*" - react-dom: "*" - peerDependenciesMeta: - lucide-react: - optional: true - react: - optional: true - react-dom: - optional: true - languageName: unknown - linkType: soft - "@sidequest/docs@workspace:packages/docs": version: 0.0.0-use.local resolution: "@sidequest/docs@workspace:packages/docs" @@ -4097,6 +4034,41 @@ __metadata: languageName: unknown linkType: soft +"@sidequest/web@workspace:packages/web": + version: 0.0.0-use.local + resolution: "@sidequest/web@workspace:packages/web" + dependencies: + "@storybook/react-vite": "npm:^10.4.6" + "@testing-library/dom": "npm:^10.4.1" + "@testing-library/jest-dom": "npm:^6.9.1" + "@testing-library/react": "npm:^16.3.2" + "@testing-library/user-event": "npm:^14.6.1" + "@types/react": "npm:^19.2.17" + "@types/react-dom": "npm:^19.2.3" + "@vitejs/plugin-react": "npm:^6.0.3" + clsx: "npm:^2.1.1" + daisyui: "npm:5.1.7" + jsdom: "npm:^29.1.1" + lucide-react: "npm:^1.23.0" + react: "npm:^19.2.7" + react-dom: "npm:^19.2.7" + storybook: "npm:^10.4.6" + vite: "npm:^8.1.3" + vite-plugin-dts: "npm:^5.0.3" + peerDependencies: + lucide-react: "*" + react: "*" + react-dom: "*" + peerDependenciesMeta: + lucide-react: + optional: true + react: + optional: true + react-dom: + optional: true + languageName: unknown + linkType: soft + "@sigstore/bundle@npm:^4.0.0": version: 4.0.0 resolution: "@sigstore/bundle@npm:4.0.0" @@ -4730,13 +4702,6 @@ __metadata: languageName: node linkType: hard -"@types/feather-icons@npm:^4.29.4": - version: 4.29.4 - resolution: "@types/feather-icons@npm:4.29.4" - checksum: 10c0/756e33dd395a57fb6b6e55a72834e3eb47cf4ce97bca2c8f7d8b44845c38b0cd397555824b09e562567191aa1d578779d8cde28f52b89fa7a00b7b9298e159b8 - languageName: node - linkType: hard - "@types/fs-extra@npm:^8.0.1": version: 8.1.5 resolution: "@types/fs-extra@npm:8.1.5" @@ -4826,15 +4791,6 @@ __metadata: languageName: node linkType: hard -"@types/morgan@npm:^1.9.10": - version: 1.9.10 - resolution: "@types/morgan@npm:1.9.10" - dependencies: - "@types/node": "npm:*" - checksum: 10c0/419f3fdefd89771f4935b690d7e49954ef8e1e3dd4a050a4b53daa363911df987c14e6d5f4fcba07a931a32c5a6ef1b318b6927c1b390cfb8ec900e84a230c02 - languageName: node - linkType: hard - "@types/node@npm:*": version: 24.3.1 resolution: "@types/node@npm:24.3.1" @@ -5909,7 +5865,7 @@ __metadata: languageName: node linkType: hard -"async@npm:^3.2.3, async@npm:^3.2.6": +"async@npm:^3.2.3": version: 3.2.6 resolution: "async@npm:3.2.6" checksum: 10c0/36484bb15ceddf07078688d95e27076379cc2f87b10c03b6dd8a83e89475a3c8df5848859dd06a4c95af1e4c16fc973de0171a77f18ea00be899aca2a4f85e70 @@ -5976,15 +5932,6 @@ __metadata: languageName: node linkType: hard -"basic-auth@npm:^2.0.1, basic-auth@npm:~2.0.1": - version: 2.0.1 - resolution: "basic-auth@npm:2.0.1" - dependencies: - safe-buffer: "npm:5.1.2" - checksum: 10c0/05f56db3a0fc31c89c86b605231e32ee143fb6ae38dc60616bc0970ae6a0f034172def99e69d3aed0e2c9e7cac84e2d63bc51a0b5ff6ab5fc8808cc8b29923c1 - languageName: node - linkType: hard - "before-after-hook@npm:^4.0.0": version: 4.0.0 resolution: "before-after-hook@npm:4.0.0" @@ -6390,15 +6337,6 @@ __metadata: languageName: node linkType: hard -"chart.js@npm:^4.5.1": - version: 4.5.1 - resolution: "chart.js@npm:4.5.1" - dependencies: - "@kurkle/color": "npm:^0.3.0" - checksum: 10c0/3f2a11dcaae9079e8e6b8ad077e2ae311f04996f9da14815730891e66215ee8b5f2c0eb70b5a156e5bde0f89a41bae13506dc6153e50fd22dcb282b21eec706f - languageName: node - linkType: hard - "check-error@npm:^2.1.1": version: 2.1.3 resolution: "check-error@npm:2.1.3" @@ -6495,13 +6433,6 @@ __metadata: languageName: node linkType: hard -"classnames@npm:^2.2.5": - version: 2.5.1 - resolution: "classnames@npm:2.5.1" - checksum: 10c0/afff4f77e62cea2d79c39962980bf316bacb0d7c49e13a21adaadb9221e1c6b9d3cdb829d8bb1b23c406f4e740507f37e1dcf506f7e3b7113d17c5bab787aa69 - languageName: node - linkType: hard - "clean-stack@npm:^2.0.0": version: 2.2.0 resolution: "clean-stack@npm:2.2.0" @@ -6921,13 +6852,6 @@ __metadata: languageName: node linkType: hard -"core-js@npm:^3.1.3": - version: 3.45.1 - resolution: "core-js@npm:3.45.1" - checksum: 10c0/c38e5fae5a05ee3a129c45e10056aafe61dbb15fd35d27e0c289f5490387541c89741185e0aeb61acb558559c6697e016c245cca738fa169a73f2b06cd30e6b6 - languageName: node - linkType: hard - "core-util-is@npm:~1.0.0": version: 1.0.3 resolution: "core-util-is@npm:1.0.3" @@ -7224,15 +7148,6 @@ __metadata: languageName: node linkType: hard -"debug@npm:2.6.9": - version: 2.6.9 - resolution: "debug@npm:2.6.9" - dependencies: - ms: "npm:2.0.0" - checksum: 10c0/121908fb839f7801180b69a7e218a40b5a0b718813b886b7d6bdb82001b931c938e2941d1e4450f33a1b1df1da653f5f7a0440c197f29fbf8a6e9d45ff6ef589 - languageName: node - linkType: hard - "debug@npm:4, debug@npm:^4.0.0, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.4, debug@npm:^4.3.5, debug@npm:^4.4.0": version: 4.4.1 resolution: "debug@npm:4.4.1" @@ -7571,17 +7486,6 @@ __metadata: languageName: node linkType: hard -"ejs@npm:^4.0.1": - version: 4.0.1 - resolution: "ejs@npm:4.0.1" - dependencies: - jake: "npm:^10.9.1" - bin: - ejs: bin/cli.js - checksum: 10c0/e937a011c5776e6a4775d79317caa9d5b4c64331d62214ff82de97de555ed338bc435d4fa54798ccc0a6e8a2e9096395cbeb79940ab2c3b29f11213ea79b48d6 - languageName: node - linkType: hard - "electron-to-chromium@npm:^1.5.211": version: 1.5.214 resolution: "electron-to-chromium@npm:1.5.214" @@ -8553,22 +8457,6 @@ __metadata: languageName: node linkType: hard -"express-basic-auth@npm:^1.2.1": - version: 1.2.1 - resolution: "express-basic-auth@npm:1.2.1" - dependencies: - basic-auth: "npm:^2.0.1" - checksum: 10c0/01a14cff7a9d1d243d502b0aef287b2e4199e1c3cabd4a2ab3166ad6b122bc77fdeefcc743f57948b2ab59c4a2bcbe8761f3877a0e28959fa53f415667e348f6 - languageName: node - linkType: hard - -"express-ejs-layouts@npm:^2.5.1": - version: 2.5.1 - resolution: "express-ejs-layouts@npm:2.5.1" - checksum: 10c0/0f68cbb7cb3072eca0261e9eb730012954ce8a22fc8cb2188f635185fefe81a69b7dc90662603e50d30ecb30e78e519a9a50217381600d7619abe207799cdbc7 - languageName: node - linkType: hard - "express@npm:^5.2.1": version: 5.2.1 resolution: "express@npm:5.2.1" @@ -8688,16 +8576,6 @@ __metadata: languageName: node linkType: hard -"feather-icons@npm:^4.29.2": - version: 4.29.2 - resolution: "feather-icons@npm:4.29.2" - dependencies: - classnames: "npm:^2.2.5" - core-js: "npm:^3.1.3" - checksum: 10c0/a23f8fbb6e96c901290308bb96267660ddd32c367074411e6a641c030448eff8041072c46e82cbf3bb3c4b2440df107e8defed09ff068e55117db6b867ca7b32 - languageName: node - linkType: hard - "fecha@npm:^4.2.0": version: 4.2.3 resolution: "fecha@npm:4.2.3" @@ -8739,15 +8617,6 @@ __metadata: languageName: node linkType: hard -"filelist@npm:^1.0.4": - version: 1.0.4 - resolution: "filelist@npm:1.0.4" - dependencies: - minimatch: "npm:^5.0.1" - checksum: 10c0/426b1de3944a3d153b053f1c0ebfd02dccd0308a4f9e832ad220707a6d1f1b3c9784d6cadf6b2f68f09a57565f63ebc7bcdc913ccf8012d834f472c46e596f41 - languageName: node - linkType: hard - "fill-range@npm:^7.1.1": version: 7.1.1 resolution: "fill-range@npm:7.1.1" @@ -9542,13 +9411,6 @@ __metadata: languageName: node linkType: hard -"htmx.org@npm:^2.0.8": - version: 2.0.8 - resolution: "htmx.org@npm:2.0.8" - checksum: 10c0/7f866a705d2f1d8bf8a1e0743cb2d6f2d8bb7635d6de85165ef6cc4d87d7210ee6cdf9b411497d1fc58aadae99a69f11266013f68092764cfffde23a44e9681f - languageName: node - linkType: hard - "http-cache-semantics@npm:^4.1.1": version: 4.2.0 resolution: "http-cache-semantics@npm:4.2.0" @@ -10401,19 +10263,6 @@ __metadata: languageName: node linkType: hard -"jake@npm:^10.9.1": - version: 10.9.4 - resolution: "jake@npm:10.9.4" - dependencies: - async: "npm:^3.2.6" - filelist: "npm:^1.0.4" - picocolors: "npm:^1.1.1" - bin: - jake: bin/cli.js - checksum: 10c0/bb52f000340d4a32f1a3893b9abe56ef2b77c25da4dbf2c0c874a8159d082dddda50a5ad10e26060198bd645b928ba8dba3b362710f46a247e335321188c5a9c - languageName: node - linkType: hard - "java-properties@npm:^1.0.2": version: 1.0.2 resolution: "java-properties@npm:1.0.2" @@ -11705,15 +11554,6 @@ __metadata: languageName: node linkType: hard -"minimatch@npm:^5.0.1": - version: 5.1.6 - resolution: "minimatch@npm:5.1.6" - dependencies: - brace-expansion: "npm:^2.0.1" - checksum: 10c0/3defdfd230914f22a8da203747c42ee3c405c39d4d37ffda284dac5e45b7e1f6c49aa8be606509002898e73091ff2a3bbfc59c2c6c71d4660609f63aa92f98e3 - languageName: node - linkType: hard - "minimatch@npm:^9.0.4, minimatch@npm:^9.0.5": version: 9.0.5 resolution: "minimatch@npm:9.0.5" @@ -11932,19 +11772,6 @@ __metadata: languageName: node linkType: hard -"morgan@npm:^1.10.1": - version: 1.10.1 - resolution: "morgan@npm:1.10.1" - dependencies: - basic-auth: "npm:~2.0.1" - debug: "npm:2.6.9" - depd: "npm:~2.0.0" - on-finished: "npm:~2.3.0" - on-headers: "npm:~1.1.0" - checksum: 10c0/2ecd68504d29151b516a6233839e4f27ae0312acc4dbcb1fe84ff9b5db0eb9b25f31258a931dcf689184b4858839572095fcc62eef3cbd7339287d59f1424346 - languageName: node - linkType: hard - "mri@npm:^1.2.0": version: 1.2.0 resolution: "mri@npm:1.2.0" @@ -11952,13 +11779,6 @@ __metadata: languageName: node linkType: hard -"ms@npm:2.0.0": - version: 2.0.0 - resolution: "ms@npm:2.0.0" - checksum: 10c0/f8fda810b39fd7255bbdc451c46286e549794fcc700dc9cd1d25658bbc4dc2563a5de6fe7c60f798a16a60c6ceb53f033cb353f493f0cf63e5199b702943159d - languageName: node - linkType: hard - "ms@npm:2.1.2": version: 2.1.2 resolution: "ms@npm:2.1.2" @@ -12514,22 +12334,6 @@ __metadata: languageName: node linkType: hard -"on-finished@npm:~2.3.0": - version: 2.3.0 - resolution: "on-finished@npm:2.3.0" - dependencies: - ee-first: "npm:1.1.1" - checksum: 10c0/c904f9e518b11941eb60279a3cbfaf1289bd0001f600a950255b1dede9fe3df8cd74f38483550b3bb9485165166acb5db500c3b4c4337aec2815c88c96fcc2ea - languageName: node - linkType: hard - -"on-headers@npm:~1.1.0": - version: 1.1.0 - resolution: "on-headers@npm:1.1.0" - checksum: 10c0/2c3b6b0d68ec9adbd561dc2d61c9b14da8ac03d8a2f0fd9e97bdf0600c887d5d97f664ff3be6876cf40cda6e3c587d73a4745e10b426ac50c7664fc5a0dfc0a1 - languageName: node - linkType: hard - "once@npm:^1.3.0, once@npm:^1.3.1, once@npm:^1.4.0": version: 1.4.0 resolution: "once@npm:1.4.0" @@ -14805,13 +14609,6 @@ __metadata: languageName: node linkType: hard -"safe-buffer@npm:5.1.2, safe-buffer@npm:~5.1.0, safe-buffer@npm:~5.1.1": - version: 5.1.2 - resolution: "safe-buffer@npm:5.1.2" - checksum: 10c0/780ba6b5d99cc9a40f7b951d47152297d0e260f0df01472a1b99d4889679a4b94a13d644f7dbc4f022572f09ae9005fa2fbb93bbbd83643316f365a3e9a45b21 - languageName: node - linkType: hard - "safe-buffer@npm:5.2.1, safe-buffer@npm:^5.0.1, safe-buffer@npm:~5.2.0": version: 5.2.1 resolution: "safe-buffer@npm:5.2.1" @@ -14819,6 +14616,13 @@ __metadata: languageName: node linkType: hard +"safe-buffer@npm:~5.1.0, safe-buffer@npm:~5.1.1": + version: 5.1.2 + resolution: "safe-buffer@npm:5.1.2" + checksum: 10c0/780ba6b5d99cc9a40f7b951d47152297d0e260f0df01472a1b99d4889679a4b94a13d644f7dbc4f022572f09ae9005fa2fbb93bbbd83643316f365a3e9a45b21 + languageName: node + linkType: hard + "safe-identifier@npm:^0.4.2": version: 0.4.2 resolution: "safe-identifier@npm:0.4.2" @@ -15171,7 +14975,6 @@ __metadata: dependencies: "@sidequest/backend": "workspace:*" "@sidequest/core": "workspace:*" - "@sidequest/dashboard": "workspace:*" "@sidequest/engine": "workspace:*" languageName: unknown linkType: soft