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.

diff --git a/src/Directory.Build.props b/src/Directory.Build.props
index ecd9420..43d6d60 100644
--- a/src/Directory.Build.props
+++ b/src/Directory.Build.props
@@ -5,13 +5,21 @@
enable
Latest
true
- NU1901;NU1902;NU1903;NU1904
none
+
+
+ false
+ $(MSBuildThisFileDirectory)..\..\WayShell\src
+
+
Kododo.$(MSBuildProjectName)
Kododo.$(MSBuildProjectName)
diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props
index b87841c..e1e7604 100644
--- a/src/Directory.Packages.props
+++ b/src/Directory.Packages.props
@@ -4,31 +4,68 @@
true
+
+
+ 10.0.13
+ 8.0.31
+ 9.0.20
+
+
+
+
+ 10.0.13
+ 10.0.3
+ 10.0.3
+ 8.0.29
+ 8.0.11
+ 8.0.9
+ 9.0.21
+ 9.0.4
+ 9.0.5
+
+
+
+
+ 10.0.13
+ 8.0.2
+ 9.0.21
+
+
-
-
-
-
-
-
+
+
+
+
+
+
+
-
+
+
-
+
+
+
-
-
+
+
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/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..ff6e91a 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": "^2.0.0-preview.1",
+ "@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,31 @@
"@jridgewell/sourcemap-codec": "^1.4.14"
}
},
+ "node_modules/@kododo-dev/way-shell": {
+ "version": "2.0.0-preview.1",
+ "resolved": "https://registry.npmjs.org/@kododo-dev/way-shell/-/way-shell-2.0.0-preview.1.tgz",
+ "integrity": "sha512-bs5g5SuBmhd+4ii7yUeCyDMi6HZdlBoiNkZSTOSxmc5W3Nv06bC3Fp744lSWdSA2X0/8aon/Xby9htjzA6UqPg==",
+ "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 +729,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 +744,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 +754,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 +783,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 +804,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 +839,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 +859,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 +892,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 +932,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 +978,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 +1001,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 +1037,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 +1067,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 +1081,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 +1133,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 +1185,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 +1271,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 +1530,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 +1611,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 +1630,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 +1862,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 +1959,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 +2087,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 +2436,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 +2491,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 +2728,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 +2803,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 +3123,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 +3444,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 +3610,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 +3623,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 +3643,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 +3689,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 +3738,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 +3761,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 +3873,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 +3962,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 +4022,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 +4065,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 +4106,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 +4182,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 +4208,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 +4224,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..c1bc102 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": "^2.0.0-preview.1",
+ "@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..8e0b244 100644
--- a/src/RunWay.Dashboard/SPA/src/App.tsx
+++ b/src/RunWay.Dashboard/SPA/src/App.tsx
@@ -1,53 +1,60 @@
-import { HashRouter, Routes, Route, Navigate } from 'react-router-dom';
-import { ThemeProvider, CssBaseline } from '@mui/material';
-import { ThemeContextProvider, useThemeMode } from './context/ThemeContext';
+import { Suspense, lazy, useEffect } from 'react';
+import { Routes, Route } from 'react-router-dom';
+import Box from '@mui/material/Box';
+import CircularProgress from '@mui/material/CircularProgress';
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';
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();
+// The pages with the data grid (MUI X) and the timeline (Lab) load when opened, so the dashboard does not wait for them.
+const JobsPage = lazy(() => import('./pages/JobsPage'));
+const JobPage = lazy(() => import('./pages/JobPage'));
+const RecurrencesPage = lazy(() => import('./pages/RecurrencesPage'));
+const RecurrencePage = lazy(() => import('./pages/RecurrencePage'));
+
+function Loading() {
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/DashboardPage.tsx b/src/RunWay.Dashboard/SPA/src/pages/DashboardPage.tsx
index 9892127..479822f 100644
--- a/src/RunWay.Dashboard/SPA/src/pages/DashboardPage.tsx
+++ b/src/RunWay.Dashboard/SPA/src/pages/DashboardPage.tsx
@@ -1,9 +1,12 @@
+import { Suspense, lazy } from 'react';
import Box from '@mui/material/Box';
import PageHeader from '../components/layout/PageHeader';
import DashboardTiles from '../components/dashboard/DashboardTiles';
-import ChartJobsHistory from '../components/dashboard/ChartJobsHistory';
import { useJobMetrics } from '../context/JobMetricsContext';
+// recharts is most of the dashboard's code; the tiles show while it loads.
+const ChartJobsHistory = lazy(() => import('../components/dashboard/ChartJobsHistory'));
+
const DashboardPage = () => {
const { history, ...jobCounts } = useJobMetrics();
@@ -12,7 +15,9 @@ const DashboardPage = () => {
-
+
+
+
);
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.EntityFramework/Entities/DbJob.cs b/src/RunWay.EntityFramework/Entities/DbJob.cs
index d8d2452..faa7a76 100644
--- a/src/RunWay.EntityFramework/Entities/DbJob.cs
+++ b/src/RunWay.EntityFramework/Entities/DbJob.cs
@@ -60,6 +60,5 @@ public DbJobOptions Options
[ForeignKey(nameof(RunnerId))]
public DbRunner? Runner { get; set; }
- [ForeignKey(nameof(ReccurenceId))]
public DbRecurrence? Recurrence { get; set; }
}
\ No newline at end of file
diff --git a/src/RunWay.EntityFramework/Entities/DbRecurrence.cs b/src/RunWay.EntityFramework/Entities/DbRecurrence.cs
index 78f9dc3..d6db40c 100644
--- a/src/RunWay.EntityFramework/Entities/DbRecurrence.cs
+++ b/src/RunWay.EntityFramework/Entities/DbRecurrence.cs
@@ -49,6 +49,5 @@ public JobOptions Options
set => this.SerializedOptions = JsonSerializer.Serialize(value);
}
- [ForeignKey(nameof(JobId))]
public DbJob? Job { get; set; }
}
\ No newline at end of file
diff --git a/src/RunWay.EntityFramework/JobsDbContext.cs b/src/RunWay.EntityFramework/JobsDbContext.cs
index e3530d7..08c021a 100644
--- a/src/RunWay.EntityFramework/JobsDbContext.cs
+++ b/src/RunWay.EntityFramework/JobsDbContext.cs
@@ -1,4 +1,4 @@
-using Kododo.RunWay.EntityFramework.Conversions;
+using Kododo.RunWay.EntityFramework.Conversions;
using Kododo.RunWay.EntityFramework.Entities;
using Microsoft.EntityFrameworkCore;
@@ -20,6 +20,11 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.HasDefaultSchema("runway");
+
+ // Two relationships, not one: a job points at the recurrence that scheduled it, and a recurrence at its next
+ // job. Configured here rather than with [ForeignKey] on both navigations, which EF Core first tries to pair.
+ modelBuilder.Entity().HasOne(x => x.Recurrence).WithMany().HasForeignKey(x => x.ReccurenceId);
+ modelBuilder.Entity().HasOne(x => x.Job).WithMany().HasForeignKey(x => x.JobId);
}
protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
diff --git a/src/RunWay.EntityFramework/Store.cs b/src/RunWay.EntityFramework/Store.cs
index ac737f1..6e42a0b 100644
--- a/src/RunWay.EntityFramework/Store.cs
+++ b/src/RunWay.EntityFramework/Store.cs
@@ -223,12 +223,10 @@ public async Task> GetManyAsync(JobsQuery query, CancellationTo
}
var count = await result.CountAsync(stoppingToken);
-
- if(query.Sorting != null && query.Sorting.Count > 0)
- {
- result = ApplySorting(result, query.Sorting);
- }
-
+
+ // Always ordered: pages of an unordered query can repeat or skip jobs.
+ result = ApplySorting(result, query.Sorting ?? []);
+
result = result
.Skip((query.Pagination.Page - 1) * query.Pagination.PageSize)
.Take(query.Pagination.PageSize);
@@ -458,7 +456,8 @@ private static IQueryable ApplySorting(IQueryable query, IReadOnly
}
}
- return orderedResult ?? query;
+ // The id last, so jobs with equal values keep their order from page to page.
+ return orderedResult?.ThenBy(x => x.Id) ?? query.OrderBy(x => x.Id);
}
private static JobId ToJobId(long id)
diff --git a/src/RunWay.SqlServer/RunWay.SqlServer.csproj b/src/RunWay.SqlServer/RunWay.SqlServer.csproj
index 35bc917..cdcf64b 100644
--- a/src/RunWay.SqlServer/RunWay.SqlServer.csproj
+++ b/src/RunWay.SqlServer/RunWay.SqlServer.csproj
@@ -12,6 +12,8 @@
runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
diff --git a/src/RunWay.SqlServer/RunWayConfigurationExtensions.cs b/src/RunWay.SqlServer/RunWayConfigurationExtensions.cs
index e2dbf5e..44bac48 100644
--- a/src/RunWay.SqlServer/RunWayConfigurationExtensions.cs
+++ b/src/RunWay.SqlServer/RunWayConfigurationExtensions.cs
@@ -10,7 +10,16 @@ public static IRunWayConfiguration UseSqlServer(this IRunWayConfiguration config
{
configuration.AddRunWayEntityFramework((serviceProvider, optionsBuilder) =>
SqlServerDbContextOptionsExtensions.UseSqlServer(optionsBuilder, connectionFactory(serviceProvider),
- options => options.MigrationsAssembly(typeof(RunWayConfigurationExtensions).Assembly.GetName().Name)));
+ options =>
+ {
+ options.MigrationsAssembly(typeof(RunWayConfigurationExtensions).Assembly.GetName().Name);
+#if NET10_0_OR_GREATER
+ // EF Core 10 sends a list (the job statuses runners poll for) as one parameter per item; the plan
+ // that gives deadlocks runners polling with runners updating jobs. One JSON parameter, as in EF
+ // Core 8 and 9, does not.
+ options.UseParameterizedCollectionMode(ParameterTranslationMode.Parameter);
+#endif
+ }));
return configuration;
}
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
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("""