From ddccab2d08ee34d6e5deaffea17bdd0307bc76f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jacek=20=C5=81api=C5=84ski?= Date: Fri, 16 Oct 2026 20:39:06 +0200 Subject: [PATCH 1/9] test: update Testcontainers to 4.16.0 Brings SSH.NET 2026.0.0 instead of the vulnerable version. --- src/Directory.Packages.props | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props index b87841c..6299d0a 100644 --- a/src/Directory.Packages.props +++ b/src/Directory.Packages.props @@ -27,8 +27,8 @@ - - + + From 631b55ffb3571cabfe59d6d5aa9e7f86ec1a56fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jacek=20=C5=81api=C5=84ski?= Date: Fri, 16 Oct 2026 20:39:06 +0200 Subject: [PATCH 2/9] chore: update Reiho to 2.0.0 Requests to the dashboard's API are strict: unknown fields and missing non-nullable values return 400. --- src/Directory.Packages.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props index 6299d0a..936cddc 100644 --- a/src/Directory.Packages.props +++ b/src/Directory.Packages.props @@ -19,7 +19,7 @@ - + From 5e8bade95bcccd71cbe3709e70ade8c2fd553a9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jacek=20=C5=81api=C5=84ski?= Date: Fri, 16 Oct 2026 20:39:07 +0200 Subject: [PATCH 3/9] build: fail on packages with known vulnerabilities NuGet audit warnings are errors, as in the other Kododo libraries. --- src/Directory.Build.props | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Directory.Build.props b/src/Directory.Build.props index ecd9420..53b1c65 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -5,7 +5,6 @@ enable Latest true - NU1901;NU1902;NU1903;NU1904 From 2f8738bf656858ae129328645a86fc0eca8ed9fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jacek=20=C5=81api=C5=84ski?= Date: Fri, 16 Oct 2026 20:39:07 +0200 Subject: [PATCH 4/9] test: keep the shared storage tests out of the test run RunWay.Storage.Tests only holds base classes for the PostgreSQL and SQL Server tests. --- src/RunWay.Storage.Tests/RunWay.Storage.Tests.csproj | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/RunWay.Storage.Tests/RunWay.Storage.Tests.csproj b/src/RunWay.Storage.Tests/RunWay.Storage.Tests.csproj index 21b96fc..f3abe21 100644 --- a/src/RunWay.Storage.Tests/RunWay.Storage.Tests.csproj +++ b/src/RunWay.Storage.Tests/RunWay.Storage.Tests.csproj @@ -4,6 +4,8 @@ net10.0 false + + false From 22a372cc4a10cbc6b2fe800ca7965c4219640043 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jacek=20=C5=81api=C5=84ski?= Date: Sat, 17 Oct 2026 13:20:22 +0200 Subject: [PATCH 5/9] feat!: run the dashboard in Way.Shell The dashboard is a Way.Shell module: layout, theme, language, API client and session handling come from the shell, and UseRunWayDashboard() mounts it with MapWay. Outside Development the dashboard answers 403 until the host calls RequireAuthorization() or AllowAnonymous() on what UseRunWayDashboard() returns. MUI 7 -> 9, Vite 7 -> 8, MUI X Data Grid 9 and Lab 9 pre-releases. --- README.md | 6 +- src/Directory.Build.props | 9 + src/Directory.Packages.props | 14 +- src/RunWay.Dashboard/.nuget/README.md | 8 +- src/RunWay.Dashboard/RunWay.Dashboard.csproj | 8 + .../RunWayConfigurationExtensions.cs | 9 +- ...ayDashboardApplicationBuilderExtensions.cs | 34 +- src/RunWay.Dashboard/SPA/.npmrc | 3 + src/RunWay.Dashboard/SPA/index.html | 8 +- src/RunWay.Dashboard/SPA/package-lock.json | 1776 +++++++---------- src/RunWay.Dashboard/SPA/package.json | 23 +- src/RunWay.Dashboard/SPA/src/App.tsx | 63 +- src/RunWay.Dashboard/SPA/src/api/api.ts | 91 +- .../components/dashboard/DashboardTiles.tsx | 4 +- .../SPA/src/components/jobs/JobsTable.tsx | 4 +- .../SPA/src/components/layout/MainLayout.tsx | 136 -- .../SPA/src/components/layout/PageHeader.tsx | 49 +- .../SPA/src/components/layout/SideMenu.tsx | 184 +- .../recurrences/RecurrencesTable.tsx | 4 +- .../SPA/src/context/JobMetricsContext.tsx | 7 +- .../SPA/src/context/LayoutContext.tsx | 11 - .../SPA/src/context/RecurrencesContext.tsx | 7 +- .../SPA/src/context/RunnersContext.tsx | 7 +- .../SPA/src/context/ThemeContext.tsx | 119 -- src/RunWay.Dashboard/SPA/src/fonts.css | 2 + src/RunWay.Dashboard/SPA/src/index.css | 24 - src/RunWay.Dashboard/SPA/src/main.tsx | 11 +- .../SPA/src/pages/JobPage.tsx | 9 +- .../SPA/src/pages/JobsPage.tsx | 9 +- .../SPA/src/pages/RecurrencePage.tsx | 11 +- .../SPA/src/pages/RecurrencesPage.tsx | 9 +- .../SPA/src/pages/RunnerPage.tsx | 10 +- .../SPA/src/pages/RunnersPage.tsx | 4 +- src/RunWay.Dashboard/SPA/src/theme.ts | 61 + .../SPA/src/utils/useWayNavigate.ts | 15 + src/RunWay.Dashboard/SPA/src/way.tsx | 7 + src/RunWay.Dashboard/SPA/vite.config.ts | 17 +- src/RunWay.Demo.WebApp/Program.cs | 3 +- src/RunWay.Demo.WebApp/e2e/check.mjs | 96 + .../Integration/DashboardShellTests.cs | 67 + src/RunWay.Tests/RunWay.Tests.csproj | 6 + 41 files changed, 1309 insertions(+), 1636 deletions(-) create mode 100644 src/RunWay.Dashboard/SPA/.npmrc delete mode 100644 src/RunWay.Dashboard/SPA/src/components/layout/MainLayout.tsx delete mode 100644 src/RunWay.Dashboard/SPA/src/context/LayoutContext.tsx delete mode 100644 src/RunWay.Dashboard/SPA/src/context/ThemeContext.tsx create mode 100644 src/RunWay.Dashboard/SPA/src/fonts.css delete mode 100644 src/RunWay.Dashboard/SPA/src/index.css create mode 100644 src/RunWay.Dashboard/SPA/src/theme.ts create mode 100644 src/RunWay.Dashboard/SPA/src/utils/useWayNavigate.ts create mode 100644 src/RunWay.Dashboard/SPA/src/way.tsx create mode 100644 src/RunWay.Demo.WebApp/e2e/check.mjs create mode 100644 src/RunWay.Tests/Integration/DashboardShellTests.cs diff --git a/README.md b/README.md index d45f7b0..b486a37 100644 --- a/README.md +++ b/README.md @@ -75,7 +75,7 @@ public class OrderService(IScheduler scheduler) ### 5. Mount the dashboard ```csharp -app.UseRunWayDashboard(); // available at /scheduler +app.UseRunWayDashboard().RequireAuthorization("Admin"); // available at /scheduler ``` --- @@ -178,6 +178,10 @@ app.UseRunWayDashboard() .RequireAuthorization(policy => policy.RequireRole("Admin")); ``` +Outside the Development environment the dashboard and its API answer `403` until you decide who may use them: `RequireAuthorization(...)`, `AllowAnonymous()` for a public demo, or a fallback authorization policy. A dashboard nobody decided on is reported in the log at startup. + +The dashboard runs in [Way.Shell](https://github.com/kododo-dev/WayShell), the UI shell shared by the Kododo Way libraries: the layout, theme, language and session handling come from the shell, and with other Ways in the same app it can also be opened from the shell's panel (`app.UseWayShell("/admin")`). Configure the shell's title, links and user with `services.AddWayShell(o => ...)`. + The dashboard shows job counts by status, a paginated job list, per-job audit timeline, recurring job schedules, and runner health. ![Dashboard overview](src/docs/screenshots/01-dashboard.png) diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 53b1c65..7b2253c 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -11,6 +11,15 @@ none + + + true + $(MSBuildThisFileDirectory)..\..\WayShell\src + + Kododo.$(MSBuildProjectName) Kododo.$(MSBuildProjectName) diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props index 936cddc..6df57ee 100644 --- a/src/Directory.Packages.props +++ b/src/Directory.Packages.props @@ -4,6 +4,13 @@ true + + + 10.0.13 + 8.0.31 + 9.0.20 + + @@ -14,15 +21,18 @@ - + + - + + + diff --git a/src/RunWay.Dashboard/.nuget/README.md b/src/RunWay.Dashboard/.nuget/README.md index 34458d0..46fbcdd 100644 --- a/src/RunWay.Dashboard/.nuget/README.md +++ b/src/RunWay.Dashboard/.nuget/README.md @@ -29,13 +29,19 @@ app.UseRunWayDashboard("/jobs/dashboard"); ## Authorization -`UseRunWayDashboard()` returns a `RouteGroupBuilder`: +`UseRunWayDashboard()` returns a `RouteGroupBuilder`. Outside the Development environment the dashboard answers `403` until you decide who may use it: ```csharp app.UseRunWayDashboard() .RequireAuthorization(policy => policy.RequireRole("Admin")); + +app.UseRunWayDashboard().AllowAnonymous(); // a public demo ``` +## Way.Shell + +The dashboard runs in [Kododo.Way.Shell](https://www.nuget.org/packages/Kododo.Way.Shell): the layout, theme, language and session handling come from the shell. Configure the title, links and user with `services.AddWayShell(o => ...)`, and add `app.UseWayShell("/admin")` for one panel with every Way in the app. + ## Features - Job list with pagination and filtering by status diff --git a/src/RunWay.Dashboard/RunWay.Dashboard.csproj b/src/RunWay.Dashboard/RunWay.Dashboard.csproj index 7f34484..5af019a 100644 --- a/src/RunWay.Dashboard/RunWay.Dashboard.csproj +++ b/src/RunWay.Dashboard/RunWay.Dashboard.csproj @@ -22,6 +22,14 @@ + + + + + + + + diff --git a/src/RunWay.Dashboard/RunWayConfigurationExtensions.cs b/src/RunWay.Dashboard/RunWayConfigurationExtensions.cs index b318539..ab2a152 100644 --- a/src/RunWay.Dashboard/RunWayConfigurationExtensions.cs +++ b/src/RunWay.Dashboard/RunWayConfigurationExtensions.cs @@ -1,12 +1,19 @@ using Kododo.Reiho.AspNetCore.API; +using Kododo.Way.Shell; namespace Kododo.RunWay.Dashboard; public static class RunWayConfigurationExtensions { + /// + /// Adds the dashboard's API handlers and Way.Shell, which the dashboard runs in. Mount it with + /// app.UseRunWayDashboard(path); configure the shell (title, links, user) with + /// services.AddWayShell(o => ...). + /// public static IRunWayConfiguration AddDashboard(this IRunWayConfiguration configuration) { configuration.Services.AddRequestHandlers(typeof(RunWayConfigurationExtensions).Assembly); + configuration.Services.AddWayShell(); return configuration; } -} \ No newline at end of file +} diff --git a/src/RunWay.Dashboard/RunWayDashboardApplicationBuilderExtensions.cs b/src/RunWay.Dashboard/RunWayDashboardApplicationBuilderExtensions.cs index 7b142e5..3d99436 100644 --- a/src/RunWay.Dashboard/RunWayDashboardApplicationBuilderExtensions.cs +++ b/src/RunWay.Dashboard/RunWayDashboardApplicationBuilderExtensions.cs @@ -1,5 +1,5 @@ using Kododo.Reiho.AspNetCore.API; -using Kododo.Reiho.AspNetCore.SPA; +using Kododo.Way.Shell; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Routing; @@ -7,22 +7,30 @@ namespace Kododo.RunWay.Dashboard; public static class RunWayDashboardApplicationBuilderExtensions { + /// The dashboard as a Way.Shell module: SPA/dist is built with the wayModule() preset. + internal static readonly WayModule Module = + new("scheduler", "Scheduler", typeof(RunWayDashboardApplicationBuilderExtensions).Assembly, "SPA/dist") { Icon = "schedule" }; + extension(IEndpointRouteBuilder endpoints) { + /// + /// Mounts the dashboard and its API at , in Way.Shell. The API is at {path}/api. + /// + /// + /// The group of the dashboard's endpoints, for authorization and other conventions. Outside the Development + /// environment the dashboard and its API answer 403 until authorization is required here, anonymous + /// access is allowed explicitly (AllowAnonymous()) or ShellOptions.AllowAnonymous is set. + /// + /// + /// + /// app.UseRunWayDashboard("/scheduler").RequireAuthorization("Admin"); + /// + /// public RouteGroupBuilder UseRunWayDashboard(string path = "/scheduler") { - var runwayEndpoints = endpoints.MapGroup(path); - - runwayEndpoints.MapApi(); - runwayEndpoints.MapEmbeddedSpa(typeof(RunWayDashboardApplicationBuilderExtensions).Assembly); - + var runwayEndpoints = endpoints.MapWay(Module, path); + runwayEndpoints.MapGroup("/api").MapRequests(typeof(RunWayDashboardApplicationBuilderExtensions).Assembly); return runwayEndpoints; } - - private void MapApi() - { - var api = endpoints.MapGroup("/api"); - api.MapRequests(typeof(RunWayDashboardApplicationBuilderExtensions).Assembly); - } } -} \ No newline at end of file +} diff --git a/src/RunWay.Dashboard/SPA/.npmrc b/src/RunWay.Dashboard/SPA/.npmrc new file mode 100644 index 0000000..aa13fb5 --- /dev/null +++ b/src/RunWay.Dashboard/SPA/.npmrc @@ -0,0 +1,3 @@ +# @kododo-dev/way-shell comes from ../WayShell until it is on npm; install it as a copy, like a registry package, +# so it uses the shared libraries installed here. +install-links=true diff --git a/src/RunWay.Dashboard/SPA/index.html b/src/RunWay.Dashboard/SPA/index.html index 2e57ce9..4c14854 100644 --- a/src/RunWay.Dashboard/SPA/index.html +++ b/src/RunWay.Dashboard/SPA/index.html @@ -2,13 +2,9 @@ - + - Scheduler - - - - + RunWay (dev)
diff --git a/src/RunWay.Dashboard/SPA/package-lock.json b/src/RunWay.Dashboard/SPA/package-lock.json index 1c3d380..eb0da43 100644 --- a/src/RunWay.Dashboard/SPA/package-lock.json +++ b/src/RunWay.Dashboard/SPA/package-lock.json @@ -10,29 +10,30 @@ "dependencies": { "@emotion/react": "^11.14.0", "@emotion/styled": "^11.14.1", - "@mui/icons-material": "^7.3.6", - "@mui/lab": "^7.0.1-beta.23", - "@mui/material": "^7.3.6", - "@mui/x-data-grid": "^8.22.1", + "@kododo-dev/way-shell": "file:../../../../WayShell/src/way-shell", + "@mui/icons-material": "^9.5.0", + "@mui/lab": "^9.0.0-beta.10", + "@mui/material": "^9.5.0", + "@mui/x-data-grid": "^9.0.0-rc.0", "dayjs": "^1.11.19", - "react": "^19.2.4", - "react-dom": "^19.2.4", - "react-router-dom": "^7.18.3", + "react": "^19.3.1", + "react-dom": "^19.3.1", + "react-router-dom": "^7.18.4", "recharts": "^3.8.0" }, "devDependencies": { "@eslint/js": "^9.39.1", "@types/node": "^24.10.1", - "@types/react": "^19.2.5", + "@types/react": "^19.3.0", "@types/react-dom": "^19.2.3", - "@vitejs/plugin-react": "^5.1.1", + "@vitejs/plugin-react": "^6.1.2", "eslint": "^9.39.1", "eslint-plugin-react-hooks": "^7.0.1", "eslint-plugin-react-refresh": "^0.4.24", "globals": "^16.5.0", - "typescript": "~5.9.3", + "typescript": "~6.0.2", "typescript-eslint": "^8.46.4", - "vite": "^7.3.6" + "vite": "^8.3.4" } }, "node_modules/@babel/code-frame": { @@ -170,16 +171,6 @@ "@babel/core": "^7.0.0" } }, - "node_modules/@babel/helper-plugin-utils": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", - "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/helper-string-parser": { "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", @@ -237,38 +228,6 @@ "node": ">=6.0.0" } }, - "node_modules/@babel/plugin-transform-react-jsx-self": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", - "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-react-jsx-source": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", - "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, "node_modules/@babel/runtime": { "version": "7.29.2", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz", @@ -469,448 +428,6 @@ "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==", "license": "MIT" }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.2.tgz", - "integrity": "sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.2.tgz", - "integrity": "sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.2.tgz", - "integrity": "sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.2.tgz", - "integrity": "sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.2.tgz", - "integrity": "sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.2.tgz", - "integrity": "sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.2.tgz", - "integrity": "sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.2.tgz", - "integrity": "sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.2.tgz", - "integrity": "sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.2.tgz", - "integrity": "sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.2.tgz", - "integrity": "sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.2.tgz", - "integrity": "sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.2.tgz", - "integrity": "sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.2.tgz", - "integrity": "sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.2.tgz", - "integrity": "sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.2.tgz", - "integrity": "sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.2.tgz", - "integrity": "sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.2.tgz", - "integrity": "sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.2.tgz", - "integrity": "sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.2.tgz", - "integrity": "sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.2.tgz", - "integrity": "sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openharmony-arm64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.2.tgz", - "integrity": "sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.2.tgz", - "integrity": "sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.2.tgz", - "integrity": "sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.2.tgz", - "integrity": "sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.2.tgz", - "integrity": "sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, "node_modules/@eslint-community/eslint-utils": { "version": "4.9.1", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", @@ -1180,10 +697,30 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@kododo-dev/way-shell": { + "version": "0.0.0", + "resolved": "file:../../../../WayShell/src/way-shell", + "license": "MIT", + "peerDependencies": { + "@emotion/react": "^11.14.0", + "@emotion/styled": "^11.14.0", + "@mui/icons-material": "^9.0.0", + "@mui/material": "^9.0.0", + "react": "^19.2.0", + "react-dom": "^19.2.0", + "react-router-dom": "^7.0.0", + "vite": "^8.0.0" + }, + "peerDependenciesMeta": { + "vite": { + "optional": true + } + } + }, "node_modules/@mui/core-downloads-tracker": { - "version": "7.3.10", - "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-7.3.10.tgz", - "integrity": "sha512-vrOpWRmPJSuwLo23J62wggEm/jvGdzqctej+UOCtgDUz6nZJQuj3ByPccVyaa7eQmwAzUwKN56FQPMKkqbj1GA==", + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-9.5.0.tgz", + "integrity": "sha512-PErEpl3Lp+1ZqWYKI7GQU8XSiciAFmLbUAZ7IoaUL598HLy1UAFuEUMEJu8yAS1v8vkXYzWprMVZzky4UwXoOA==", "license": "MIT", "funding": { "type": "opencollective", @@ -1191,12 +728,12 @@ } }, "node_modules/@mui/icons-material": { - "version": "7.3.10", - "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-7.3.10.tgz", - "integrity": "sha512-Au0ma4NSKGKNiimukj8UT/W1x2Qx6Qwn2RvFGykiSqVLYBNlIOPbjnIMvrwLGLu89EEpTVdu/ys/OduZR+tWqw==", + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-9.5.0.tgz", + "integrity": "sha512-dahtVaqnRUIBgaZ4WC9EdP+LgJ5d2rslASYIX+nDFG6+ssUiM11jQ+Gd1wH8vhUGoNA9ES40MYics5BI8zJFXQ==", "license": "MIT", "dependencies": { - "@babel/runtime": "^7.28.6" + "@babel/runtime": "^7.29.7 || ^8.0.5" }, "engines": { "node": ">=14.0.0" @@ -1206,7 +743,7 @@ "url": "https://opencollective.com/mui-org" }, "peerDependencies": { - "@mui/material": "^7.3.10", + "@mui/material": "^9.5.0", "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", "react": "^17.0.0 || ^18.0.0 || ^19.0.0" }, @@ -1216,16 +753,22 @@ } } }, + "node_modules/@mui/icons-material/node_modules/@babel/runtime": { + "version": "8.0.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-8.0.7.tgz", + "integrity": "sha512-k0hagDDq/6w4v+0Sp4wFvIz/yswH5O7Va6aRTnD7N62Tnborp4X+YB+H3XUcWVSR4aE47WPEXiIIZchBs0GmYg==", + "license": "MIT" + }, "node_modules/@mui/lab": { - "version": "7.0.1-beta.24", - "resolved": "https://registry.npmjs.org/@mui/lab/-/lab-7.0.1-beta.24.tgz", - "integrity": "sha512-ihgrrxeJfB3owIggdca4vtgWIFBgG4RGQV548DAbtClJqwEMmc4JXWW8Imt+b5RbDyQ1FRAAXhE/wuieAXlgRQ==", + "version": "9.0.0-beta.10", + "resolved": "https://registry.npmjs.org/@mui/lab/-/lab-9.0.0-beta.10.tgz", + "integrity": "sha512-XLjMJRiOQfD2p5YIorNuymI3CZqrAIAZJsaaeoomMEwAp3p1VwpSmR072VZZr+PfB1r2aUvouxmCXQNGHyw4qw==", "license": "MIT", "dependencies": { - "@babel/runtime": "^7.28.6", - "@mui/system": "^7.3.10", - "@mui/types": "^7.4.12", - "@mui/utils": "^7.3.10", + "@babel/runtime": "^7.29.7 || ^8.0.5", + "@mui/system": "^9.5.0", + "@mui/types": "^9.5.0", + "@mui/utils": "^9.5.0", "clsx": "^2.1.1", "prop-types": "^15.8.1" }, @@ -1239,8 +782,8 @@ "peerDependencies": { "@emotion/react": "^11.5.0", "@emotion/styled": "^11.3.0", - "@mui/material": "^7.3.10", - "@mui/material-pigment-css": "^7.3.10", + "@mui/material": "^9.5.0", + "@mui/material-pigment-css": "^9.5.0", "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", "react": "^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" @@ -1260,23 +803,29 @@ } } }, + "node_modules/@mui/lab/node_modules/@babel/runtime": { + "version": "8.0.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-8.0.7.tgz", + "integrity": "sha512-k0hagDDq/6w4v+0Sp4wFvIz/yswH5O7Va6aRTnD7N62Tnborp4X+YB+H3XUcWVSR4aE47WPEXiIIZchBs0GmYg==", + "license": "MIT" + }, "node_modules/@mui/material": { - "version": "7.3.10", - "resolved": "https://registry.npmjs.org/@mui/material/-/material-7.3.10.tgz", - "integrity": "sha512-cHvGOk2ZEfbQt3LnGe0ZKd/ETs9gsUpkW66DCO+GSjMZhpdKU4XsuIr7zJ/B/2XaN8ihxuzHfYAR4zPtCN4RYg==", + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/@mui/material/-/material-9.5.0.tgz", + "integrity": "sha512-QgqvdVjZoKxP8E8SBxYY6LrdV+kw00moXweZQWBbMMdifPKZAGfUaaXhGKIca6fjKnCL6HJIe/AWWCpqNvyHVw==", "license": "MIT", "dependencies": { - "@babel/runtime": "^7.28.6", - "@mui/core-downloads-tracker": "^7.3.10", - "@mui/system": "^7.3.10", - "@mui/types": "^7.4.12", - "@mui/utils": "^7.3.10", + "@babel/runtime": "^7.29.7 || ^8.0.5", + "@mui/core-downloads-tracker": "^9.5.0", + "@mui/system": "^9.5.0", + "@mui/types": "^9.5.0", + "@mui/utils": "^9.5.0", "@popperjs/core": "^2.11.8", "@types/react-transition-group": "^4.4.12", "clsx": "^2.1.1", "csstype": "^3.2.3", "prop-types": "^15.8.1", - "react-is": "^19.2.3", + "react-is": "^19.3.0", "react-transition-group": "^4.4.5" }, "engines": { @@ -1289,7 +838,7 @@ "peerDependencies": { "@emotion/react": "^11.5.0", "@emotion/styled": "^11.3.0", - "@mui/material-pigment-css": "^7.3.10", + "@mui/material-pigment-css": "^9.5.0", "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", "react": "^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" @@ -1309,14 +858,20 @@ } } }, + "node_modules/@mui/material/node_modules/@babel/runtime": { + "version": "8.0.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-8.0.7.tgz", + "integrity": "sha512-k0hagDDq/6w4v+0Sp4wFvIz/yswH5O7Va6aRTnD7N62Tnborp4X+YB+H3XUcWVSR4aE47WPEXiIIZchBs0GmYg==", + "license": "MIT" + }, "node_modules/@mui/private-theming": { - "version": "7.3.10", - "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-7.3.10.tgz", - "integrity": "sha512-j3EZN+zOctxUISvJSmsEPo5o2F8zse4l5vRkBY+ps6UtnL6J7o14kUaI4w7gwo73id9e3cDNMVQK/9BVaMHVBw==", + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-9.5.0.tgz", + "integrity": "sha512-UHZKySlr4ZJeA5HEhe/Tq1fhsR5573ViKYn7JV80dCVJtgqRQIplRSer1DHPg2Yi22llX7QRhgM0Vtrx4kZHSA==", "license": "MIT", "dependencies": { - "@babel/runtime": "^7.28.6", - "@mui/utils": "^7.3.10", + "@babel/runtime": "^7.29.7 || ^8.0.5", + "@mui/utils": "^9.5.0", "prop-types": "^15.8.1" }, "engines": { @@ -1336,13 +891,19 @@ } } }, + "node_modules/@mui/private-theming/node_modules/@babel/runtime": { + "version": "8.0.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-8.0.7.tgz", + "integrity": "sha512-k0hagDDq/6w4v+0Sp4wFvIz/yswH5O7Va6aRTnD7N62Tnborp4X+YB+H3XUcWVSR4aE47WPEXiIIZchBs0GmYg==", + "license": "MIT" + }, "node_modules/@mui/styled-engine": { - "version": "7.3.10", - "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-7.3.10.tgz", - "integrity": "sha512-WxE9SiF8xskAQqGjsp0poXCkCqsoXFEsSr0HBXfApmGHR+DBnXRp+z46Vsltg4gpPM4Z96DeAQRpeAOnhNg7Ng==", + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-9.5.0.tgz", + "integrity": "sha512-IX1ceHnZ0bJgzJRWO/Ayl4ME1rNmpWVYwV28yb089UAMR17rJkV1qfr7ApsWQXXPi1oi+2JapJrWQAiAnTlJHQ==", "license": "MIT", "dependencies": { - "@babel/runtime": "^7.28.6", + "@babel/runtime": "^7.29.7 || ^8.0.5", "@emotion/cache": "^11.14.0", "@emotion/serialize": "^1.3.3", "@emotion/sheet": "^1.4.0", @@ -1370,17 +931,23 @@ } } }, + "node_modules/@mui/styled-engine/node_modules/@babel/runtime": { + "version": "8.0.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-8.0.7.tgz", + "integrity": "sha512-k0hagDDq/6w4v+0Sp4wFvIz/yswH5O7Va6aRTnD7N62Tnborp4X+YB+H3XUcWVSR4aE47WPEXiIIZchBs0GmYg==", + "license": "MIT" + }, "node_modules/@mui/system": { - "version": "7.3.10", - "resolved": "https://registry.npmjs.org/@mui/system/-/system-7.3.10.tgz", - "integrity": "sha512-/sfPpdpJaQn7BSF+avjIdHSYmxHp0UOBYNxSG9QGKfMOD6sLANCpRPCnanq1Pe0lFf0NHkO2iUk0TNzdWC1USQ==", + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/@mui/system/-/system-9.5.0.tgz", + "integrity": "sha512-OZuAwvoiaaHpvn0a540VH1DUwwmojJubGfNmO2g+nb2ybNDP4/gF7t7aA6AMduELSuJ5yxwlzyFvUO3CfLv1Yg==", "license": "MIT", "dependencies": { - "@babel/runtime": "^7.28.6", - "@mui/private-theming": "^7.3.10", - "@mui/styled-engine": "^7.3.10", - "@mui/types": "^7.4.12", - "@mui/utils": "^7.3.10", + "@babel/runtime": "^7.29.7 || ^8.0.5", + "@mui/private-theming": "^9.5.0", + "@mui/styled-engine": "^9.5.0", + "@mui/types": "^9.5.0", + "@mui/utils": "^9.5.0", "clsx": "^2.1.1", "csstype": "^3.2.3", "prop-types": "^15.8.1" @@ -1410,13 +977,19 @@ } } }, + "node_modules/@mui/system/node_modules/@babel/runtime": { + "version": "8.0.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-8.0.7.tgz", + "integrity": "sha512-k0hagDDq/6w4v+0Sp4wFvIz/yswH5O7Va6aRTnD7N62Tnborp4X+YB+H3XUcWVSR4aE47WPEXiIIZchBs0GmYg==", + "license": "MIT" + }, "node_modules/@mui/types": { - "version": "7.4.12", - "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.4.12.tgz", - "integrity": "sha512-iKNAF2u9PzSIj40CjvKJWxFXJo122jXVdrmdh0hMYd+FR+NuJMkr/L88XwWLCRiJ5P1j+uyac25+Kp6YC4hu6w==", + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/@mui/types/-/types-9.5.0.tgz", + "integrity": "sha512-O0S6xS80qEQqW2CYNrd4GlBEQ1xpMOHZ5b4zO/DaLu6NZmtthM6smEg0Gz/u9gX5vL+8NB3EiBIxmxSCcG590Q==", "license": "MIT", "dependencies": { - "@babel/runtime": "^7.28.6" + "@babel/runtime": "^7.29.7 || ^8.0.5" }, "peerDependencies": { "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0" @@ -1427,18 +1000,24 @@ } } }, + "node_modules/@mui/types/node_modules/@babel/runtime": { + "version": "8.0.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-8.0.7.tgz", + "integrity": "sha512-k0hagDDq/6w4v+0Sp4wFvIz/yswH5O7Va6aRTnD7N62Tnborp4X+YB+H3XUcWVSR4aE47WPEXiIIZchBs0GmYg==", + "license": "MIT" + }, "node_modules/@mui/utils": { - "version": "7.3.10", - "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-7.3.10.tgz", - "integrity": "sha512-7y2eIfy0h7JPz+Yy4pS+wgV68d46PuuxDqKBN4Q8VlPQSsCAGwroMCV6xWyc7g9dvEp8ZNFsknc59GHWO+r6Ow==", + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-9.5.0.tgz", + "integrity": "sha512-8m0LfSgE2pj58znY3kZ+anwRKbnBkcu/7AJPA8L/77wEjN0QnpX4/zRAccyplp1VEUnMsUBADeTRin5Mkf7epA==", "license": "MIT", "dependencies": { - "@babel/runtime": "^7.28.6", - "@mui/types": "^7.4.12", + "@babel/runtime": "^7.29.7 || ^8.0.5", + "@mui/types": "^9.5.0", "@types/prop-types": "^15.7.15", "clsx": "^2.1.1", "prop-types": "^15.8.1", - "react-is": "^19.2.3" + "react-is": "^19.3.0" }, "engines": { "node": ">=14.0.0" @@ -1457,16 +1036,22 @@ } } }, + "node_modules/@mui/utils/node_modules/@babel/runtime": { + "version": "8.0.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-8.0.7.tgz", + "integrity": "sha512-k0hagDDq/6w4v+0Sp4wFvIz/yswH5O7Va6aRTnD7N62Tnborp4X+YB+H3XUcWVSR4aE47WPEXiIIZchBs0GmYg==", + "license": "MIT" + }, "node_modules/@mui/x-data-grid": { - "version": "8.28.2", - "resolved": "https://registry.npmjs.org/@mui/x-data-grid/-/x-data-grid-8.28.2.tgz", - "integrity": "sha512-nqVhOtXOfPcKtoULuZuUp9Hdgzcn4xzqjYwqxZkTPnzKGZqkXPGaupy6n+9ssyYg7jg9ot+zNUJB0sbsvlDY/g==", + "version": "9.0.0-rc.0", + "resolved": "https://registry.npmjs.org/@mui/x-data-grid/-/x-data-grid-9.0.0-rc.0.tgz", + "integrity": "sha512-uRXlRO6UKknT3in/vavrgpbgpV6A9JOi3gn3FO2sFx43CavX13OZ6h4tg2N9nih2bQO04lck2evw7V4VgXPg3A==", "license": "MIT", "dependencies": { - "@babel/runtime": "^7.28.4", - "@mui/utils": "^7.3.5", - "@mui/x-internals": "8.26.0", - "@mui/x-virtualizer": "0.3.4", + "@babel/runtime": "^7.28.6", + "@mui/utils": "9.0.0-beta.1", + "@mui/x-internals": "9.0.0-rc.0", + "@mui/x-virtualizer": "1.0.0-rc.0", "clsx": "^2.1.1", "prop-types": "^15.8.1", "use-sync-external-store": "^1.6.0" @@ -1481,8 +1066,8 @@ "peerDependencies": { "@emotion/react": "^11.9.0", "@emotion/styled": "^11.8.1", - "@mui/material": "^5.15.14 || ^6.0.0 || ^7.0.0", - "@mui/system": "^5.15.14 || ^6.0.0 || ^7.0.0", + "@mui/material": "^7.3.0 || ^9.0.0-beta.0 || ^9.0.0", + "@mui/system": "^7.3.0 || ^9.0.0-beta.1", "react": "^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" }, @@ -1495,14 +1080,44 @@ } } }, + "node_modules/@mui/x-data-grid/node_modules/@mui/utils": { + "version": "9.0.0-beta.1", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-9.0.0-beta.1.tgz", + "integrity": "sha512-kKm1qSUq5x/HSsRQPwPuCbXZ+7ZLmdR9DHlKhwwGlwsO+3kNcz7QOwLiWUgxAxXbHi8xULGZzibe3UaqDCiWIA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.29.2", + "@mui/types": "^9.0.0-beta.0", + "@types/prop-types": "^15.7.15", + "clsx": "^2.1.1", + "prop-types": "^15.8.1", + "react-is": "^19.2.4" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@mui/x-internals": { - "version": "8.26.0", - "resolved": "https://registry.npmjs.org/@mui/x-internals/-/x-internals-8.26.0.tgz", - "integrity": "sha512-B9OZau5IQUvIxwpJZhoFJKqRpmWf5r0yMmSXjQuqb5WuqM755EuzWJOenY48denGoENzMLT8hQpA0hRTeU2IPA==", + "version": "9.0.0-rc.0", + "resolved": "https://registry.npmjs.org/@mui/x-internals/-/x-internals-9.0.0-rc.0.tgz", + "integrity": "sha512-phyLcs00D8bVDFgliUBrifEGhMPuIXb9+egbH8PhQf4+Pzh4yWyTsFMAOepOKub6cXm1aGvRcIgcGPzz7ppFew==", "license": "MIT", "dependencies": { - "@babel/runtime": "^7.28.4", - "@mui/utils": "^7.3.5", + "@babel/runtime": "^7.28.6", + "@mui/utils": "9.0.0-beta.1", "reselect": "^5.1.1", "use-sync-external-store": "^1.6.0" }, @@ -1517,15 +1132,45 @@ "react": "^17.0.0 || ^18.0.0 || ^19.0.0" } }, + "node_modules/@mui/x-internals/node_modules/@mui/utils": { + "version": "9.0.0-beta.1", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-9.0.0-beta.1.tgz", + "integrity": "sha512-kKm1qSUq5x/HSsRQPwPuCbXZ+7ZLmdR9DHlKhwwGlwsO+3kNcz7QOwLiWUgxAxXbHi8xULGZzibe3UaqDCiWIA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.29.2", + "@mui/types": "^9.0.0-beta.0", + "@types/prop-types": "^15.7.15", + "clsx": "^2.1.1", + "prop-types": "^15.8.1", + "react-is": "^19.2.4" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@mui/x-virtualizer": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/@mui/x-virtualizer/-/x-virtualizer-0.3.4.tgz", - "integrity": "sha512-b80Wt08bT+KVDunurdd4es76PdYaazoNGpGde3Ki7jGVaSpoljMYSRVlCtifei+4cOUD3NKJhG4e3av/czTekg==", + "version": "1.0.0-rc.0", + "resolved": "https://registry.npmjs.org/@mui/x-virtualizer/-/x-virtualizer-1.0.0-rc.0.tgz", + "integrity": "sha512-9UDS/Abk9qOox5BZOOCl0twf6wLbUjfcwCObnHLnt4LgU0SBxwpvP9ioK4bduYHdharpYmyjd7hKHCqW7AtTUw==", "license": "MIT", "dependencies": { - "@babel/runtime": "^7.28.4", - "@mui/utils": "^7.3.5", - "@mui/x-internals": "8.26.0" + "@babel/runtime": "^7.28.6", + "@mui/utils": "9.0.0-beta.1", + "@mui/x-internals": "9.0.0-rc.0" }, "engines": { "node": ">=14.0.0" @@ -1539,6 +1184,46 @@ "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" } }, + "node_modules/@mui/x-virtualizer/node_modules/@mui/utils": { + "version": "9.0.0-beta.1", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-9.0.0-beta.1.tgz", + "integrity": "sha512-kKm1qSUq5x/HSsRQPwPuCbXZ+7ZLmdR9DHlKhwwGlwsO+3kNcz7QOwLiWUgxAxXbHi8xULGZzibe3UaqDCiWIA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.29.2", + "@mui/types": "^9.0.0-beta.0", + "@types/prop-types": "^15.7.15", + "clsx": "^2.1.1", + "prop-types": "^15.8.1", + "react-is": "^19.2.4" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@oxc-project/types": { + "version": "0.154.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.154.0.tgz", + "integrity": "sha512-KYOUGQFlLWR+s4QMV2Q9ko38TZm7EdO87Qm7rvZLqk/webse/cDjtyxKhg/47l0byCbs/k46LT8qc4PKjAikzA==", + "devOptional": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/oxc-project" + } + }, "node_modules/@popperjs/core": { "version": "2.11.8", "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", @@ -1585,362 +1270,252 @@ "url": "https://opencollective.com/immer" } }, - "node_modules/@rolldown/pluginutils": { - "version": "1.0.0-rc.3", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.3.tgz", - "integrity": "sha512-eybk3TjzzzV97Dlj5c+XrBFW57eTNhzod66y9HrBlzJ6NsCrWCp/2kaPS3K9wJmurBC0Tdw4yPjXKZqlznim3Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.2.tgz", - "integrity": "sha512-dnlp69efPPg6Uaw2dVqzWRfAWRnYVb1XJ8CyyhIbZeaq4CA5/mLeZ1IEt9QqQxmbdvagjLIm2ZL8BxXv5lH4Yw==", + "node_modules/@rolldown/binding-android-arm-eabi": { + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.14.tgz", + "integrity": "sha512-aWR+XKH9+rW7m5fWq/5360J3mHycrx/o6xZM2DQFdJnTMdyP7/WzxPjhtLbVWdKo2y612/4U1bWqb3HQhiF7vg==", "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ "android" - ] + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.2.tgz", - "integrity": "sha512-OqZTwDRDchGRHHm/hwLOL7uVPB9aUvI0am/eQuWMNyFHf5PSEQmyEeYYheA0EPPKUO/l0uigCp+iaTjoLjVoHg==", + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.14.tgz", + "integrity": "sha512-QiVxHofl6A8lQU7WAtOUY4QMVZXohxwz7WJJoeh8NBEUyKfIkMRKhYyq8az/1RypNiIXzNTtiS6KwHz0erxYRQ==", "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ "android" - ] + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.2.tgz", - "integrity": "sha512-UwRE7CGpvSVEQS8gUMBe1uADWjNnVgP3Iusyda1nSRwNDCsRjnGc7w6El6WLQsXmZTbLZx9cecegumcitNfpmA==", + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.14.tgz", + "integrity": "sha512-lTQOzHuBIooh3jIr5XINQkjPGEuGy+Dsy0meXDr6Sjty0b99kYgGh4uBC/8mJX1T0kC8d6S0aPP1Y+bHkh6aBw==", "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ "darwin" - ] + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.2.tgz", - "integrity": "sha512-gjEtURKLCC5VXm1I+2i1u9OhxFsKAQJKTVB8WvDAHF+oZlq0GTVFOlTlO1q3AlCTE/DF32c16ESvfgqR7343/g==", + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.14.tgz", + "integrity": "sha512-hyMKEGatjmBYCs/Q4sbIcL/GqpFEqsX3wc+HYVQ/85A4rxc1o7rPm4DUI4GV4k/h0ITddwFSGpqQunI14GXjTA==", "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ "darwin" - ] - }, - "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.2.tgz", - "integrity": "sha512-Bcl6CYDeAgE70cqZaMojOi/eK63h5Me97ZqAQoh77VPjMysA/4ORQBRGo3rRy45x4MzVlU9uZxs8Uwy7ZaKnBw==", - "cpu": [ - "arm64" ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.2.tgz", - "integrity": "sha512-LU+TPda3mAE2QB0/Hp5VyeKJivpC6+tlOXd1VMoXV/YFMvk/MNk5iXeBfB4MQGRWyOYVJ01625vjkr0Az98OJQ==", + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.14.tgz", + "integrity": "sha512-u371GbXoVXrEgP20PwzoGNOdAOqAMhzo19JzzY+V4YmmnC+DTOoqdbMu+O+54emM59Mik/UdrtWgcuEDKWRPKA==", "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ "freebsd" - ] - }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.2.tgz", - "integrity": "sha512-2QxQrM+KQ7DAW4o22j+XZ6RKdxjLD7BOWTP0Bv0tmjdyhXSsr2Ul1oJDQqh9Zf5qOwTuTc7Ek83mOFaKnodPjg==", - "cpu": [ - "arm" ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.2.tgz", - "integrity": "sha512-TbziEu2DVsTEOPif2mKWkMeDMLoYjx95oESa9fkQQK7r/Orta0gnkcDpzwufEcAO2BLBsD7mZkXGFqEdMRRwfw==", + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.14.tgz", + "integrity": "sha512-BqwKsdZQf6EqVWr8fv2MrDYzAvOZ7+NYFSJESRGs87fkseEhs0TU9TXgRJTkZoRRkxTizyEg1LKUXfU3/78uqg==", "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.2.tgz", - "integrity": "sha512-bO/rVDiDUuM2YfuCUwZ1t1cP+/yqjqz+Xf2VtkdppefuOFS2OSeAfgafaHNkFn0t02hEyXngZkxtGqXcXwO8Rg==", - "cpu": [ - "arm64" ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.2.tgz", - "integrity": "sha512-hr26p7e93Rl0Za+JwW7EAnwAvKkehh12BU1Llm9Ykiibg4uIr2rbpxG9WCf56GuvidlTG9KiiQT/TXT1yAWxTA==", + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.14.tgz", + "integrity": "sha512-SD9kY8M009x/7s57Z1dgUVlP5+uYBgKQK0Tkr1WIWngrtUJmRCW1RJFMY7QDla2khXhmg2i5Io7MUjcmZNZ/KQ==", "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.2.tgz", - "integrity": "sha512-pOjB/uSIyDt+ow3k/RcLvUAOGpysT2phDn7TTUB3n75SlIgZzM6NKAqlErPhoFU+npgY3/n+2HYIQVbF70P9/A==", - "cpu": [ - "loong64" ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-linux-loong64-musl": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.2.tgz", - "integrity": "sha512-2/w+q8jszv9Ww1c+6uJT3OwqhdmGP2/4T17cu8WuwyUuuaCDDJ2ojdyYwZzCxx0GcsZBhzi3HmH+J5pZNXnd+Q==", + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.14.tgz", + "integrity": "sha512-BJ2CL19M6uuWesMgnqJzgxoI+N+eV7b0A9O9vLV42y+gitC41fNa8KElau1tHVzuLWX5GzEuxMPhAO0tQXQMkg==", "cpu": [ - "loong64" + "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ "linux" - ] - }, - "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.2.tgz", - "integrity": "sha512-11+aL5vKheYgczxtPVVRhdptAM2H7fcDR5Gw4/bTcteuZBlH4oP9f5s9zYO9aGZvoGeBpqXI/9TZZihZ609wKw==", - "cpu": [ - "ppc64" ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-linux-ppc64-musl": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.2.tgz", - "integrity": "sha512-i16fokAGK46IVZuV8LIIwMdtqhin9hfYkCh8pf8iC3QU3LpwL+1FSFGej+O7l3E/AoknL6Dclh2oTdnRMpTzFQ==", + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.14.tgz", + "integrity": "sha512-4+Kl8daPV8NDHaD7mfCcc1pRM+Qg7ab8DyMm5FG5WoZgv4h2i3KFN1CybYBNbQqmuYm7ftJLvmMI1eHYJ22S/Q==", "cpu": [ "ppc64" ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.2.tgz", - "integrity": "sha512-49FkKS6RGQoriDSK/6E2GkAsAuU5kETFCh7pG4yD/ylj9rKhTmO3elsnmBvRD4PgJPds5W2PkhC82aVwmUcJ7A==", - "cpu": [ - "riscv64" - ], - "dev": true, "license": "MIT", "optional": true, "os": [ "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.2.tgz", - "integrity": "sha512-mjYNkHPfGpUR00DuM1ZZIgs64Hpf4bWcz9Z41+4Q+pgDx73UwWdAYyf6EG/lRFldmdHHzgrYyge5akFUW0D3mQ==", - "cpu": [ - "riscv64" ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.2.tgz", - "integrity": "sha512-ALyvJz965BQk8E9Al/JDKKDLH2kfKFLTGMlgkAbbYtZuJt9LU8DW3ZoDMCtQpXAltZxwBHevXz5u+gf0yA0YoA==", + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.14.tgz", + "integrity": "sha512-V/h8ePq1fKDrmtFMz6xcCN3DKvuvMRrwwD1q0PkBeeo4DpK64biaKvqWGVxqCeYKqeRk7gE7/iKPxLCn08O97A==", "cpu": [ "s390x" ], - "dev": true, "license": "MIT", "optional": true, "os": [ "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.2.tgz", - "integrity": "sha512-UQjrkIdWrKI626Du8lCQ6MJp/6V1LAo2bOK9OTu4mSn8GGXIkPXk/Vsp4bLHCd9Z9Iz2OTEaokUE90VweJgIYQ==", - "cpu": [ - "x64" ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.2.tgz", - "integrity": "sha512-bTsRGj6VlSdn/XD4CGyzMnzaBs9bsRxy79eTqTCBsA8TMIEky7qg48aPkvJvFe1HyzQ5oMZdg7AnVlWQSKLTnw==", + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.14.tgz", + "integrity": "sha512-WxalXzgrLVpKC93xfpaJ3gCXUWzeCr+FRCbDVD2mUAy4L6Vo3lNVz0epUEUpxPWJkLNYUwM4OSIikv36OJ9mLg==", "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ "linux" - ] - }, - "node_modules/@rollup/rollup-openbsd-x64": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.2.tgz", - "integrity": "sha512-6d4Z3534xitaA1FcMWP7mQPq5zGwBmGbhphh2DwaA1aNIXUu3KTOfwrWpbwI4/Gr0uANo7NTtaykFyO2hPuFLg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ] - }, - "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.2.tgz", - "integrity": "sha512-NetAg5iO2uN7eB8zE5qrZ3CSil+7IJt4WDFLcC75Ymywq1VZVD6qJ6EvNLjZ3rEm6gB7XW5JdT60c6MN35Z85Q==", - "cpu": [ - "arm64" ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ] + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.2.tgz", - "integrity": "sha512-NCYhOotpgWZ5kdxCZsv6Iudx0wX8980Q/oW4pNFNihpBKsDbEA1zpkfxJGC0yugsUuyDZ7gL37dbzwhR0VI7pQ==", + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.14.tgz", + "integrity": "sha512-3G3tnAmTpdk9jO3GQtyHXqeFD2SlecfTws/Y3M2mmj9nMBwwuLT4kur8sZKeu7ENUHSpVO3cIsm5A52LEWWwlg==", "cpu": [ - "arm64" + "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ - "win32" - ] + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.2.tgz", - "integrity": "sha512-RXsaOqXxfoUBQoOgvmmijVxJnW2IGB0eoMO7F8FAjaj0UTywUO/luSqimWBJn04WNgUkeNhh7fs7pESXajWmkg==", + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.14.tgz", + "integrity": "sha512-qyr5XiNGxm2ytx17vvtcJ9nXn6v1gENETi503bJx3OGwZ1tSonj7BosxbMa/xeAfSl5iS9X6KjEWYXLu6v1L7Q==", "cpu": [ - "ia32" + "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ - "win32" - ] + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.2.tgz", - "integrity": "sha512-qdAzEULD+/hzObedtmV6iBpdL5TIbKVztGiK7O3/KYSf+HIzU257+MX1EXJcyIiDbMAqmbwaufcYPvyRryeZtA==", + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.14.tgz", + "integrity": "sha512-oA4o5o0S2yB199YyYXDzKUv1Z56Rc+mgOjwm7Z/vJ9Z8igiDPYvoOuDyo6g7AK/ZaF09nPhDKVnGBcYHO11GXA==", "cpu": [ - "x64" + "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ "win32" - ] + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.2.tgz", - "integrity": "sha512-Nd/SgG27WoA9e+/TdK74KnHz852TLa94ovOYySo/yMPuTmpckK/jIF2jSwS3g7ELSKXK13/cVdmg1Z/DaCWKxA==", + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.14.tgz", + "integrity": "sha512-9mE94bf2GZUaR/fUSXCMpBoWkctu421Mi0ORpm4zqfbkjKep7RxzPFa5etcGs5h+2KApoWBOA/hAJa/7saGgEQ==", "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ "win32" - ] + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", + "devOptional": true, + "license": "MIT" }, "node_modules/@standard-schema/spec": { "version": "1.1.0", @@ -1954,51 +1529,6 @@ "integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==", "license": "MIT" }, - "node_modules/@types/babel__core": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", - "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.20.7", - "@babel/types": "^7.20.7", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" - } - }, - "node_modules/@types/babel__generator": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", - "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__template": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", - "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__traverse": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", - "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.28.2" - } - }, "node_modules/@types/d3-array": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz", @@ -2080,7 +1610,7 @@ "version": "24.12.2", "resolved": "https://registry.npmjs.org/@types/node/-/node-24.12.2.tgz", "integrity": "sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "undici-types": "~7.16.0" @@ -2099,9 +1629,9 @@ "license": "MIT" }, "node_modules/@types/react": { - "version": "19.2.14", - "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", - "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", + "version": "19.3.0", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.3.0.tgz", + "integrity": "sha512-N0rFCuH9YoxG9/m61l9MfpJKfmLOVU0em7ipIz6TRgSSkvReLB9vL85GB+yr8Bs5leqpvg96JSwF4ZS1s4viQg==", "license": "MIT", "dependencies": { "csstype": "^3.2.2" @@ -2331,9 +1861,9 @@ } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { - "version": "5.0.9", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", - "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.12.tgz", + "integrity": "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2428,24 +1958,33 @@ } }, "node_modules/@vitejs/plugin-react": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.2.0.tgz", - "integrity": "sha512-YmKkfhOAi3wsB1PhJq5Scj3GXMn3WvtQ/JC0xoopuHoXSdmtdStOpFrYaT1kie2YgFBcIe64ROzMYRjCrYOdYw==", + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.1.2.tgz", + "integrity": "sha512-fhJNGUdscQa4HkI0xFxwonWyhnVReCPDbXI7FH7jmzpnVCrFQI7PRrhWecO263610vOrs9tr2Y4xcf8uAdFxbw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/core": "^7.29.0", - "@babel/plugin-transform-react-jsx-self": "^7.27.1", - "@babel/plugin-transform-react-jsx-source": "^7.27.1", - "@rolldown/pluginutils": "1.0.0-rc.3", - "@types/babel__core": "^7.20.5", - "react-refresh": "^0.18.0" + "@rolldown/pluginutils": "^1.0.1" }, "engines": { "node": "^20.19.0 || >=22.12.0" }, "peerDependencies": { - "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" + "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0", + "babel-plugin-react-compiler": "^1.0.0", + "oxc-transform-react": "^0.152.0", + "vite": "^8.0.0" + }, + "peerDependenciesMeta": { + "@rolldown/plugin-babel": { + "optional": true + }, + "babel-plugin-react-compiler": { + "optional": true + }, + "oxc-transform-react": { + "optional": true + } } }, "node_modules/acorn": { @@ -2547,9 +2086,9 @@ } }, "node_modules/brace-expansion": { - "version": "1.1.18", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz", - "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==", + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.21.tgz", + "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==", "dev": true, "license": "MIT", "dependencies": { @@ -2896,6 +2435,16 @@ "dev": true, "license": "MIT" }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "devOptional": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, "node_modules/dom-helpers": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", @@ -2941,48 +2490,6 @@ "benchmarks" ] }, - "node_modules/esbuild": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.2.tgz", - "integrity": "sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.28.2", - "@esbuild/android-arm": "0.28.2", - "@esbuild/android-arm64": "0.28.2", - "@esbuild/android-x64": "0.28.2", - "@esbuild/darwin-arm64": "0.28.2", - "@esbuild/darwin-x64": "0.28.2", - "@esbuild/freebsd-arm64": "0.28.2", - "@esbuild/freebsd-x64": "0.28.2", - "@esbuild/linux-arm": "0.28.2", - "@esbuild/linux-arm64": "0.28.2", - "@esbuild/linux-ia32": "0.28.2", - "@esbuild/linux-loong64": "0.28.2", - "@esbuild/linux-mips64el": "0.28.2", - "@esbuild/linux-ppc64": "0.28.2", - "@esbuild/linux-riscv64": "0.28.2", - "@esbuild/linux-s390x": "0.28.2", - "@esbuild/linux-x64": "0.28.2", - "@esbuild/netbsd-arm64": "0.28.2", - "@esbuild/netbsd-x64": "0.28.2", - "@esbuild/openbsd-arm64": "0.28.2", - "@esbuild/openbsd-x64": "0.28.2", - "@esbuild/openharmony-arm64": "0.28.2", - "@esbuild/sunos-x64": "0.28.2", - "@esbuild/win32-arm64": "0.28.2", - "@esbuild/win32-ia32": "0.28.2", - "@esbuild/win32-x64": "0.28.2" - } - }, "node_modules/escalade": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", @@ -3220,7 +2727,7 @@ "version": "6.5.0", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=12.0.0" @@ -3295,7 +2802,6 @@ "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, "hasInstallScript": true, "license": "MIT", "optional": true, @@ -3616,6 +3122,256 @@ "node": ">= 0.8.0" } }, + "node_modules/lightningcss": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz", + "integrity": "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==", + "devOptional": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.33.0", + "lightningcss-darwin-arm64": "1.33.0", + "lightningcss-darwin-x64": "1.33.0", + "lightningcss-freebsd-x64": "1.33.0", + "lightningcss-linux-arm-gnueabihf": "1.33.0", + "lightningcss-linux-arm64-gnu": "1.33.0", + "lightningcss-linux-arm64-musl": "1.33.0", + "lightningcss-linux-x64-gnu": "1.33.0", + "lightningcss-linux-x64-musl": "1.33.0", + "lightningcss-win32-arm64-msvc": "1.33.0", + "lightningcss-win32-x64-msvc": "1.33.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.33.0.tgz", + "integrity": "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.33.0.tgz", + "integrity": "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.33.0.tgz", + "integrity": "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.33.0.tgz", + "integrity": "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.33.0.tgz", + "integrity": "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==", + "cpu": [ + "arm" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.33.0.tgz", + "integrity": "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.33.0.tgz", + "integrity": "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.33.0.tgz", + "integrity": "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.33.0.tgz", + "integrity": "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.33.0.tgz", + "integrity": "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.33.0.tgz", + "integrity": "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", @@ -3687,10 +3443,10 @@ "license": "MIT" }, "node_modules/nanoid": { - "version": "3.3.19", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.19.tgz", - "integrity": "sha512-Y2tUNy4ouw6tq5oDSKeQYGOyhkUBhNOcGV/02KC+6kd9eDGqdZd++mjMiIDilrBYvjEnCYvVtsuHCuP+okSfug==", - "dev": true, + "version": "3.3.20", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.20.tgz", + "integrity": "sha512-uKdg2G3GNCKQn9byYOpxbGqrT2fGO5KRt5J/8b3pok8rT6qxGWF6hxMyJiEYtAf+FVyYuD9hRaDqX5uPFYJ4ZQ==", + "devOptional": true, "funding": [ { "type": "github", @@ -3853,10 +3609,10 @@ "license": "ISC" }, "node_modules/picomatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", - "dev": true, + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", + "devOptional": true, "license": "MIT", "engines": { "node": ">=12" @@ -3866,10 +3622,10 @@ } }, "node_modules/postcss": { - "version": "8.5.28", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.28.tgz", - "integrity": "sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A==", - "dev": true, + "version": "8.5.30", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.30.tgz", + "integrity": "sha512-zkd+e9S1AQHhmaBKye9zlFHWjOwVgqXl1q3ECYy8561K7qxR2cREf/STLskulBFd78k6AOFeA+ff1TtEzk9bAA==", + "devOptional": true, "funding": [ { "type": "opencollective", @@ -3886,9 +3642,9 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.18", + "nanoid": "^3.3.20", "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" + "source-map-js": "^1.2.2" }, "engines": { "node": "^10 || ^12 || >=14" @@ -3932,30 +3688,30 @@ } }, "node_modules/react": { - "version": "19.2.5", - "resolved": "https://registry.npmjs.org/react/-/react-19.2.5.tgz", - "integrity": "sha512-llUJLzz1zTUBrskt2pwZgLq59AemifIftw4aB7JxOqf1HY2FDaGDxgwpAPVzHU1kdWabH7FauP4i1oEeer2WCA==", + "version": "19.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-19.3.1.tgz", + "integrity": "sha512-QNhWdt7nrgVWWQG58QuGsAR3jv3Hbe4mb4vz7w2csnHavciZJgda1CPNaldF6eVxv90ud3jUXpKlHL69gO38yw==", "license": "MIT", "engines": { "node": ">=0.10.0" } }, "node_modules/react-dom": { - "version": "19.2.5", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.5.tgz", - "integrity": "sha512-J5bAZz+DXMMwW/wV3xzKke59Af6CHY7G4uYLN1OvBcKEsWOs4pQExj86BBKamxl/Ik5bx9whOrvBlSDfWzgSag==", + "version": "19.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.3.1.tgz", + "integrity": "sha512-Y0bARz53lhEC1NMCASs0/j+faB/zqt3R8hpvg1e8+61ZVJ4UL0UdC4cBbXfZMcIuhrfgeILTLe+ZeBCavQPtKQ==", "license": "MIT", "dependencies": { - "scheduler": "^0.27.0" + "scheduler": "^0.28.0" }, "peerDependencies": { - "react": "^19.2.5" + "react": "^19.3.1" } }, "node_modules/react-is": { - "version": "19.2.5", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.5.tgz", - "integrity": "sha512-Dn0t8IQhCmeIT3wu+Apm1/YVsJXsGWi6k4sPdnBIdqMVtHtv0IGi6dcpNpNkNac0zB2uUAqNX3MHzN8c+z2rwQ==", + "version": "19.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.3.1.tgz", + "integrity": "sha512-RRieF6IyxQBkcK6pP8xOHprLTPW6QjRkbGdImtqNme+/hYVwCZkLQLdlZGOhUqXwkS9IEynaGvPJtv6XB/cz0Q==", "license": "MIT" }, "node_modules/react-redux": { @@ -3981,20 +3737,10 @@ } } }, - "node_modules/react-refresh": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.18.0.tgz", - "integrity": "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/react-router": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.18.3.tgz", - "integrity": "sha512-gyXgtdr5uACJ5b1Q4udzjVV+tb/rlHIMJKuJ0e89R4Kzgz47z/rgP0dIKxktqIEUhDHluGTPJJH/wRha7CyqsA==", + "version": "7.18.4", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.18.4.tgz", + "integrity": "sha512-PUPQcMhMGRAslLcvtlPz/kmzBEWPhLdgLFrL7pLNepBL6dX0lWj4WD2cUYVgYCuT3jxvghYFg81cDTj44DhetQ==", "license": "MIT", "dependencies": { "cookie": "^1.0.1", @@ -4014,12 +3760,12 @@ } }, "node_modules/react-router-dom": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.18.3.tgz", - "integrity": "sha512-ytVbyBBM7vMfRCam25r0WMhSVSom909A8p+8m0/f1w853dz/xfFu6etAT2SEbVoSnI+ZoPRDqIsQXVT89gp7kg==", + "version": "7.18.4", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.18.4.tgz", + "integrity": "sha512-yrfmJHIpDG7taCpqKjT1G5B6q3O2K+RN8/fgNf0lTjCwiPbQ0ei6vXX9ZjQR+7ld8Tr7Z5xmyMnZ8YJrphWQUw==", "license": "MIT", "dependencies": { - "react-router": "7.18.3" + "react-router": "7.18.4" }, "engines": { "node": ">=20.0.0" @@ -4126,55 +3872,44 @@ "node": ">=4" } }, - "node_modules/rollup": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.2.tgz", - "integrity": "sha512-J9qZyW++QK/09NyN/zeO0dG/1GdGfyp9lV8ajHnRVLfo/uFsbji5mHnDgn/qYdUHyCkM2N+8VyspgZclfAh0eQ==", - "dev": true, + "node_modules/rolldown": { + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.14.tgz", + "integrity": "sha512-eTBTTqMR6uHKLgcUqpJP8ntzDI6V4LYA8negAMBEI7gkgVvOdrx6VsvS2Ym+83KhOPdLCHWJu0w7UQFJZhHKNw==", + "devOptional": true, "license": "MIT", "dependencies": { - "@types/estree": "1.0.8" + "@oxc-project/types": "=0.154.0", + "@rolldown/pluginutils": "^1.0.0" }, "bin": { - "rollup": "dist/bin/rollup" + "rolldown": "bin/cli.mjs" }, "engines": { - "node": ">=18.0.0", - "npm": ">=8.0.0" + "node": "^20.19.0 || >=22.12.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.60.2", - "@rollup/rollup-android-arm64": "4.60.2", - "@rollup/rollup-darwin-arm64": "4.60.2", - "@rollup/rollup-darwin-x64": "4.60.2", - "@rollup/rollup-freebsd-arm64": "4.60.2", - "@rollup/rollup-freebsd-x64": "4.60.2", - "@rollup/rollup-linux-arm-gnueabihf": "4.60.2", - "@rollup/rollup-linux-arm-musleabihf": "4.60.2", - "@rollup/rollup-linux-arm64-gnu": "4.60.2", - "@rollup/rollup-linux-arm64-musl": "4.60.2", - "@rollup/rollup-linux-loong64-gnu": "4.60.2", - "@rollup/rollup-linux-loong64-musl": "4.60.2", - "@rollup/rollup-linux-ppc64-gnu": "4.60.2", - "@rollup/rollup-linux-ppc64-musl": "4.60.2", - "@rollup/rollup-linux-riscv64-gnu": "4.60.2", - "@rollup/rollup-linux-riscv64-musl": "4.60.2", - "@rollup/rollup-linux-s390x-gnu": "4.60.2", - "@rollup/rollup-linux-x64-gnu": "4.60.2", - "@rollup/rollup-linux-x64-musl": "4.60.2", - "@rollup/rollup-openbsd-x64": "4.60.2", - "@rollup/rollup-openharmony-arm64": "4.60.2", - "@rollup/rollup-win32-arm64-msvc": "4.60.2", - "@rollup/rollup-win32-ia32-msvc": "4.60.2", - "@rollup/rollup-win32-x64-gnu": "4.60.2", - "@rollup/rollup-win32-x64-msvc": "4.60.2", - "fsevents": "~2.3.2" + "@rolldown/binding-android-arm-eabi": "1.2.14", + "@rolldown/binding-android-arm64": "1.2.14", + "@rolldown/binding-darwin-arm64": "1.2.14", + "@rolldown/binding-darwin-x64": "1.2.14", + "@rolldown/binding-freebsd-x64": "1.2.14", + "@rolldown/binding-linux-arm-gnueabihf": "1.2.14", + "@rolldown/binding-linux-arm64-gnu": "1.2.14", + "@rolldown/binding-linux-arm64-musl": "1.2.14", + "@rolldown/binding-linux-ppc64-gnu": "1.2.14", + "@rolldown/binding-linux-s390x-gnu": "1.2.14", + "@rolldown/binding-linux-x64-gnu": "1.2.14", + "@rolldown/binding-linux-x64-musl": "1.2.14", + "@rolldown/binding-openharmony-arm64": "1.2.14", + "@rolldown/binding-win32-arm64-msvc": "1.2.14", + "@rolldown/binding-win32-x64-msvc": "1.2.14" } }, "node_modules/scheduler": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", - "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.28.0.tgz", + "integrity": "sha512-juorfCmIkIw8tT+p5BXSm6PJjQF/ycEYmKyzURCIt/RaZIhL+PulbQ9Yu2z1HdOJDdqDTlxA1+xKBmHXJsczAw==", "license": "MIT" }, "node_modules/semver": { @@ -4226,10 +3961,10 @@ } }, "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "dev": true, + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.2.tgz", + "integrity": "sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==", + "devOptional": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" @@ -4286,10 +4021,10 @@ "license": "MIT" }, "node_modules/tinyglobby": { - "version": "0.2.16", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", - "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", - "dev": true, + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "devOptional": true, "license": "MIT", "dependencies": { "fdir": "^6.5.0", @@ -4329,9 +4064,9 @@ } }, "node_modules/typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", + "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", "dev": true, "license": "Apache-2.0", "bin": { @@ -4370,7 +4105,7 @@ "version": "7.16.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/update-browserslist-db": { @@ -4446,18 +4181,17 @@ } }, "node_modules/vite": { - "version": "7.3.6", - "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.6.tgz", - "integrity": "sha512-4XP60spRGjSZFf1qYH+dJIkK2znL3zQfl9KkOV9MkkRR/3Dls0dxaBsQPTloEc5BLXWPL9vsOxopxyKoMmDueg==", - "dev": true, + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.3.4.tgz", + "integrity": "sha512-IftLE8phJhdNRIGNEV4JCCvfNScoZQVfcXaN8TQropWGJtOHcu+iAB9pHcuGvmEb/kddw0LBe/jS8ztdC6xdiQ==", + "devOptional": true, "license": "MIT", "dependencies": { - "esbuild": "^0.27.0 || ^0.28.0", - "fdir": "^6.5.0", - "picomatch": "^4.0.3", - "postcss": "^8.5.6", - "rollup": "^4.43.0", - "tinyglobby": "^0.2.15" + "lightningcss": "^1.33.0", + "picomatch": "^4.0.7", + "postcss": "^8.5.29", + "rolldown": "~1.2.12", + "tinyglobby": "^0.2.17" }, "bin": { "vite": "bin/vite.js" @@ -4473,9 +4207,10 @@ }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.7.1", + "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", - "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", @@ -4488,13 +4223,16 @@ "@types/node": { "optional": true }, - "jiti": { + "@vitejs/devtools": { "optional": true }, - "less": { + "esbuild": { "optional": true }, - "lightningcss": { + "jiti": { + "optional": true + }, + "less": { "optional": true }, "sass": { diff --git a/src/RunWay.Dashboard/SPA/package.json b/src/RunWay.Dashboard/SPA/package.json index 149e66a..06b0640 100644 --- a/src/RunWay.Dashboard/SPA/package.json +++ b/src/RunWay.Dashboard/SPA/package.json @@ -12,28 +12,29 @@ "dependencies": { "@emotion/react": "^11.14.0", "@emotion/styled": "^11.14.1", - "@mui/icons-material": "^7.3.6", - "@mui/lab": "^7.0.1-beta.23", - "@mui/material": "^7.3.6", - "@mui/x-data-grid": "^8.22.1", + "@kododo-dev/way-shell": "file:../../../../WayShell/src/way-shell", + "@mui/icons-material": "^9.5.0", + "@mui/lab": "^9.0.0-beta.10", + "@mui/material": "^9.5.0", + "@mui/x-data-grid": "^9.0.0-rc.0", "dayjs": "^1.11.19", - "react": "^19.2.4", - "react-dom": "^19.2.4", - "react-router-dom": "^7.18.3", + "react": "^19.3.1", + "react-dom": "^19.3.1", + "react-router-dom": "^7.18.4", "recharts": "^3.8.0" }, "devDependencies": { "@eslint/js": "^9.39.1", "@types/node": "^24.10.1", - "@types/react": "^19.2.5", + "@types/react": "^19.3.0", "@types/react-dom": "^19.2.3", - "@vitejs/plugin-react": "^5.1.1", + "@vitejs/plugin-react": "^6.1.2", "eslint": "^9.39.1", "eslint-plugin-react-hooks": "^7.0.1", "eslint-plugin-react-refresh": "^0.4.24", "globals": "^16.5.0", - "typescript": "~5.9.3", + "typescript": "~6.0.2", "typescript-eslint": "^8.46.4", - "vite": "^7.3.6" + "vite": "^8.3.4" } } diff --git a/src/RunWay.Dashboard/SPA/src/App.tsx b/src/RunWay.Dashboard/SPA/src/App.tsx index 243a1b2..f2e21df 100644 --- a/src/RunWay.Dashboard/SPA/src/App.tsx +++ b/src/RunWay.Dashboard/SPA/src/App.tsx @@ -1,10 +1,9 @@ -import { HashRouter, Routes, Route, Navigate } from 'react-router-dom'; -import { ThemeProvider, CssBaseline } from '@mui/material'; -import { ThemeContextProvider, useThemeMode } from './context/ThemeContext'; +import { useEffect } from 'react'; +import { Routes, Route } from 'react-router-dom'; import { JobMetricsProvider } from './context/JobMetricsContext'; import { RunnersProvider } from './context/RunnersContext'; import { RecurrencesProvider } from './context/RecurrencesContext'; -import MainLayout from './components/layout/MainLayout'; +import SideMenu from './components/layout/SideMenu'; import DashboardPage from './pages/DashboardPage'; import JobsPage from './pages/JobsPage'; import JobPage from './pages/JobPage'; @@ -12,42 +11,36 @@ import RunnersPage from './pages/RunnersPage'; import RunnerPage from './pages/RunnerPage'; import RecurrencesPage from './pages/RecurrencesPage'; import RecurrencePage from './pages/RecurrencePage'; +import { useWayNavigate } from './utils/useWayNavigate'; -function AppInner() { - const { muiTheme } = useThemeMode(); - return ( - - - - - - - - - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - - - - - - - - ); +/** Replaces the current address with the dashboard's root, e.g. for an unknown page. */ +function ToDashboard() { + const navigate = useWayNavigate(); + useEffect(() => navigate('/', { replace: true }), [navigate]); + return null; } +// The dashboard inside Way.Shell: the shell provides the router, theme and layout; the dashboard's menu goes into the +// shell's side menu. function App() { return ( - - - + + + + + + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + + + + ); } diff --git a/src/RunWay.Dashboard/SPA/src/api/api.ts b/src/RunWay.Dashboard/SPA/src/api/api.ts index 3c8018a..95efcfa 100644 --- a/src/RunWay.Dashboard/SPA/src/api/api.ts +++ b/src/RunWay.Dashboard/SPA/src/api/api.ts @@ -1,77 +1,36 @@ +import { useMemo } from 'react'; +import { useShell, type WayApi } from '@kododo-dev/way-shell'; import type { JobListItem, JobMetrics, JobCategory, + JobDetails, PagedResult, RunnerDetails, Runner, Recurrence } from "./api.model"; -export async function fetchJobMetrics(): Promise { - return await fetch('api/GetStatusesCounts', { method: 'POST' }) - .then((res) => res.json()) - .then((data) => data as JobMetrics); -} - -export async function fetchJobs(category: JobCategory, page: number = 1): Promise> { - return await fetch('api/GetJobs', { - method: 'POST', - body: JSON.stringify({ category, page }), - headers: { 'Content-Type': 'application/json' }}) - .then((res) => res.json()); -} - -export async function fetchJobDetails(id: string) { - return await fetch('api/GetJobDetails', { - method: 'POST', - body: JSON.stringify({ id }), - headers: { 'Content-Type': 'application/json' }}) - .then((res) => res.json()); -} - -export async function fetchRunners(): Promise { - return await fetch('api/GetRunners', { method: 'POST' }) - .then((res) => res.json()); -} - -export async function fetchRunner(id: string): Promise { - return await fetch('api/GetRunnerDetails', { - method: 'POST', - body: JSON.stringify({ id }), - headers: { 'Content-Type': 'application/json' }, - }).then((res) => res.json()); -} - -export async function fetchRecurrences(page: number = 1): Promise> { - return await fetch('api/GetRecurrences', { - method: 'POST', - body: JSON.stringify({ page }), - headers: { 'Content-Type': 'application/json' }, - }).then((res) => res.json()); -} - -export async function fetchRecurrence(id: string): Promise { - return await fetch('api/GetRecurrence', { - method: 'POST', - body: JSON.stringify({ id }), - headers: { 'Content-Type': 'application/json' }, - }).then((res) => res.json()); -} - -export async function fetchRecurrenceJobs(id: string, page: number = 1): Promise> { - return await fetch('api/GetRecurrenceJobs', { - method: 'POST', - body: JSON.stringify({ id, page }), - headers: { 'Content-Type': 'application/json' }, - }).then((res) => res.json()); -} - -export async function requeueJob(id: string): Promise { - const res = await fetch('api/RequeueJob', { - method: 'POST', - body: JSON.stringify({ id }), - headers: { 'Content-Type': 'application/json' }, - }); - if (!res.ok) throw new Error(`Requeue failed: ${res.status}`); +/** The dashboard's API, on the shell's client: its apiBase, problem details and expired-session handling. */ +export function createRunWayApi(api: WayApi) { + return { + fetchJobMetrics: (): Promise => api.post('GetStatusesCounts'), + fetchJobs: (category: JobCategory, page: number = 1): Promise> => + api.post('GetJobs', { category, page }), + fetchJobDetails: (id: string): Promise => api.post('GetJobDetails', { id }), + fetchRunners: (): Promise => api.post('GetRunners'), + fetchRunner: (id: string): Promise => api.post('GetRunnerDetails', { id }), + fetchRecurrences: (page: number = 1): Promise> => api.post('GetRecurrences', { page }), + fetchRecurrence: (id: string): Promise => api.post('GetRecurrence', { id }), + fetchRecurrenceJobs: (id: string, page: number = 1): Promise> => + api.post('GetRecurrenceJobs', { id, page }), + requeueJob: (id: string): Promise => api.post('RequeueJob', { id }), + }; +} + +export type RunWayApi = ReturnType; + +export function useApi(): RunWayApi { + const { api } = useShell(); + return useMemo(() => createRunWayApi(api), [api]); } diff --git a/src/RunWay.Dashboard/SPA/src/components/dashboard/DashboardTiles.tsx b/src/RunWay.Dashboard/SPA/src/components/dashboard/DashboardTiles.tsx index fed2fe4..23fcc9f 100644 --- a/src/RunWay.Dashboard/SPA/src/components/dashboard/DashboardTiles.tsx +++ b/src/RunWay.Dashboard/SPA/src/components/dashboard/DashboardTiles.tsx @@ -1,7 +1,7 @@ import Box from '@mui/material/Box'; import Typography from '@mui/material/Typography'; import { useTheme } from '@mui/material/styles'; -import { useNavigate } from 'react-router-dom'; +import { useWayNavigate } from '../../utils/useWayNavigate'; import { CATEGORY_COLORS } from '../../utils/jobUtils'; interface DashboardTilesProps { @@ -27,7 +27,7 @@ const MONO = { fontFamily: "'IBM Plex Mono', monospace" }; const DashboardTiles = ({ jobCounts }: DashboardTilesProps) => { const theme = useTheme(); const isDark = theme.palette.mode === 'dark'; - const navigate = useNavigate(); + const navigate = useWayNavigate(); const cardBg = isDark ? '#222' : '#fff'; const borderBase = isDark ? '#2e2e2e' : '#e0e0e0'; diff --git a/src/RunWay.Dashboard/SPA/src/components/jobs/JobsTable.tsx b/src/RunWay.Dashboard/SPA/src/components/jobs/JobsTable.tsx index b67c162..942002c 100644 --- a/src/RunWay.Dashboard/SPA/src/components/jobs/JobsTable.tsx +++ b/src/RunWay.Dashboard/SPA/src/components/jobs/JobsTable.tsx @@ -4,7 +4,7 @@ import type { GridColDef } from '@mui/x-data-grid'; import dayjs from 'dayjs'; import relativeTime from 'dayjs/plugin/relativeTime'; import { type JobListItem, type JobCategory } from '../../api/api.model'; -import { useNavigate } from 'react-router-dom'; +import { useWayNavigate } from '../../utils/useWayNavigate'; import { useTheme } from '@mui/material/styles'; import JobStatusBadge from './JobStatusBadge'; @@ -87,7 +87,7 @@ function getColumns(category: JobCategory, hideType: boolean): GridColDef[] { const JobsTable = ({ category, rows, rowCount, loading, paginationModel, setPaginationModel, hideType = false }: JobsTableProps) => { const theme = useTheme(); const isDark = theme.palette.mode === 'dark'; - const navigate = useNavigate(); + const navigate = useWayNavigate(); const [columns, setColumns] = useState([]); useEffect(() => { diff --git a/src/RunWay.Dashboard/SPA/src/components/layout/MainLayout.tsx b/src/RunWay.Dashboard/SPA/src/components/layout/MainLayout.tsx deleted file mode 100644 index 674e479..0000000 --- a/src/RunWay.Dashboard/SPA/src/components/layout/MainLayout.tsx +++ /dev/null @@ -1,136 +0,0 @@ -import React, { useState, useCallback } from 'react'; -import Drawer from '@mui/material/Drawer'; -import Box from '@mui/material/Box'; -import Typography from '@mui/material/Typography'; -import IconButton from '@mui/material/IconButton'; -import Tooltip from '@mui/material/Tooltip'; -import useMediaQuery from '@mui/material/useMediaQuery'; -import DarkModeIcon from '@mui/icons-material/DarkMode'; -import LightModeIcon from '@mui/icons-material/LightMode'; -import SideMenu from './SideMenu'; -import { useThemeMode } from '../../context/ThemeContext'; -import { useTheme } from '@mui/material/styles'; -import { LayoutContext } from '../../context/LayoutContext'; - -const DRAWER_WIDTH = 248; - -const MainLayout = ({ children }: { children: React.ReactNode }) => { - const { mode, toggleMode } = useThemeMode(); - const theme = useTheme(); - const isDark = mode === 'dark'; - const isMobile = useMediaQuery(theme.breakpoints.down('md')); - - const [mobileOpen, setMobileOpen] = useState(false); - const openMobileMenu = useCallback(() => setMobileOpen(true), []); - const closeMobileMenu = useCallback(() => setMobileOpen(false), []); - - const drawerBg = isDark ? '#222' : '#fafafa'; - const borderColor = isDark ? '#2e2e2e' : '#e0e0e0'; - const bg = isDark ? '#1a1a1a' : '#f5f5f5'; - - const drawerContent = ( - <> - - - - Scheduler - - - - - {isDark - ? - : - } - - - - - - ); - - return ( - - - {isMobile ? ( - - {drawerContent} - - ) : ( - - {drawerContent} - - )} - - - {children} - - - - ); -}; - -export default MainLayout; diff --git a/src/RunWay.Dashboard/SPA/src/components/layout/PageHeader.tsx b/src/RunWay.Dashboard/SPA/src/components/layout/PageHeader.tsx index 7ab52d3..33e2bbc 100644 --- a/src/RunWay.Dashboard/SPA/src/components/layout/PageHeader.tsx +++ b/src/RunWay.Dashboard/SPA/src/components/layout/PageHeader.tsx @@ -1,14 +1,11 @@ import React from 'react'; import Box from '@mui/material/Box'; -import Typography from '@mui/material/Typography'; import IconButton from '@mui/material/IconButton'; import Tooltip from '@mui/material/Tooltip'; import RefreshIcon from '@mui/icons-material/Refresh'; -import MenuIcon from '@mui/icons-material/Menu'; -import useMediaQuery from '@mui/material/useMediaQuery'; import type { SxProps } from '@mui/material'; import { useTheme } from '@mui/material/styles'; -import { useLayout } from '../../context/LayoutContext'; +import { PageHeader as ShellPageHeader } from '@kododo-dev/way-shell'; interface PageHeaderProps { title: string; @@ -21,50 +18,12 @@ const MONO = { fontFamily: "'IBM Plex Mono', monospace" }; const PageHeader = ({ title, onRefresh, actions, sx }: PageHeaderProps) => { const theme = useTheme(); - const { openMobileMenu } = useLayout(); - const isMobile = useMediaQuery(theme.breakpoints.down('md')); - const isDark = theme.palette.mode === 'dark'; - - const borderColor = isDark ? '#2e2e2e' : '#e0e0e0'; - const headerBg = isDark ? '#1e1e1e' : '#fff'; + // The shell's header bar: the menu button on narrow screens; the dashboard's title and actions in it. return ( - - {isMobile && ( - - - - )} - - - - {title} - - - + {title}}> {actions} - {onRefresh && ( { )} - + ); }; diff --git a/src/RunWay.Dashboard/SPA/src/components/layout/SideMenu.tsx b/src/RunWay.Dashboard/SPA/src/components/layout/SideMenu.tsx index c3c8c11..c28d06a 100644 --- a/src/RunWay.Dashboard/SPA/src/components/layout/SideMenu.tsx +++ b/src/RunWay.Dashboard/SPA/src/components/layout/SideMenu.tsx @@ -1,4 +1,6 @@ -import { useLocation, useNavigate } from 'react-router-dom'; +import { useParams } from 'react-router-dom'; +import { WayMenu } from '@kododo-dev/way-shell'; +import { useWayNavigate } from '../../utils/useWayNavigate'; import List from '@mui/material/List'; import ListItem from '@mui/material/ListItem'; import ListItemButton from '@mui/material/ListItemButton'; @@ -11,8 +13,8 @@ import Badge from '@mui/material/Badge'; import DashboardIcon from '@mui/icons-material/Dashboard'; import HourglassEmptyIcon from '@mui/icons-material/HourglassEmpty'; import AutorenewIcon from '@mui/icons-material/Autorenew'; -import CheckCircleOutlineIcon from '@mui/icons-material/CheckCircleOutline'; -import ErrorOutlineIcon from '@mui/icons-material/ErrorOutline'; +import CheckCircleOutlineIcon from '@mui/icons-material/CheckCircleOutlined'; +import ErrorOutlineIcon from '@mui/icons-material/ErrorOutlined'; import RotateRightIcon from '@mui/icons-material/RotateRight'; import DirectionsRunIcon from '@mui/icons-material/DirectionsRun'; import RepeatIcon from '@mui/icons-material/Repeat'; @@ -30,16 +32,16 @@ const jobCategories = [ { key: 'failed', label: 'Failed', icon: , color: CATEGORY_COLORS.failed }, ]; -const SideMenu = ({ onNavigate }: { onNavigate?: () => void }) => { - const location = useLocation(); - const navigate = useNavigate(); +// The dashboard's items in the shell's side menu; paths are from the dashboard's root. +const SideMenu = () => { + const navigate = useWayNavigate(); const theme = useTheme(); const isDark = theme.palette.mode === 'dark'; const { history: _history, ...jobCounts } = useJobMetrics(); const { onlineCount: runnersCount } = useRunners(); const { totalCount: recurrencesCount } = useRecurrences(); - const path = location.pathname; + const path = '/' + (useParams()['*'] ?? ''); const isDashboard = path === '/' || path === '/dashboard'; const isRunners = path === '/runners'; const isRecurrences = path === '/recurrences' || path.startsWith('/recurrences/'); @@ -72,97 +74,99 @@ const SideMenu = ({ onNavigate }: { onNavigate?: () => void }) => { }; return ( - - - - - { navigate('/'); onNavigate?.(); }}> - - - - - - - + - - - - Jobs - - - + - {jobCategories.map(cat => { - const active = jobCategoryFromPath === cat.key; - const count = jobCounts[cat.key as keyof typeof jobCounts] ?? 0; - return ( - - { navigate(`/jobs/category/${cat.key}`); onNavigate?.(); }} - > - - {cat.icon} - - - {count > 0 && ( - - )} - - - ); - })} - - - - - - { navigate('/recurrences'); onNavigate?.(); }}> - - - {recurrencesCount > 0 && ( - - )} - - - - - - { navigate('/runners'); onNavigate?.(); }}> - - - {runnersCount > 0 && ( - - )} + navigate('/')}> + + + + + + + + Jobs + + + + + {jobCategories.map(cat => { + const active = jobCategoryFromPath === cat.key; + const count = jobCounts[cat.key as keyof typeof jobCounts] ?? 0; + return ( + + navigate(`/jobs/category/${cat.key}`)} + > + + {cat.icon} + + + {count > 0 && ( + + )} + + + ); + })} + + + + + + navigate('/recurrences')}> + + + {recurrencesCount > 0 && ( + + )} + + + + + + + navigate('/runners')}> + + + {runnersCount > 0 && ( + + )} + + + + + - + ); }; diff --git a/src/RunWay.Dashboard/SPA/src/components/recurrences/RecurrencesTable.tsx b/src/RunWay.Dashboard/SPA/src/components/recurrences/RecurrencesTable.tsx index 5eaf48e..ccfe516 100644 --- a/src/RunWay.Dashboard/SPA/src/components/recurrences/RecurrencesTable.tsx +++ b/src/RunWay.Dashboard/SPA/src/components/recurrences/RecurrencesTable.tsx @@ -2,7 +2,7 @@ import { DataGrid } from '@mui/x-data-grid'; import type { GridColDef } from '@mui/x-data-grid'; import { type Recurrence } from '../../api/api.model'; import { useTheme } from '@mui/material/styles'; -import { useNavigate } from 'react-router-dom'; +import { useWayNavigate } from '../../utils/useWayNavigate'; interface RecurrencesTableProps { rows: Recurrence[]; @@ -44,7 +44,7 @@ const columns: GridColDef[] = [ const RecurrencesTable = ({ rows, rowCount, loading, paginationModel, setPaginationModel }: RecurrencesTableProps) => { const theme = useTheme(); const isDark = theme.palette.mode === 'dark'; - const navigate = useNavigate(); + const navigate = useWayNavigate(); const borderColor = isDark ? '#2e2e2e' : '#e0e0e0'; const headerBg = isDark ? '#222' : '#fafafa'; diff --git a/src/RunWay.Dashboard/SPA/src/context/JobMetricsContext.tsx b/src/RunWay.Dashboard/SPA/src/context/JobMetricsContext.tsx index 7742315..f994522 100644 --- a/src/RunWay.Dashboard/SPA/src/context/JobMetricsContext.tsx +++ b/src/RunWay.Dashboard/SPA/src/context/JobMetricsContext.tsx @@ -1,6 +1,6 @@ import { createContext, useContext, useEffect, useState, useRef } from 'react'; import type { ReactNode } from 'react'; -import { fetchJobMetrics } from '../api/api'; +import { useApi } from '../api/api'; export interface JobCounts { running: number; @@ -28,6 +28,7 @@ const JobMetricsContext = createContext({ }); export const JobMetricsProvider = ({ children }: { children: ReactNode }) => { + const api = useApi(); const [jobCounts, setJobCounts] = useState({ running: 0, succeeded: 0, @@ -41,7 +42,7 @@ export const JobMetricsProvider = ({ children }: { children: ReactNode }) => { useEffect(() => { let mounted = true; const fetchMetrics = async () => { - const data = await fetchJobMetrics(); + const data = await api.fetchJobMetrics(); const now = Date.now(); if (mounted) { setJobCounts(data); @@ -66,7 +67,7 @@ export const JobMetricsProvider = ({ children }: { children: ReactNode }) => { mounted = false; clearInterval(interval); }; - }, []); + }, [api]); return ( diff --git a/src/RunWay.Dashboard/SPA/src/context/LayoutContext.tsx b/src/RunWay.Dashboard/SPA/src/context/LayoutContext.tsx deleted file mode 100644 index d933640..0000000 --- a/src/RunWay.Dashboard/SPA/src/context/LayoutContext.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import { createContext, useContext } from 'react'; - -interface LayoutContextValue { - openMobileMenu: () => void; -} - -export const LayoutContext = createContext({ - openMobileMenu: () => {}, -}); - -export const useLayout = () => useContext(LayoutContext); diff --git a/src/RunWay.Dashboard/SPA/src/context/RecurrencesContext.tsx b/src/RunWay.Dashboard/SPA/src/context/RecurrencesContext.tsx index b84612c..999958b 100644 --- a/src/RunWay.Dashboard/SPA/src/context/RecurrencesContext.tsx +++ b/src/RunWay.Dashboard/SPA/src/context/RecurrencesContext.tsx @@ -1,6 +1,6 @@ import { createContext, useContext, useEffect, useState } from 'react'; import type { ReactNode } from 'react'; -import { fetchRecurrences } from '../api/api'; +import { useApi } from '../api/api'; interface RecurrencesContextValue { totalCount: number; @@ -11,13 +11,14 @@ const RecurrencesContext = createContext({ }); export const RecurrencesProvider = ({ children }: { children: ReactNode }) => { + const api = useApi(); const [totalCount, setTotalCount] = useState(0); useEffect(() => { let mounted = true; const load = async () => { try { - const data = await fetchRecurrences(1); + const data = await api.fetchRecurrences(1); if (mounted) setTotalCount(data.totalItems || 0); } catch {} }; @@ -27,7 +28,7 @@ export const RecurrencesProvider = ({ children }: { children: ReactNode }) => { mounted = false; clearInterval(interval); }; - }, []); + }, [api]); return ( diff --git a/src/RunWay.Dashboard/SPA/src/context/RunnersContext.tsx b/src/RunWay.Dashboard/SPA/src/context/RunnersContext.tsx index 93a00b2..0778d3e 100644 --- a/src/RunWay.Dashboard/SPA/src/context/RunnersContext.tsx +++ b/src/RunWay.Dashboard/SPA/src/context/RunnersContext.tsx @@ -1,6 +1,6 @@ import { createContext, useContext, useEffect, useState } from 'react'; import type { ReactNode } from 'react'; -import { fetchRunners } from '../api/api'; +import { useApi } from '../api/api'; import type {Runner} from '../api/api.model'; interface RunnersContextValue { @@ -16,13 +16,14 @@ const RunnersContext = createContext({ }); export const RunnersProvider = ({ children }: { children: ReactNode }) => { + const api = useApi(); const [runners, setRunners] = useState([]); const [loaded, setLoaded] = useState(false); useEffect(() => { let mounted = true; const load = async () => { - const data = await fetchRunners(); + const data = await api.fetchRunners(); if (mounted) { setRunners(data); setLoaded(true); @@ -34,7 +35,7 @@ export const RunnersProvider = ({ children }: { children: ReactNode }) => { mounted = false; clearInterval(interval); }; - }, []); + }, [api]); return ( x.status === 'Online').length, loaded }}> diff --git a/src/RunWay.Dashboard/SPA/src/context/ThemeContext.tsx b/src/RunWay.Dashboard/SPA/src/context/ThemeContext.tsx deleted file mode 100644 index d085ed5..0000000 --- a/src/RunWay.Dashboard/SPA/src/context/ThemeContext.tsx +++ /dev/null @@ -1,119 +0,0 @@ -import React, { createContext, useContext, useState, useCallback, useMemo } from 'react'; -import { createTheme, type Theme } from '@mui/material/styles'; - -type ThemeMode = 'dark' | 'light'; - -interface ThemeContextValue { - mode: ThemeMode; - toggleMode: () => void; - muiTheme: Theme; -} - -const STORAGE_KEY = 'runway-theme'; - -function buildTheme(mode: ThemeMode): Theme { - const isDark = mode === 'dark'; - return createTheme({ - palette: { - mode, - background: { - default: isDark ? '#1a1a1a' : '#f5f5f5', - paper: isDark ? '#242424' : '#ffffff', - }, - primary: { main: isDark ? '#90caf9' : '#1976d2' }, - secondary: { main: isDark ? '#ce93d8' : '#9c27b0' }, - error: { main: '#f44336' }, - success: { main: '#4caf50' }, - divider: isDark ? '#333' : '#ddd', - text: { - primary: isDark ? '#e0e0e0' : '#1a1a1a', - secondary: isDark ? '#888' : '#555', - }, - }, - typography: { - fontFamily: "'IBM Plex Mono', 'Fira Code', monospace", - }, - components: { - MuiDrawer: { - styleOverrides: { - paper: { - background: isDark ? '#222' : '#fafafa', - borderRight: `1px solid ${isDark ? '#333' : '#e0e0e0'}`, - }, - }, - }, - MuiCard: { - styleOverrides: { - root: { - background: isDark ? '#2a2a2a' : '#fff', - border: `1px solid ${isDark ? '#333' : '#ddd'}`, - }, - }, - }, - MuiOutlinedInput: { - styleOverrides: { - root: { - '& fieldset': { borderColor: isDark ? '#444' : '#ccc' }, - '&:hover fieldset': { borderColor: isDark ? '#666' : '#999' }, - }, - input: { - fontFamily: "'IBM Plex Mono', monospace", - fontSize: '0.875rem', - }, - }, - }, - MuiListItemButton: { - styleOverrides: { - root: { - borderRadius: 6, - margin: '2px 8px', - width: 'calc(100% - 16px)', - '&.Mui-selected': { - background: isDark ? '#333' : '#e3f2fd', - '&:hover': { background: isDark ? '#3a3a3a' : '#bbdefb' }, - }, - }, - }, - }, - MuiChip: { - styleOverrides: { - root: { - fontFamily: "'IBM Plex Mono', monospace", - fontSize: '0.7rem', - }, - }, - }, - }, - }); -} - -const ThemeContext = createContext({ - mode: 'dark', - toggleMode: () => {}, - muiTheme: buildTheme('dark'), -}); - -export const ThemeContextProvider = ({ children }: { children: React.ReactNode }) => { - const [mode, setMode] = useState(() => { - const stored = localStorage.getItem(STORAGE_KEY); - return stored === 'light' ? 'light' : 'dark'; - }); - - const toggleMode = useCallback(() => { - setMode(prev => { - const next = prev === 'dark' ? 'light' : 'dark'; - localStorage.setItem(STORAGE_KEY, next); - return next; - }); - }, []); - - const muiTheme = useMemo(() => buildTheme(mode), [mode]); - - return ( - - {children} - - ); -}; - -export const useThemeMode = () => useContext(ThemeContext); diff --git a/src/RunWay.Dashboard/SPA/src/fonts.css b/src/RunWay.Dashboard/SPA/src/fonts.css new file mode 100644 index 0000000..7977535 --- /dev/null +++ b/src/RunWay.Dashboard/SPA/src/fonts.css @@ -0,0 +1,2 @@ +/* The dashboard's monospace font; only a font face, no page-wide styles (the page belongs to the shell). */ +@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;600;700&display=swap'); diff --git a/src/RunWay.Dashboard/SPA/src/index.css b/src/RunWay.Dashboard/SPA/src/index.css deleted file mode 100644 index 922fb8b..0000000 --- a/src/RunWay.Dashboard/SPA/src/index.css +++ /dev/null @@ -1,24 +0,0 @@ -:root { - font-family: 'IBM Plex Mono', 'Fira Code', monospace; - line-height: 1.5; - font-weight: 400; - font-synthesis: none; - text-rendering: optimizeLegibility; - -webkit-font-smoothing: antialiased; -} - -* { box-sizing: border-box; } - -body { - margin: 0; - min-width: 320px; - min-height: 100vh; -} - -::-webkit-scrollbar { width: 6px; height: 6px; } -::-webkit-scrollbar-track { background: transparent; } -::-webkit-scrollbar-thumb { background: #444; border-radius: 3px; } -::-webkit-scrollbar-thumb:hover { background: #666; } - -[data-theme="light"] ::-webkit-scrollbar-thumb { background: #ccc; } -[data-theme="light"] ::-webkit-scrollbar-thumb:hover { background: #aaa; } diff --git a/src/RunWay.Dashboard/SPA/src/main.tsx b/src/RunWay.Dashboard/SPA/src/main.tsx index a757447..b95981b 100644 --- a/src/RunWay.Dashboard/SPA/src/main.tsx +++ b/src/RunWay.Dashboard/SPA/src/main.tsx @@ -1,7 +1,8 @@ -import { createRoot } from 'react-dom/client' -import './index.css' -import App from './App.tsx' +// `npm run dev`: the dashboard inside WayDevHost, with the API of the demo app through Vite's proxy. +import { createRoot } from 'react-dom/client'; +import { WayDevHost } from '@kododo-dev/way-shell'; +import way from './way'; createRoot(document.getElementById('root')!).render( - -) + , +); diff --git a/src/RunWay.Dashboard/SPA/src/pages/JobPage.tsx b/src/RunWay.Dashboard/SPA/src/pages/JobPage.tsx index 8db9873..5ff0373 100644 --- a/src/RunWay.Dashboard/SPA/src/pages/JobPage.tsx +++ b/src/RunWay.Dashboard/SPA/src/pages/JobPage.tsx @@ -1,6 +1,6 @@ import { useCallback, useEffect, useState } from 'react'; import { useParams } from 'react-router-dom'; -import { fetchJobDetails, requeueJob } from '../api/api'; +import { useApi } from '../api/api'; import type { JobDetails, JobAuditItem } from '../api/api.model'; import Box from '@mui/material/Box'; import Typography from '@mui/material/Typography'; @@ -29,6 +29,7 @@ const REFRESH_INTERVAL = 5000; const JobPage = () => { const { id } = useParams<{ id: string }>(); + const api = useApi(); const theme = useTheme(); const isDark = theme.palette.mode === 'dark'; @@ -44,11 +45,11 @@ const JobPage = () => { const fetchAndSetJob = useCallback(() => { if (!id) return; - fetchJobDetails(id) + api.fetchJobDetails(id) .then(data => { setJob(data); setError(null); }) .catch(() => setError('Error while fetching job details.')) .finally(() => setLoading(false)); - }, [id]); + }, [api, id]); useEffect(() => { setLoading(true); @@ -62,7 +63,7 @@ const JobPage = () => { setRequeueing(true); setRequeueError(null); try { - await requeueJob(id); + await api.requeueJob(id); fetchAndSetJob(); } catch { setRequeueError('Requeue failed. Please try again.'); diff --git a/src/RunWay.Dashboard/SPA/src/pages/JobsPage.tsx b/src/RunWay.Dashboard/SPA/src/pages/JobsPage.tsx index 9d079a6..7a94ae9 100644 --- a/src/RunWay.Dashboard/SPA/src/pages/JobsPage.tsx +++ b/src/RunWay.Dashboard/SPA/src/pages/JobsPage.tsx @@ -1,7 +1,7 @@ import { useEffect, useState } from 'react'; import { useParams } from 'react-router-dom'; import Box from '@mui/material/Box'; -import { fetchJobs } from '../api/api'; +import { useApi } from '../api/api'; import { type JobCategory } from '../api/api.model'; import type { JobListItem } from '../api/api.model'; import JobsTable from '../components/jobs/JobsTable'; @@ -25,6 +25,7 @@ const categoryLabel: Record = { const JobsPage = () => { const { category } = useParams<{ category?: string }>(); + const api = useApi(); const jobCategoryFromPath = category || null; const [rows, setRows] = useState([]); @@ -35,19 +36,19 @@ const JobsPage = () => { useEffect(() => { if (jobCategoryFromPath && pathToJobCategory[jobCategoryFromPath] !== undefined) { setLoading(true); - fetchJobs(pathToJobCategory[jobCategoryFromPath], paginationModel.page + 1).then(data => { + api.fetchJobs(pathToJobCategory[jobCategoryFromPath], paginationModel.page + 1).then(data => { setRows(data.items || []); setRowCount(data.totalItems || 0); setLoading(false); setPaginationModel(prev => ({ ...prev, pageSize: data.pageSize })); }); } - }, [jobCategoryFromPath, paginationModel.page]); + }, [api, jobCategoryFromPath, paginationModel.page]); const handleRefresh = async () => { if (!jobCategoryFromPath || pathToJobCategory[jobCategoryFromPath] === undefined) return; setLoading(true); - const data = await fetchJobs(pathToJobCategory[jobCategoryFromPath], paginationModel.page + 1); + const data = await api.fetchJobs(pathToJobCategory[jobCategoryFromPath], paginationModel.page + 1); setRows(data.items || []); setRowCount(data.totalItems || 0); setLoading(false); diff --git a/src/RunWay.Dashboard/SPA/src/pages/RecurrencePage.tsx b/src/RunWay.Dashboard/SPA/src/pages/RecurrencePage.tsx index 9bc6237..b8d0735 100644 --- a/src/RunWay.Dashboard/SPA/src/pages/RecurrencePage.tsx +++ b/src/RunWay.Dashboard/SPA/src/pages/RecurrencePage.tsx @@ -1,6 +1,6 @@ import { useCallback, useEffect, useState } from 'react'; import { useParams } from 'react-router-dom'; -import { fetchRecurrence, fetchRecurrenceJobs } from '../api/api'; +import { useApi } from '../api/api'; import type { Recurrence, JobListItem } from '../api/api.model'; import Box from '@mui/material/Box'; import Typography from '@mui/material/Typography'; @@ -15,6 +15,7 @@ const MONO = { fontFamily: "'IBM Plex Mono', monospace" }; const RecurrencePage = () => { const { id } = useParams<{ id: string }>(); + const api = useApi(); const theme = useTheme(); const isDark = theme.palette.mode === 'dark'; @@ -33,22 +34,22 @@ const RecurrencePage = () => { const fetchAndSetRecurrence = useCallback(() => { if (!id) return; - fetchRecurrence(id) + api.fetchRecurrence(id) .then(data => { setRecurrence(data); setError(null); }) .catch(() => setError('Error while fetching recurrence details.')) .finally(() => setLoading(false)); - }, [id]); + }, [api, id]); const fetchJobs = useCallback((page: number) => { if (!id) return; setJobsLoading(true); - fetchRecurrenceJobs(id, page).then(data => { + api.fetchRecurrenceJobs(id, page).then(data => { setJobRows(data.items || []); setJobRowCount(data.totalItems || 0); setJobPaginationModel(prev => ({ ...prev, pageSize: data.pageSize })); setJobsLoading(false); }); - }, [id]); + }, [api, id]); useEffect(() => { setLoading(true); diff --git a/src/RunWay.Dashboard/SPA/src/pages/RecurrencesPage.tsx b/src/RunWay.Dashboard/SPA/src/pages/RecurrencesPage.tsx index 49c7cdc..105bd0f 100644 --- a/src/RunWay.Dashboard/SPA/src/pages/RecurrencesPage.tsx +++ b/src/RunWay.Dashboard/SPA/src/pages/RecurrencesPage.tsx @@ -1,11 +1,12 @@ import { useEffect, useState } from 'react'; import Box from '@mui/material/Box'; -import { fetchRecurrences } from '../api/api'; +import { useApi } from '../api/api'; import type { Recurrence } from '../api/api.model'; import RecurrencesTable from '../components/recurrences/RecurrencesTable'; import PageHeader from '../components/layout/PageHeader'; const RecurrencesPage = () => { + const api = useApi(); const [rows, setRows] = useState([]); const [paginationModel, setPaginationModel] = useState({ page: 0, pageSize: 10 }); const [rowCount, setRowCount] = useState(0); @@ -13,17 +14,17 @@ const RecurrencesPage = () => { useEffect(() => { setLoading(true); - fetchRecurrences(paginationModel.page + 1).then(data => { + api.fetchRecurrences(paginationModel.page + 1).then(data => { setRows(data.items || []); setRowCount(data.totalItems || 0); setLoading(false); setPaginationModel(prev => ({ ...prev, pageSize: data.pageSize })); }); - }, [paginationModel.page]); + }, [api, paginationModel.page]); const handleRefresh = async () => { setLoading(true); - const data = await fetchRecurrences(paginationModel.page + 1); + const data = await api.fetchRecurrences(paginationModel.page + 1); setRows(data.items || []); setRowCount(data.totalItems || 0); setLoading(false); diff --git a/src/RunWay.Dashboard/SPA/src/pages/RunnerPage.tsx b/src/RunWay.Dashboard/SPA/src/pages/RunnerPage.tsx index d7665b0..3dbe667 100644 --- a/src/RunWay.Dashboard/SPA/src/pages/RunnerPage.tsx +++ b/src/RunWay.Dashboard/SPA/src/pages/RunnerPage.tsx @@ -1,5 +1,6 @@ import { useEffect, useState } from 'react'; -import { useParams, useNavigate } from 'react-router-dom'; +import { useParams } from 'react-router-dom'; +import { useWayNavigate } from '../utils/useWayNavigate'; import Box from '@mui/material/Box'; import Typography from '@mui/material/Typography'; import Chip from '@mui/material/Chip'; @@ -8,7 +9,7 @@ import Tooltip from '@mui/material/Tooltip'; import RefreshIcon from '@mui/icons-material/Refresh'; import { useTheme } from '@mui/material/styles'; import type {RunnerDetails} from "../api/api.model.ts"; -import {fetchRunner} from "../api/api.ts"; +import { useApi } from '../api/api'; const MONO = { fontFamily: "'IBM Plex Mono', monospace" }; @@ -32,7 +33,8 @@ function formatDate(dateStr: string): string { const RunnerPage = () => { const { id } = useParams<{ id: string }>(); - const navigate = useNavigate(); + const navigate = useWayNavigate(); + const api = useApi(); const theme = useTheme(); const isDark = theme.palette.mode === 'dark'; @@ -55,7 +57,7 @@ const RunnerPage = () => { if (showLoading) setRunnerLoading(true); setRunnerError(false); try { - const data = await fetchRunner(id); + const data = await api.fetchRunner(id); setRunner(data); } catch { setRunnerError(true); diff --git a/src/RunWay.Dashboard/SPA/src/pages/RunnersPage.tsx b/src/RunWay.Dashboard/SPA/src/pages/RunnersPage.tsx index 941ece5..d7ed6aa 100644 --- a/src/RunWay.Dashboard/SPA/src/pages/RunnersPage.tsx +++ b/src/RunWay.Dashboard/SPA/src/pages/RunnersPage.tsx @@ -1,4 +1,4 @@ -import { useNavigate } from 'react-router-dom'; +import { useWayNavigate } from '../utils/useWayNavigate'; import Box from '@mui/material/Box'; import Typography from '@mui/material/Typography'; import { useTheme } from '@mui/material/styles'; @@ -26,7 +26,7 @@ interface RunnerCardProps { } const RunnerCard = ({ runner }: RunnerCardProps) => { - const navigate = useNavigate(); + const navigate = useWayNavigate(); const theme = useTheme(); const isDark = theme.palette.mode === 'dark'; const online = isOnline(runner.lastSeenAt); diff --git a/src/RunWay.Dashboard/SPA/src/theme.ts b/src/RunWay.Dashboard/SPA/src/theme.ts new file mode 100644 index 0000000..8aca1c9 --- /dev/null +++ b/src/RunWay.Dashboard/SPA/src/theme.ts @@ -0,0 +1,61 @@ +import type { ThemeOptions } from '@mui/material'; + +const MONO = "'IBM Plex Mono', 'Fira Code', monospace"; + +/** The dashboard's look on top of the shell's theme: monospace type and its own greys, cards and inputs. */ +export function runWayTheme(mode: 'light' | 'dark'): ThemeOptions { + const isDark = mode === 'dark'; + return { + palette: { + background: { + default: isDark ? '#1a1a1a' : '#f5f5f5', + paper: isDark ? '#242424' : '#ffffff', + }, + primary: { main: isDark ? '#90caf9' : '#1976d2' }, + secondary: { main: isDark ? '#ce93d8' : '#9c27b0' }, + error: { main: '#f44336' }, + success: { main: '#4caf50' }, + divider: isDark ? '#333' : '#ddd', + text: { + primary: isDark ? '#e0e0e0' : '#1a1a1a', + secondary: isDark ? '#888' : '#555', + }, + }, + typography: { fontFamily: MONO }, + components: { + MuiCard: { + styleOverrides: { + root: { + background: isDark ? '#2a2a2a' : '#fff', + border: `1px solid ${isDark ? '#333' : '#ddd'}`, + }, + }, + }, + MuiOutlinedInput: { + styleOverrides: { + root: { + '& fieldset': { borderColor: isDark ? '#444' : '#ccc' }, + '&:hover fieldset': { borderColor: isDark ? '#666' : '#999' }, + }, + input: { fontFamily: MONO, fontSize: '0.875rem' }, + }, + }, + MuiListItemButton: { + styleOverrides: { + root: { + borderRadius: 6, + margin: '2px 8px', + width: 'calc(100% - 16px)', + '&.Mui-selected': { + background: isDark ? '#333' : '#e3f2fd', + '&:hover': { background: isDark ? '#3a3a3a' : '#bbdefb' }, + }, + }, + }, + }, + MuiChip: { + styleOverrides: { root: { fontFamily: MONO, fontSize: '0.7rem' } }, + }, + }, + }; +} diff --git a/src/RunWay.Dashboard/SPA/src/utils/useWayNavigate.ts b/src/RunWay.Dashboard/SPA/src/utils/useWayNavigate.ts new file mode 100644 index 0000000..47a444a --- /dev/null +++ b/src/RunWay.Dashboard/SPA/src/utils/useWayNavigate.ts @@ -0,0 +1,15 @@ +import { useCallback } from 'react'; +import { useShell } from '@kododo-dev/way-shell'; + +/** + * Navigates by a path from the dashboard's root, e.g. `/jobs/42`, through the shell: in the panel the dashboard sits + * under #/{wayId}/, on its own page under #/. Router links relative to the current route would resolve against the + * whole path of the shell's splat route. + */ +export function useWayNavigate() { + const { navigate, wayId } = useShell(); + return useCallback( + (path: string, options?: { replace?: boolean }) => navigate(wayId, path.replace(/^\//, ''), options), + [navigate, wayId], + ); +} diff --git a/src/RunWay.Dashboard/SPA/src/way.tsx b/src/RunWay.Dashboard/SPA/src/way.tsx new file mode 100644 index 0000000..5804689 --- /dev/null +++ b/src/RunWay.Dashboard/SPA/src/way.tsx @@ -0,0 +1,7 @@ +import { defineWay } from '@kododo-dev/way-shell'; +import App from './App'; +import { runWayTheme } from './theme'; +import './fonts.css'; + +// The module Way.Shell loads (built by `npm run build` into dist/way.js and dist/way.css). +export default defineWay({ App, theme: runWayTheme }); diff --git a/src/RunWay.Dashboard/SPA/vite.config.ts b/src/RunWay.Dashboard/SPA/vite.config.ts index 52f567f..5fd30f1 100644 --- a/src/RunWay.Dashboard/SPA/vite.config.ts +++ b/src/RunWay.Dashboard/SPA/vite.config.ts @@ -1,17 +1,10 @@ import { defineConfig } from 'vite' import react from '@vitejs/plugin-react' -import path from 'path' +import { wayModule } from '@kododo-dev/way-shell/vite' +// `npm run build`: src/way.tsx as a Way.Shell module (dist/way.js, dist/way.css), embedded by RunWay.Dashboard. +// `npm run dev`: src/main.tsx with the module in WayDevHost; the API goes to the demo app (RunWay.Demo.WebApp). export default defineConfig({ - base: '', - plugins: [react()], - resolve: { - alias: { - react: path.resolve(__dirname, 'node_modules/react'), - 'react-dom': path.resolve(__dirname, 'node_modules/react-dom'), - }, - }, - build: { - chunkSizeWarningLimit: 1000 - } + plugins: [react(), wayModule()], + server: { proxy: { '/scheduler/api': process.env.RUNWAY_BACKEND ?? 'http://localhost:5236' } }, }) diff --git a/src/RunWay.Demo.WebApp/Program.cs b/src/RunWay.Demo.WebApp/Program.cs index cf3c252..e2cd107 100644 --- a/src/RunWay.Demo.WebApp/Program.cs +++ b/src/RunWay.Demo.WebApp/Program.cs @@ -76,7 +76,8 @@ await runway.Job(new FailingJob { Reason = "intentional demo error" }) return Results.Ok(); }); -app.UseRunWayDashboard(); +// The public demo: anyone may look at the jobs. +app.UseRunWayDashboard().AllowAnonymous(); await app.InitializeRunWayDatabaseAsync(); diff --git a/src/RunWay.Demo.WebApp/e2e/check.mjs b/src/RunWay.Demo.WebApp/e2e/check.mjs new file mode 100644 index 0000000..aa5c352 --- /dev/null +++ b/src/RunWay.Demo.WebApp/e2e/check.mjs @@ -0,0 +1,96 @@ +// Browser scenarios of the dashboard in Way.Shell, against the running demo (Production, AllowAnonymous): +// node check.mjs http://localhost:5092 [chrome|msedge|firefox|webkit] +// Schedules a few jobs through the demo's own endpoints first. Needs the playwright package +// (npm i --no-save playwright); firefox and webkit need `npx playwright install`. +import { chromium, firefox, webkit } from 'playwright'; + +const base = process.argv[2].replace(/\/$/, ''); +const which = process.argv[3] ?? 'chrome'; +const browser = which === 'firefox' ? await firefox.launch() : which === 'webkit' ? await webkit.launch() : await chromium.launch({ channel: which }); +const results = []; +const record = (name, ok, detail = '') => results.push({ name, ok, detail }); +const visible = (l, timeout = 10000) => l.first().waitFor({ timeout }).then(() => true, () => false); +const hashIs = (page, hash) => page.waitForURL((u) => u.hash === hash, { timeout: 10000 }).then(() => true, () => false); + +for (const job of ['quick', 'fail']) await fetch(`${base}/jobs/${job}`, { method: 'POST' }); + +async function open(options = {}) { + const context = await browser.newContext({ locale: 'en-US', colorScheme: 'light', ...options }); + const page = await context.newPage(); + const errors = []; + page.on('pageerror', (e) => errors.push(e.message)); + page.on('console', (m) => { if (m.type() === 'error' && !m.text().startsWith('Failed to load resource')) errors.push(m.text()); }); + page.on('response', (r) => { if (r.status() >= 400 && !r.url().endsWith('/favicon.ico')) errors.push(`${r.status()} ${r.url()}`); }); + return { context, page, errors }; +} + +{ + const { context, page, errors } = await open(); + await page.goto(`${base}/scheduler/`); + record('dashboard opens in the shell', await visible(page.getByRole('heading', { name: 'Dashboard' }))); + record('title from the Way', (await page.title()) === 'Scheduler', await page.title()); + record('status tiles', await visible(page.getByText('PENDING'))); + const menu = page.getByTestId('way-menu'); + record("dashboard's menu in the shell menu", await visible(menu.getByText('Recurrences'))); + + // The failing job reaches Failed after its retries; Pending, Running or Retrying hold it until then. + await menu.getByText('Failed').click(); + record('jobs by status from the menu', await hashIs(page, '#/jobs/category/failed'), page.url()); + const failedRow = page.locator('.MuiDataGrid-row').filter({ hasText: 'FailingJob' }); + const hasFailed = await visible(failedRow, 60000); + record('data grid with a failed job', hasFailed); + + if (hasFailed) { + await failedRow.first().click(); + record('job page from a grid row', (await visible(page.getByRole('heading', { name: /^Job #/ }))) && /#\/jobs\/[^/]+$/.test(new URL(page.url()).hash), page.url()); + const requeue = page.getByRole('button', { name: 'Requeue' }); + if (await visible(requeue, 5000)) { + const [response] = await Promise.all([ + page.waitForResponse((r) => r.url().endsWith('/scheduler/api/RequeueJob')), + requeue.click(), + ]); + record('requeue through the shell API client', response.ok(), `${response.status()}`); + } else { + record('requeue through the shell API client', false, 'no Requeue button'); + } + } + + await menu.getByText('Recurrences').click(); + record('menu from a nested page (paths from the Way root)', await hashIs(page, '#/recurrences'), page.url()); + await visible(page.locator('.MuiDataGrid-row')); + await page.locator('.MuiDataGrid-row').first().click(); + record('recurrence page', await visible(page.getByRole('heading', { name: /^Recurrence #/ }))); + await menu.getByText('Runners').click(); + record('runners from the menu', await hashIs(page, '#/runners'), page.url()); + + await page.goto(`${base}/scheduler/#/nope`); + record('unknown page goes to the Way root', await hashIs(page, '#/'), page.url()); + + await page.getByRole('button', { name: 'Language' }).click(); + await page.getByRole('menuitem', { name: 'Polski' }).click(); + record('shell language switch', await visible(page.getByRole('button', { name: 'Ciemny motyw' }))); + await page.getByRole('button', { name: 'Ciemny motyw' }).click(); + const header = await page.locator('main header').first().evaluate((e) => getComputedStyle(e).backgroundColor); + record("dashboard's dark paper in its area", header === 'rgb(36, 36, 36)', header); + const font = await page.locator('main h1').first().evaluate((e) => getComputedStyle(e).fontFamily); + record("dashboard's monospace theme in its area", /IBM Plex Mono/.test(font), font); + record('no errors', errors.length === 0, errors.join(' | ')); + await context.close(); +} + +{ + const { context, page } = await open({ viewport: { width: 390, height: 800 } }); + await page.goto(`${base}/scheduler/#/jobs/category/pending`); + await visible(page.getByRole('heading', { name: 'Pending jobs' })); + await page.getByRole('button', { name: 'Open menu' }).click(); + await page.getByTestId('way-menu').getByText('Runners').click(); + const closed = await page.getByTestId('way-menu').waitFor({ state: 'hidden', timeout: 5000 }).then(() => true, () => false); + record('phone: an item of the menu closes it', closed && (await hashIs(page, '#/runners')), page.url()); + await context.close(); +} + +await browser.close(); +const failed = results.filter((r) => !r.ok); +for (const r of results) console.log(`${r.ok ? 'ok ' : 'FAIL'} ${r.name}${r.detail ? ` — ${r.detail}` : ''}`); +console.log(`\n${which} ${base}: ${results.length - failed.length}/${results.length} ok`); +process.exit(failed.length ? 1 : 0); diff --git a/src/RunWay.Tests/Integration/DashboardShellTests.cs b/src/RunWay.Tests/Integration/DashboardShellTests.cs new file mode 100644 index 0000000..1f7dd21 --- /dev/null +++ b/src/RunWay.Tests/Integration/DashboardShellTests.cs @@ -0,0 +1,67 @@ +using System.Net; +using System.Text; +using Kododo.RunWay.Dashboard; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Routing; +using Microsoft.AspNetCore.TestHost; +using Microsoft.Extensions.DependencyInjection; +using Xunit; + +namespace Kododo.RunWay.Tests.Integration; + +/// The dashboard runs in Way.Shell: the shell's page, the dashboard's module, and the shell's protection. +public class DashboardShellTests +{ + private static async Task<(WebApplication App, HttpClient Client)> StartAsync(Action? decide) + { + var builder = WebApplication.CreateBuilder(new WebApplicationOptions { EnvironmentName = "Production" }); + builder.WebHost.UseTestServer(); + builder.Services.AddRunWay(x => x.AddDashboard()); + var app = builder.Build(); + var group = app.UseRunWayDashboard("/jobs"); + decide?.Invoke(group); + await app.StartAsync(); + return (app, app.GetTestClient()); + } + + [Fact] + public async Task Page_IsTheShellWithTheDashboardsModule() + { + var (app, client) = await StartAsync(g => g.AllowAnonymous()); + await using var _ = app; + + var html = await client.GetStringAsync("/jobs/"); + + Assert.Contains("""""", html); + Assert.Contains("""