From f3e4935ab41b25e647bcb32290b8aa3f28c527e5 Mon Sep 17 00:00:00 2001 From: Ajay Singh Date: Mon, 23 Mar 2026 05:35:40 +0000 Subject: [PATCH 01/50] feat: harden config and startup validation --- README.md | 34 +++ docs/improvement-plan.md | 107 ++++++++++ docs/open-issues-plan.md | 65 ++++++ package-lock.json | 7 - src/config.reader.ts | 160 ++++++++++++++- src/index.ts | 67 +++--- src/middlewares/nameSpaceHandler.ts | 17 ++ src/server.ts | 34 ++- src/types.ts | 17 +- test/index.js | 307 ++++++++++++++++++++++++++++ yarn.lock | 5 - 11 files changed, 752 insertions(+), 68 deletions(-) create mode 100644 docs/improvement-plan.md create mode 100644 docs/open-issues-plan.md diff --git a/README.md b/README.md index 181816b..f30c768 100644 --- a/README.md +++ b/README.md @@ -74,6 +74,40 @@ dummyRest: cache: false ``` +## Allow / Deny URL patterns + +Each namespace accepts optional `allow` and `deny` glob pattern lists that control which paths Osham proxies. Requests blocked by these rules receive a `403` response with the header `x-osham-cache: denied`. + +**Precedence rules:** + +- If `deny` is set and the path matches any pattern → **403 Forbidden** (deny always wins). +- Else if `allow` is set and the path does **not** match any pattern → **403 Forbidden**. +- If neither `allow` nor `deny` is present, all paths within the namespace are handled normally (existing behavior unchanged). + +**Example:** + +```yaml +myNs: + expose: '/api/v1/*' + target: 'http://localhost:3000' + cache: + expires: 10s + allow: + - '/employees/**' + - '/employee/*' + deny: + - '/employees/private/**' +``` + +In this example: + +- `/employees/123` → proxied (matches allow) +- `/employee/5` → proxied (matches allow) +- `/employees/private/data` → **403** (deny wins, even though it also matches `/employees/**` in allow) +- `/departments/1` → **403** (not in allow list) + +Patterns follow glob syntax (e.g. `*` matches a single path segment, `**` matches any number of segments). + ## Purge cache (administrative) Osham provides an admin endpoint to invalidate cache by exact key or by pattern. See the detailed guide: diff --git a/docs/improvement-plan.md b/docs/improvement-plan.md new file mode 100644 index 0000000..6c39c8a --- /dev/null +++ b/docs/improvement-plan.md @@ -0,0 +1,107 @@ +# Osham Improvement Plan + +## Snapshot +Osham is a TypeScript-based configurable proxy cache for HTTP APIs with request pooling, purge support, and Prometheus metrics. The project already has a useful core and a decent integration-style test suite, but it shows its age in dependency hygiene, config validation, operational hardening, and contributor ergonomics. + +## What looks good +- Clear purpose and compact architecture. +- Good README for a first-time user. +- Useful integration tests around pooling, cache keys, purge, health, and metrics. +- Metrics and purge docs already exist. + +## Main weaknesses observed +1. **Dependency health is poor** + - `npm install` reports many vulnerabilities. + - Core packages are old (`redis@3`, old eslint/typescript toolchain, `path-to-regexp@0.1.7`). +2. **Config loading is fragile** + - `cache-config.yml` is required but only loosely parsed. + - No schema validation despite a TODO in `config.reader.ts`. + - Startup errors are likely to be confusing for users. +3. **Server startup / operational hardening is thin** + - Minimal startup logging. + - No explicit startup validation for required SSL envs when `SECURE=true`. + - Error handling is basic and may hide root causes. +4. **Type modeling can be improved** + - `ICacheConfig` typing is too loose. + - Some types blur top-level config vs namespace map. +5. **Developer experience is dated** + - Build/test/lint depend on local install but repo gives no setup section for contributors. + - No CI metadata found at top level. +6. **Docs can be stronger** + - Missing architecture/flow explanation for cache key composition, pooling behavior, and deployment expectations. + - No troubleshooting guide. + - README references `cache-config.example.yml`, but that file does not appear in repo root. + +## Recommended task breakdown + +### Phase 1 — Stabilize and validate +1. **Add config schema validation** + - Validate top-level config and namespace definitions on startup. + - Produce clear error messages with field names and examples. + - Fix README/example references if example file is missing. + +2. **Improve startup and runtime error handling** + - Validate required env vars for secure mode. + - Fail fast with actionable messages. + - Improve request-chain error handling so users get safe errors and logs remain useful. + +3. **Tighten type definitions** + - Separate global config shape from namespace map. + - Reduce unsafe casting in `config.reader.ts`. + +### Phase 2 — Upgrade maintainability +4. **Modernize dependencies carefully** + - Audit direct dependencies. + - Upgrade low-risk tooling first. + - Investigate runtime-impacting upgrades separately (especially redis and routing-related libs). + - Keep behavior stable and expand tests before risky upgrades. + +5. **Expand automated validation** + - Add tests for invalid config. + - Add tests for secure-mode startup validation. + - Add tests for edge cases around header/query variation and purge safety. + +6. **Add CI** + - Run build, lint, and tests on push/PR. + - Optionally add audit checks with a non-blocking or staged policy. + +### Phase 3 — Product/documentation polish +7. **Improve docs** + - Add contributor setup section. + - Add troubleshooting page. + - Add deployment guidance (Redis expectations, TLS mode, purge protection, metrics scraping). + - Document cache key composition and pooling semantics. + +8. **Operational hardening** + - Restrict/admin-protect purge in production. + - Add clearer warnings around anonymous GET-only caching. + - Consider namespaced observability and startup config summary logs. + +## Suggested execution order for Claude + +### Task A — Config validation + startup failures +**Goal:** Make startup safe and predictable. +- Implement config validation. +- Add tests for invalid/missing config. +- Improve secure mode env validation. +- Update README/docs to match actual config behavior. + +### Task B — Type cleanup +**Goal:** Reduce unsafe casts and improve maintainability. +- Refactor config/type model. +- Remove loose typing where practical. +- Keep external behavior unchanged. + +### Task C — Docs cleanup +**Goal:** Make onboarding and operations easier. +- Fix missing/incorrect doc references. +- Add troubleshooting and contributor setup. + +### Task D — Dependency modernization plan +**Goal:** Reduce risk before touching runtime-heavy packages. +- Produce an upgrade matrix. +- Separate safe upgrades from risky ones. +- Make changes incrementally with test coverage. + +## Immediate recommendation +Have Claude start with **Task A (config validation + startup hardening)** first. It gives the best user-visible improvement with relatively low architectural risk. diff --git a/docs/open-issues-plan.md b/docs/open-issues-plan.md new file mode 100644 index 0000000..ba9dd7f --- /dev/null +++ b/docs/open-issues-plan.md @@ -0,0 +1,65 @@ +# Osham Open Issues Plan + +## Current open items + +### Issue #53 — Allow/Disallow urls patterns +- URL: https://github.com/ajaysinghj8/osham/issues/53 +- Type: feature +- Status: open +- Notes: issue body is empty, so implementation needs a sensible config design and documented precedence rules. + +### PR #82 — build(deps): bump flatted from 3.1.0 to 3.4.2 +- URL: https://github.com/ajaysinghj8/osham/pull/82 +- Type: dependency/security maintenance +- Status: open +- Notes: low-risk dependency/security update; should be reviewed after the current local code changes are stabilized. + +## Proposed sequencing + +1. Finish local hardening work already in flight: + - Task A: startup/config validation + - Task B: type cleanup and deeper validation +2. Implement Issue #53 with tests and docs. +3. Review dependency PR #82 and either merge or manually absorb the version bump. +4. Continue docs/CI/dependency modernization. + +## Issue #53 implementation proposal + +### Goal +Allow Osham users to declare URL patterns that are explicitly allowed or explicitly denied for caching/proxy behavior within a namespace. + +### Proposed config model +Per namespace: + +```yml +myNs: + expose: '/api/*' + target: 'http://localhost:3000' + allow: + - '/employees/**' + - '/employee/*' + deny: + - '/employees/private/**' +``` + +Optional future rule-level support can be added later, but namespace-level support is enough for the first release. + +### Proposed semantics +- If `allow` exists, only matching paths are allowed. +- If `deny` exists, matching paths are denied. +- If both exist, **deny wins**. +- If neither exists, current behavior remains unchanged. +- Denied requests should fail clearly and predictably (or bypass caching/proxying based on chosen product behavior). + +### Recommended first implementation +- Add optional `allow` / `deny` arrays to namespace config. +- Validate them in config loading. +- Add path matching helper. +- Enforce allow/deny before proxy/cache handling. +- Add tests for precedence and default behavior. +- Document examples in README. + +## PR #82 handling plan +- Check whether `flatted` is directly or transitively relevant after current install tree resolution. +- If low-risk, absorb the bump after current feature work lands. +- Re-run tests/lint after dependency update. diff --git a/package-lock.json b/package-lock.json index 5eec56b..7cbb9c9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -89,7 +89,6 @@ "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", @@ -1034,7 +1033,6 @@ "integrity": "sha512-KO0J5SRF08pMXzq9+abyHnaGQgUJZ3Z3ax+pmqz9vl81JxmTTOUfQmq7/4awVfq09b6C4owNlOgOwp61pYRBSg==", "dev": true, "license": "BSD-2-Clause", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "4.13.0", "@typescript-eslint/types": "4.13.0", @@ -1179,7 +1177,6 @@ "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", "dev": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -1952,7 +1949,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -3746,7 +3742,6 @@ "integrity": "sha512-fbgTiE8BfUJZuBeq2Yi7J3RB3WGUQ9PNuNbmgi6jt9Iv8qrkxfy19Ds3OpL1Pm7zg3BtTVhvcUZbIRQ0wmSjAQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.0.0", "@eslint/eslintrc": "^0.3.0", @@ -8721,7 +8716,6 @@ "integrity": "sha512-PqyhM2yCjg/oKkFPtTGUojv7gnZAoG80ttl45O6x2Ug/rMJw4wcc9k6aaf2hibP7BGVCCM33gZoGjyvt9mm16Q==", "dev": true, "license": "MIT", - "peer": true, "bin": { "prettier": "bin-prettier.js" }, @@ -10881,7 +10875,6 @@ "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", "dev": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" diff --git a/src/config.reader.ts b/src/config.reader.ts index 0b77a74..45ae159 100644 --- a/src/config.reader.ts +++ b/src/config.reader.ts @@ -1,7 +1,7 @@ import { load } from 'js-yaml'; import { readFileSync } from 'fs'; import { join } from 'path'; -import { ICacheConfig } from './types'; +import { IFullConfig, INameSpaceOptions, ICacheOptions, IRulesOptions } from './types'; function supplant(o = {}) { return this.replace(/\${([^{}]*)}/g, (a: string, b: string) => { @@ -13,9 +13,159 @@ function supplant(o = {}) { }); } -export function getCacheConfig(): ICacheConfig { +const RESERVED_KEYS = new Set(['version', 'xResponseTime', 'health', 'purge', 'metrics', 'changeOrigin']); + +function validateCacheOptions(value: unknown, context: string): ICacheOptions { + if (value === false || value === undefined || value === null) { + return value === false ? false : undefined; + } + if (typeof value !== 'object' || Array.isArray(value)) { + throw new Error(`cache-config.yml: ${context}: "cache" must be an object, false, or omitted`); + } + const c = value as Record; + if ('expires' in c && c.expires !== undefined && typeof c.expires !== 'string' && typeof c.expires !== 'number') { + throw new Error(`cache-config.yml: ${context}: "cache.expires" must be a string or number (e.g. "10s", 60)`); + } + if ('pool' in c && c.pool !== undefined && typeof c.pool !== 'boolean') { + throw new Error(`cache-config.yml: ${context}: "cache.pool" must be a boolean`); + } + if ('query' in c && c.query !== undefined && c.query !== false) { + if (!Array.isArray(c.query) || !(c.query as unknown[]).every(q => typeof q === 'string')) { + throw new Error(`cache-config.yml: ${context}: "cache.query" must be false or an array of strings`); + } + } + if ('headers' in c && c.headers !== undefined && c.headers !== false) { + if (!Array.isArray(c.headers) || !(c.headers as unknown[]).every(h => typeof h === 'string')) { + throw new Error(`cache-config.yml: ${context}: "cache.headers" must be false or an array of strings`); + } + } + return value as ICacheOptions; +} + +function validateRules(value: unknown, ns: string): IRulesOptions { + if (value === undefined || value === null) return {}; + if (typeof value !== 'object' || Array.isArray(value)) { + throw new Error(`cache-config.yml: namespace "${ns}": "rules" must be an object`); + } + const rules = value as Record; + for (const rulePath of Object.keys(rules)) { + const rule = rules[rulePath]; + if (typeof rule !== 'object' || Array.isArray(rule) || rule === null) { + throw new Error(`cache-config.yml: namespace "${ns}": rule "${rulePath}" must be an object with a "cache" field`); + } + validateCacheOptions((rule as Record).cache, `namespace "${ns}", rule "${rulePath}"`); + } + return value as IRulesOptions; +} + +export function validateConfig(config: unknown): IFullConfig { + if (typeof config !== 'object' || config === null || Array.isArray(config)) { + throw new Error('cache-config.yml: config must be a YAML mapping object'); + } + const cfg = config as Record; + + if (!cfg.version) { + throw new Error('cache-config.yml: missing required field "version" (e.g. version: \'1\')'); + } + + const BOOL_GLOBAL_FLAGS = ['xResponseTime', 'health', 'purge', 'metrics', 'changeOrigin'] as const; + for (const field of BOOL_GLOBAL_FLAGS) { + if (field in cfg && cfg[field] !== undefined && typeof cfg[field] !== 'boolean') { + throw new Error(`cache-config.yml: "${field}" must be a boolean (true or false)`); + } + } + + const namespaceKeys = Object.keys(cfg).filter(k => !RESERVED_KEYS.has(k)); + if (namespaceKeys.length === 0) { + throw new Error('cache-config.yml: at least one proxy namespace must be defined with "expose" and "target" fields'); + } + + const namespaces: Record = {}; + + for (const ns of namespaceKeys) { + const nsConfig = cfg[ns]; + if (typeof nsConfig !== 'object' || nsConfig === null || Array.isArray(nsConfig)) { + throw new Error(`cache-config.yml: namespace "${ns}" must be an object`); + } + const nsCfg = nsConfig as Record; + + if (typeof nsCfg.expose !== 'string' || !nsCfg.expose) { + throw new Error( + `cache-config.yml: namespace "${ns}" is missing required string field "expose" (e.g. expose: '/api/v1/*')`, + ); + } + if (typeof nsCfg.target !== 'string' || !nsCfg.target) { + throw new Error( + `cache-config.yml: namespace "${ns}" is missing required string field "target" (e.g. target: 'http://localhost:3000')`, + ); + } + if ('port' in nsCfg && nsCfg.port !== undefined && typeof nsCfg.port !== 'number') { + throw new Error(`cache-config.yml: namespace "${ns}": "port" must be a number`); + } + if ('timeout' in nsCfg && nsCfg.timeout !== undefined && typeof nsCfg.timeout !== 'number') { + throw new Error(`cache-config.yml: namespace "${ns}": "timeout" must be a number`); + } + if ( + 'followRedirects' in nsCfg && + nsCfg.followRedirects !== undefined && + typeof nsCfg.followRedirects !== 'boolean' + ) { + throw new Error(`cache-config.yml: namespace "${ns}": "followRedirects" must be a boolean`); + } + if ('changeOrigin' in nsCfg && nsCfg.changeOrigin !== undefined && typeof nsCfg.changeOrigin !== 'boolean') { + throw new Error(`cache-config.yml: namespace "${ns}": "changeOrigin" must be a boolean`); + } + + for (const listField of ['allow', 'deny'] as const) { + if (listField in nsCfg && nsCfg[listField] !== undefined) { + if (!Array.isArray(nsCfg[listField]) || !(nsCfg[listField] as unknown[]).every(p => typeof p === 'string')) { + throw new Error( + `cache-config.yml: namespace "${ns}": "${listField}" must be an array of glob pattern strings`, + ); + } + } + } + + const cache = validateCacheOptions(nsCfg.cache, `namespace "${ns}"`); + const rules = validateRules(nsCfg.rules, ns); + + const nsOptions: INameSpaceOptions = { + expose: nsCfg.expose, + target: nsCfg.target, + cache, + rules, + }; + if (nsCfg.port !== undefined) nsOptions.port = nsCfg.port as number; + if (nsCfg.timeout !== undefined) nsOptions.timeout = nsCfg.timeout as number; + if (nsCfg.followRedirects !== undefined) nsOptions.followRedirects = nsCfg.followRedirects as boolean; + if (nsCfg.changeOrigin !== undefined) nsOptions.changeOrigin = nsCfg.changeOrigin as boolean; + if (nsCfg.allow !== undefined) nsOptions.allow = nsCfg.allow as string[]; + if (nsCfg.deny !== undefined) nsOptions.deny = nsCfg.deny as string[]; + + namespaces[ns] = nsOptions; + } + + return { + globalConfig: { + version: String(cfg.version), + xResponseTime: cfg.xResponseTime === true, + health: cfg.health === true, + purge: cfg.purge === true, + metrics: cfg.metrics === true, + changeOrigin: cfg.changeOrigin === true, + }, + namespaces, + }; +} + +export function getCacheConfig(): IFullConfig { const configFilePath = join(process.cwd(), 'cache-config.yml'); - const config = load(supplant.call(readFileSync(configFilePath, 'utf-8'), process.env)); - // @todo : validate yml config rules - return (config as unknown) as ICacheConfig; + let raw: string; + try { + raw = readFileSync(configFilePath, 'utf-8'); + } catch { + throw new Error(`cache-config.yml not found at "${configFilePath}". Create it to configure Osham.`); + } + const config = load(supplant.call(raw, process.env)); + return validateConfig(config); } diff --git a/src/index.ts b/src/index.ts index 03eb977..53cc2c0 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,6 @@ import { config } from 'dotenv'; config(); +import * as Debug from 'debug'; import { Server } from './server'; import { IncomingMessage, ServerResponse } from 'http'; import { getCacheConfig } from './config.reader'; @@ -10,59 +11,45 @@ import { MetricsEndpoint } from './middlewares/metricsEndpoint'; import { createNameSpaceHandler } from './middlewares/nameSpaceHandler'; import { CtxProvider } from './ctx.provider'; import * as compose from 'koa-compose'; -import { isNameSpace } from './utils'; -import { INameSpaceOptions, IContext } from './types'; +import { IContext } from './types'; import { ComposedMiddleware } from 'koa-compose'; // import { timeoutMiddlewareProvider } from './middlewares/timeoutMiddleware'; -const middlewares: Array> = []; -const cacheConfig = getCacheConfig(); +const logger = Debug('acp:index'); -// /** -// * For each namespace -// * create handler -// */ +const middlewares: Array> = []; +const { globalConfig, namespaces } = getCacheConfig(); if (process.env.TIMEOUT) { // middlewares.push(timeoutMiddlewareProvider(+process.env.TIMEOUT)); } -for (const key in cacheConfig) { - if (!Object.prototype.hasOwnProperty.call(cacheConfig, key)) continue; - switch (key) { - case 'version': - case 'changeOrigin': - break; - case 'xResponseTime': - middlewares.push(RouteTimeReqRes); - break; - case 'health': - middlewares.push(HealthCheck); - break; - case 'purge': - if (Reflect.get(cacheConfig, key) === true) { - middlewares.push(PurgeCache); - } - break; - case 'metrics': - if (Reflect.get(cacheConfig, key) === true) { - middlewares.push(MetricsEndpoint); - } - break; - default: { - // it is namespace - const options: INameSpaceOptions = Reflect.get(cacheConfig, key); - if (isNameSpace(key, options)) { - middlewares.push(createNameSpaceHandler(key, options)); - } - } - } + +if (globalConfig.xResponseTime) middlewares.push(RouteTimeReqRes); +if (globalConfig.health) middlewares.push(HealthCheck); +if (globalConfig.purge) middlewares.push(PurgeCache); +if (globalConfig.metrics) middlewares.push(MetricsEndpoint); + +for (const [key, options] of Object.entries(namespaces)) { + middlewares.push(createNameSpaceHandler(key, options)); } Server.on('request', async (req: IncomingMessage, res: ServerResponse) => { const ctx = CtxProvider(req, res); const chain = compose(middlewares); res.statusCode = 404; - const onerror = (err: string) => res.end(err); + + const handleError = (err: unknown) => { + const message = err instanceof Error ? err.message : String(err); + logger(`Unhandled request error for ${req.method} ${req.url}: ${message}`); + if (!res.headersSent) { + res.statusCode = 500; + res.setHeader('content-type', 'text/plain; charset=utf-8'); + res.end('Internal Server Error'); + return; + } + res.end(); + }; + const handleResponse = () => ctx.respond(); - return chain(ctx).then(handleResponse).catch(onerror); + return chain(ctx).then(handleResponse).catch(handleError); }); diff --git a/src/middlewares/nameSpaceHandler.ts b/src/middlewares/nameSpaceHandler.ts index d9bc366..afaecfa 100644 --- a/src/middlewares/nameSpaceHandler.ts +++ b/src/middlewares/nameSpaceHandler.ts @@ -9,6 +9,7 @@ import { createProxy } from '../proxy'; import * as Koa from 'koa'; import { OshamHeaders } from '../osham.headers'; +import { minimatch } from 'minimatch'; // eslint-disable-next-line const pathToRegExp = require('path-to-regexp'); @@ -33,6 +34,22 @@ export function createNameSpaceHandler( const pathToCall = ctx.path.match(namespacePath)[1]; logger(`${pathToCall} will be processed!`); + // Allow/deny pattern enforcement: deny wins over allow. + // Normalize to an absolute path so patterns like '/employees/**' match consistently. + const matchPath = pathToCall.startsWith('/') ? pathToCall : `/${pathToCall}`; + if (options.deny && options.deny.some(pattern => minimatch(matchPath, pattern))) { + ctx.statusCode = 403; + ctx.set('x-osham-cache', 'denied'); + ctx.body = 'Forbidden'; + return ctx.respond(); + } + if (options.allow && !options.allow.some(pattern => minimatch(matchPath, pattern))) { + ctx.statusCode = 403; + ctx.set('x-osham-cache', 'denied'); + ctx.body = 'Forbidden'; + return ctx.respond(); + } + const cacheConfig = configContext.getCacheConfig(pathToCall); const proxyPath = pathToCall + (ctx.search || ''); if (ctx.method !== 'GET' || !cacheConfig) { diff --git a/src/server.ts b/src/server.ts index 99e859b..83659d0 100644 --- a/src/server.ts +++ b/src/server.ts @@ -1,17 +1,33 @@ import * as Debug from 'debug'; -import { readFileSync } from 'fs'; -import { createServer } from 'http'; -import { createServer as createSecureServer } from 'https'; +import { existsSync, readFileSync } from 'fs'; +import { Server as HttpServer, createServer } from 'http'; +import { Server as HttpsServer, ServerOptions, createServer as createSecureServer } from 'https'; const logger = Debug('acp:server'); -function createAServer() { +export function getSecureServerOptions(): ServerOptions { + if (!process.env.SSL_KEY) { + throw new Error('SECURE=true requires SSL_KEY env var to be set to a PEM key file path'); + } + if (!process.env.SSL_CERT) { + throw new Error('SECURE=true requires SSL_CERT env var to be set to a PEM certificate file path'); + } + if (!existsSync(process.env.SSL_KEY)) { + throw new Error(`SECURE=true could not find SSL key file at "${process.env.SSL_KEY}"`); + } + if (!existsSync(process.env.SSL_CERT)) { + throw new Error(`SECURE=true could not find SSL certificate file at "${process.env.SSL_CERT}"`); + } + + return { + key: readFileSync(process.env.SSL_KEY), + cert: readFileSync(process.env.SSL_CERT), + }; +} + +export function createAServer(): HttpServer | HttpsServer { if (process.env.SECURE === 'true') { - const options = { - key: readFileSync(process.env.SSL_KEY), - cert: readFileSync(process.env.SSL_CERT), - }; - return createSecureServer(options); + return createSecureServer(getSecureServerOptions()); } return createServer(); } diff --git a/src/types.ts b/src/types.ts index 2427096..b8b853a 100644 --- a/src/types.ts +++ b/src/types.ts @@ -20,8 +20,10 @@ export interface INameSpaceOptions { followRedirects?: boolean; changeOrigin?: boolean; timeout?: number; - rules: IRulesOptions; + rules?: IRulesOptions; cache: ICacheOptions; + allow?: string[]; + deny?: string[]; } export interface IInternalResponse { @@ -41,7 +43,18 @@ export interface IParentConfig { changeOrigin: boolean; } -export type ICacheConfig = IParentConfig | { [key: string]: INameSpaceOptions }; +/** + * IFullConfig is the structured result of parsing and validating cache-config.yml. + * It separates the top-level global settings from the namespace map, eliminating + * the need for unsafe casts when iterating over config entries. + * + * Replaces the old ICacheConfig union type (IParentConfig | { [key: string]: INameSpaceOptions }) + * which blurred the two concerns into a single flat object. + */ +export interface IFullConfig { + globalConfig: IParentConfig; + namespaces: Record; +} export interface IContext { req: IncomingMessage; diff --git a/test/index.js b/test/index.js index c67c624..ccc8b8a 100644 --- a/test/index.js +++ b/test/index.js @@ -107,6 +107,17 @@ dummyRest: - x-locale /employee/2/: cache: false +restrictedRest: + expose: '/restricted/*' + target: 'http://localhost:${stubPort}' + changeOrigin: true + cache: + expires: 10s + allow: + - '/employees/**' + - '/employee/*' + deny: + - '/employees/private/**' `; fs.writeFileSync(path.join(tempDir, 'cache-config.yml'), cacheConfig); @@ -154,6 +165,302 @@ after(function (done) { if (pending === 0) done(); }); +describe('Allow/Deny URL Patterns', function () { + this.timeout(5000); + + it('Should allow a path matching an allow pattern', async function () { + const res = await client.get('/restricted/employees/123'); + assert.strictEqual(res.headers['x-osham-cache'], undefined, 'should not be denied'); + assert.notStrictEqual(res.status, 403, 'status should not be 403'); + }); + + it('Should allow a path matching another allow pattern', async function () { + const res = await client.get('/restricted/employee/5'); + assert.strictEqual(res.headers['x-osham-cache'], undefined, 'should not be denied'); + assert.notStrictEqual(res.status, 403, 'status should not be 403'); + }); + + it('Should deny a path not matching any allow pattern', async function () { + const res = await client.get('/restricted/departments/1'); + assert.strictEqual(res.status, 403); + assert.strictEqual(res.headers['x-osham-cache'], 'denied'); + }); + + it('Should deny a path matching a deny pattern even if it also matches allow', async function () { + // /employees/private/secret matches allow (/employees/**) but deny wins + const res = await client.get('/restricted/employees/private/secret'); + assert.strictEqual(res.status, 403); + assert.strictEqual(res.headers['x-osham-cache'], 'denied'); + }); + + it('Should deny a path matching deny but not in allow', async function () { + const res = await client.get('/restricted/employees/private/other'); + assert.strictEqual(res.status, 403); + assert.strictEqual(res.headers['x-osham-cache'], 'denied'); + }); +}); + +describe('Startup Validation', function () { + this.timeout(5000); + + function loadServerModuleWithEnv(env) { + const serverModulePath = require.resolve('../lib/server'); + const previousEnv = { + SECURE: process.env.SECURE, + SSL_KEY: process.env.SSL_KEY, + SSL_CERT: process.env.SSL_CERT, + PORT: process.env.PORT, + }; + + delete require.cache[serverModulePath]; + Object.assign(process.env, env); + + try { + return require('../lib/server'); + } finally { + delete require.cache[serverModulePath]; + for (const [key, value] of Object.entries(previousEnv)) { + if (value === undefined) { + delete process.env[key]; + } else { + process.env[key] = value; + } + } + } + } + + it('Should throw when SECURE=true and SSL_KEY is missing', function () { + assert.throws( + () => loadServerModuleWithEnv({ SECURE: 'true', SSL_KEY: '', SSL_CERT: '/tmp/cert.pem', PORT: '0' }), + /SECURE=true requires SSL_KEY env var/, + ); + }); + + it('Should throw when SECURE=true and SSL_CERT is missing', function () { + assert.throws( + () => loadServerModuleWithEnv({ SECURE: 'true', SSL_KEY: '/tmp/key.pem', SSL_CERT: '', PORT: '0' }), + /SECURE=true requires SSL_CERT env var/, + ); + }); + + it('Should throw when SECURE=true and SSL key file does not exist', function () { + assert.throws( + () => + loadServerModuleWithEnv({ + SECURE: 'true', + SSL_KEY: '/tmp/does-not-exist-key.pem', + SSL_CERT: '/tmp/also-missing-cert.pem', + PORT: '0', + }), + /could not find SSL key file/, + ); + }); +}); + +describe('Config Validation', function () { + this.timeout(5000); + + let validationTempDir; + + before(function () { + validationTempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'osham-validation-')); + }); + + after(function () { + try { + if (originalCwd) process.chdir(originalCwd); + } catch (e) { + // ignore + } + }); + + function withConfig(yaml, fn) { + const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'osham-cfg-')); + fs.writeFileSync(path.join(dir, 'cache-config.yml'), yaml); + const prev = process.cwd(); + process.chdir(dir); + try { + return fn(); + } finally { + process.chdir(prev); + } + } + + it('Should throw when cache-config.yml is missing', function () { + process.chdir(validationTempDir); + // eslint-disable-next-line @typescript-eslint/no-var-requires + const { getCacheConfig } = require('../lib/config.reader'); + assert.throws(() => getCacheConfig(), /cache-config\.yml not found/); + }); + + it('Should throw when version field is missing', function () { + // eslint-disable-next-line @typescript-eslint/no-var-requires + const { getCacheConfig } = require('../lib/config.reader'); + withConfig(`myNs:\n expose: '/api/*'\n target: 'http://localhost:3000'\n`, () => + assert.throws(() => getCacheConfig(), /missing required field "version"/), + ); + }); + + it('Should throw when no namespace is defined', function () { + // eslint-disable-next-line @typescript-eslint/no-var-requires + const { getCacheConfig } = require('../lib/config.reader'); + withConfig(`version: '1'\n`, () => assert.throws(() => getCacheConfig(), /at least one proxy namespace/)); + }); + + it('Should throw when namespace is missing expose', function () { + // eslint-disable-next-line @typescript-eslint/no-var-requires + const { getCacheConfig } = require('../lib/config.reader'); + withConfig(`version: '1'\nmyNs:\n target: 'http://localhost:3000'\n`, () => + assert.throws(() => getCacheConfig(), /missing required string field "expose"/), + ); + }); + + it('Should throw when namespace is missing target', function () { + // eslint-disable-next-line @typescript-eslint/no-var-requires + const { getCacheConfig } = require('../lib/config.reader'); + withConfig(`version: '1'\nmyNs:\n expose: '/api/*'\n`, () => + assert.throws(() => getCacheConfig(), /missing required string field "target"/), + ); + }); + + it('Should accept a valid minimal config', function () { + // eslint-disable-next-line @typescript-eslint/no-var-requires + const { getCacheConfig } = require('../lib/config.reader'); + withConfig(`version: '1'\nmyNs:\n expose: '/api/*'\n target: 'http://localhost:3000'\n`, () => + assert.doesNotThrow(() => getCacheConfig()), + ); + }); + + it('Should return structured IFullConfig with globalConfig and namespaces', function () { + // eslint-disable-next-line @typescript-eslint/no-var-requires + const { getCacheConfig } = require('../lib/config.reader'); + withConfig( + `version: '1'\nxResponseTime: true\nhealth: true\npurge: true\nmyNs:\n expose: '/api/*'\n target: 'http://localhost:3000'\n`, + () => { + const cfg = getCacheConfig(); + assert.ok(cfg.globalConfig, 'should have globalConfig'); + assert.ok(cfg.namespaces, 'should have namespaces'); + assert.strictEqual(cfg.globalConfig.version, '1'); + assert.strictEqual(cfg.globalConfig.xResponseTime, true); + assert.strictEqual(cfg.globalConfig.health, true); + assert.strictEqual(cfg.globalConfig.purge, true); + assert.strictEqual(cfg.globalConfig.metrics, false); + assert.ok(cfg.namespaces.myNs, 'should have myNs namespace'); + assert.strictEqual(cfg.namespaces.myNs.expose, '/api/*'); + assert.strictEqual(cfg.namespaces.myNs.target, 'http://localhost:3000'); + }, + ); + }); + + it('Should throw when global boolean flag is not a boolean', function () { + // eslint-disable-next-line @typescript-eslint/no-var-requires + const { getCacheConfig } = require('../lib/config.reader'); + withConfig(`version: '1'\nhealth: yes_please\nmyNs:\n expose: '/api/*'\n target: 'http://localhost:3000'\n`, () => + assert.throws(() => getCacheConfig(), /"health" must be a boolean/), + ); + }); + + it('Should throw when namespace port is not a number', function () { + // eslint-disable-next-line @typescript-eslint/no-var-requires + const { getCacheConfig } = require('../lib/config.reader'); + withConfig( + `version: '1'\nmyNs:\n expose: '/api/*'\n target: 'http://localhost:3000'\n port: "not-a-number"\n`, + () => assert.throws(() => getCacheConfig(), /"port" must be a number/), + ); + }); + + it('Should throw when namespace timeout is not a number', function () { + // eslint-disable-next-line @typescript-eslint/no-var-requires + const { getCacheConfig } = require('../lib/config.reader'); + withConfig(`version: '1'\nmyNs:\n expose: '/api/*'\n target: 'http://localhost:3000'\n timeout: "fast"\n`, () => + assert.throws(() => getCacheConfig(), /"timeout" must be a number/), + ); + }); + + it('Should throw when namespace cache is not an object or false', function () { + // eslint-disable-next-line @typescript-eslint/no-var-requires + const { getCacheConfig } = require('../lib/config.reader'); + withConfig(`version: '1'\nmyNs:\n expose: '/api/*'\n target: 'http://localhost:3000'\n cache: "invalid"\n`, () => + assert.throws(() => getCacheConfig(), /"cache" must be an object, false, or omitted/), + ); + }); + + it('Should throw when cache.query is not false or array of strings', function () { + // eslint-disable-next-line @typescript-eslint/no-var-requires + const { getCacheConfig } = require('../lib/config.reader'); + withConfig( + `version: '1'\nmyNs:\n expose: '/api/*'\n target: 'http://localhost:3000'\n cache:\n query: "all"\n`, + () => assert.throws(() => getCacheConfig(), /"cache.query" must be false or an array of strings/), + ); + }); + + it('Should throw when cache.headers is not false or array of strings', function () { + // eslint-disable-next-line @typescript-eslint/no-var-requires + const { getCacheConfig } = require('../lib/config.reader'); + withConfig( + `version: '1'\nmyNs:\n expose: '/api/*'\n target: 'http://localhost:3000'\n cache:\n headers: 123\n`, + () => assert.throws(() => getCacheConfig(), /"cache.headers" must be false or an array of strings/), + ); + }); + + it('Should throw when rules entry is not an object', function () { + // eslint-disable-next-line @typescript-eslint/no-var-requires + const { getCacheConfig } = require('../lib/config.reader'); + withConfig( + `version: '1'\nmyNs:\n expose: '/api/*'\n target: 'http://localhost:3000'\n rules:\n /foo/: "invalid"\n`, + () => assert.throws(() => getCacheConfig(), /must be an object with a "cache" field/), + ); + }); + + it('Should accept cache: false on a namespace', function () { + // eslint-disable-next-line @typescript-eslint/no-var-requires + const { getCacheConfig } = require('../lib/config.reader'); + withConfig(`version: '1'\nmyNs:\n expose: '/api/*'\n target: 'http://localhost:3000'\n cache: false\n`, () => { + const cfg = getCacheConfig(); + assert.strictEqual(cfg.namespaces.myNs.cache, false); + }); + }); + + it('Should accept cache: false on a rule', function () { + // eslint-disable-next-line @typescript-eslint/no-var-requires + const { getCacheConfig } = require('../lib/config.reader'); + withConfig( + `version: '1'\nmyNs:\n expose: '/api/*'\n target: 'http://localhost:3000'\n rules:\n /foo/:\n cache: false\n`, + () => assert.doesNotThrow(() => getCacheConfig()), + ); + }); + + it('Should throw when allow is not an array of strings', function () { + // eslint-disable-next-line @typescript-eslint/no-var-requires + const { getCacheConfig } = require('../lib/config.reader'); + withConfig( + `version: '1'\nmyNs:\n expose: '/api/*'\n target: 'http://localhost:3000'\n allow: "not-an-array"\n`, + () => assert.throws(() => getCacheConfig(), /"allow" must be an array of glob pattern strings/), + ); + }); + + it('Should throw when deny is not an array of strings', function () { + // eslint-disable-next-line @typescript-eslint/no-var-requires + const { getCacheConfig } = require('../lib/config.reader'); + withConfig(`version: '1'\nmyNs:\n expose: '/api/*'\n target: 'http://localhost:3000'\n deny:\n - 123\n`, () => + assert.throws(() => getCacheConfig(), /"deny" must be an array of glob pattern strings/), + ); + }); + + it('Should accept valid allow and deny arrays', function () { + // eslint-disable-next-line @typescript-eslint/no-var-requires + const { getCacheConfig } = require('../lib/config.reader'); + withConfig( + `version: '1'\nmyNs:\n expose: '/api/*'\n target: 'http://localhost:3000'\n allow:\n - '/employees/**'\n deny:\n - '/employees/private/**'\n`, + () => { + const cfg = getCacheConfig(); + assert.deepStrictEqual(cfg.namespaces.myNs.allow, ['/employees/**']); + assert.deepStrictEqual(cfg.namespaces.myNs.deny, ['/employees/private/**']); + }, + ); + }); +}); + describe('Specifications', function () { this.timeout(5000); it('Multiple requests to same resource should queue', async function () { diff --git a/yarn.lock b/yarn.lock index 1c74425..5717199 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2376,11 +2376,6 @@ fs.realpath@^1.0.0: resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz" integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= -fsevents@~2.1.2: - version "2.1.3" - resolved "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz" - integrity sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ== - function-bind@^1.1.1: version "1.1.1" resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz" From b36e4f1abe5993bd9c465854ef2e184a95216355 Mon Sep 17 00:00:00 2001 From: Ajay Singh Date: Mon, 23 Mar 2026 06:44:40 +0000 Subject: [PATCH 02/50] feat: polish allow/deny edge cases, unknown-key warnings, startup summary, purge safety MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - nameSpaceHandler: match both /path and /path/ against allow/deny glob patterns so trailing-slash requests are handled consistently without pattern-specific quirks - config.reader: warn on unknown namespace keys (with list of known keys) to catch typos before they silently become no-ops - index.ts: print a human-readable startup summary of config version, enabled features, and each namespace (expose→target, cache config, allow/deny lists) - purgeCache: add broadPatternWarning() — purge responses include a 'warning' field when the pattern lacks the 'O:' prefix that scopes it to one namespace; add optional OSHAM_PURGE_SECRET env-var gate: if set, callers must supply the value in x-osham-purge-secret header or receive 401 - tests: 9 new cases covering trailing-slash allow/deny, single-star depth enforcement, nested precedence, unknown-key warnings, broad-pattern warning, and purge auth Co-Authored-By: Claude Sonnet 4.6 --- src/config.reader.ts | 23 +++++ src/index.ts | 25 ++++++ src/middlewares/nameSpaceHandler.ts | 13 ++- src/middlewares/purgeCache.ts | 32 ++++++- test/index.js | 127 ++++++++++++++++++++++++++++ 5 files changed, 214 insertions(+), 6 deletions(-) diff --git a/src/config.reader.ts b/src/config.reader.ts index 45ae159..e5b1322 100644 --- a/src/config.reader.ts +++ b/src/config.reader.ts @@ -15,6 +15,19 @@ function supplant(o = {}) { const RESERVED_KEYS = new Set(['version', 'xResponseTime', 'health', 'purge', 'metrics', 'changeOrigin']); +const KNOWN_NAMESPACE_KEYS = new Set([ + 'expose', + 'target', + 'port', + 'timeout', + 'followRedirects', + 'changeOrigin', + 'cache', + 'rules', + 'allow', + 'deny', +]); + function validateCacheOptions(value: unknown, context: string): ICacheOptions { if (value === false || value === undefined || value === null) { return value === false ? false : undefined; @@ -126,6 +139,16 @@ export function validateConfig(config: unknown): IFullConfig { } } + for (const key of Object.keys(nsCfg)) { + if (!KNOWN_NAMESPACE_KEYS.has(key)) { + // eslint-disable-next-line no-console + console.warn( + `cache-config.yml: namespace "${ns}": unknown key "${key}" will be ignored` + + ` — known keys: ${[...KNOWN_NAMESPACE_KEYS].join(', ')}`, + ); + } + } + const cache = validateCacheOptions(nsCfg.cache, `namespace "${ns}"`); const rules = validateRules(nsCfg.rules, ns); diff --git a/src/index.ts b/src/index.ts index 53cc2c0..cb45b71 100644 --- a/src/index.ts +++ b/src/index.ts @@ -20,6 +20,31 @@ const logger = Debug('acp:index'); const middlewares: Array> = []; const { globalConfig, namespaces } = getCacheConfig(); +// Startup summary — always visible so operators know exactly what loaded. +const enabledFeatures = + (['xResponseTime', 'health', 'purge', 'metrics', 'changeOrigin'] as const).filter(f => globalConfig[f]).join(', ') || + 'none'; +// eslint-disable-next-line no-console +console.log(`[osham] Config v${globalConfig.version} loaded. Features: ${enabledFeatures}`); +for (const [ns, opts] of Object.entries(namespaces)) { + const cacheInfo = + opts.cache === false + ? 'cache=disabled' + : opts.cache + ? `cache expires=${opts.cache.expires ?? 'default'}${opts.cache.pool ? ' pool=on' : ''}` + : 'cache=unconfigured'; + // eslint-disable-next-line no-console + console.log(`[osham] Namespace "${ns}": ${opts.expose} → ${opts.target} (${cacheInfo})`); + if (opts.allow?.length) { + // eslint-disable-next-line no-console + console.log(`[osham] allow: ${opts.allow.join(', ')}`); + } + if (opts.deny?.length) { + // eslint-disable-next-line no-console + console.log(`[osham] deny: ${opts.deny.join(', ')}`); + } +} + if (process.env.TIMEOUT) { // middlewares.push(timeoutMiddlewareProvider(+process.env.TIMEOUT)); } diff --git a/src/middlewares/nameSpaceHandler.ts b/src/middlewares/nameSpaceHandler.ts index afaecfa..855f751 100644 --- a/src/middlewares/nameSpaceHandler.ts +++ b/src/middlewares/nameSpaceHandler.ts @@ -35,15 +35,20 @@ export function createNameSpaceHandler( logger(`${pathToCall} will be processed!`); // Allow/deny pattern enforcement: deny wins over allow. - // Normalize to an absolute path so patterns like '/employees/**' match consistently. - const matchPath = pathToCall.startsWith('/') ? pathToCall : `/${pathToCall}`; - if (options.deny && options.deny.some(pattern => minimatch(matchPath, pattern))) { + // Normalize to an absolute path. We try matching both /path and /path/ so that + // patterns like '/employees/**' match whether the request ends with '/' or not, + // and patterns like '/employee/*' correctly reject deeper paths like '/employee/5/sub'. + const raw = pathToCall.startsWith('/') ? pathToCall : `/${pathToCall}`; + const matchPath = raw; + const matchPathAlt = raw.endsWith('/') && raw.length > 1 ? raw.slice(0, -1) : `${raw}/`; + const matchesAny = (pattern: string) => minimatch(matchPath, pattern) || minimatch(matchPathAlt, pattern); + if (options.deny && options.deny.some(matchesAny)) { ctx.statusCode = 403; ctx.set('x-osham-cache', 'denied'); ctx.body = 'Forbidden'; return ctx.respond(); } - if (options.allow && !options.allow.some(pattern => minimatch(matchPath, pattern))) { + if (options.allow && !options.allow.some(matchesAny)) { ctx.statusCode = 403; ctx.set('x-osham-cache', 'denied'); ctx.body = 'Forbidden'; diff --git a/src/middlewares/purgeCache.ts b/src/middlewares/purgeCache.ts index 648eecd..62570cd 100644 --- a/src/middlewares/purgeCache.ts +++ b/src/middlewares/purgeCache.ts @@ -4,6 +4,21 @@ import { Cache } from '../services/cache.service'; const DEFAULT_PURGE_PATH = '/__osham/purge'; +/** + * Returns a warning string if `pattern` is unusually broad (e.g. no namespace prefix), + * otherwise undefined. Broad purges can accidentally clear keys across all namespaces. + * Use the "O::" prefix to scope purges safely. + */ +function broadPatternWarning(pattern: string): string | undefined { + if (pattern === '*' || pattern === '**' || !pattern.startsWith('O:')) { + return ( + 'Pattern may match keys across all namespaces. ' + + 'Use the "O::" prefix (e.g. "O:myNs:/api/v1/users*") for a safer, scoped purge.' + ); + } + return undefined; +} + export async function PurgeCache(ctx: IContext, next: Koa.Next): Promise { const purgePath = process.env.OSHAM_PURGE_PATH || DEFAULT_PURGE_PATH; if (ctx.path !== purgePath) { @@ -17,6 +32,19 @@ export async function PurgeCache(ctx: IContext, next: Koa.Next): Promise { return; } + // Optional shared-secret auth. Set OSHAM_PURGE_SECRET to require callers to supply + // the matching value in the x-osham-purge-secret request header. + const purgeSecret = process.env.OSHAM_PURGE_SECRET; + if (purgeSecret) { + const provided = ctx.get('x-osham-purge-secret'); + if (provided !== purgeSecret) { + ctx.statusCode = 401; + ctx.set('content-type', 'application/json'); + ctx.body = JSON.stringify({ error: 'Unauthorized: x-osham-purge-secret header required' }); + return; + } + } + const query = ctx.query || {}; const key = query.key; const pattern = query.pattern; @@ -28,14 +56,14 @@ export async function PurgeCache(ctx: IContext, next: Koa.Next): Promise { } try { + const warning = pattern ? broadPatternWarning(String(pattern)) : undefined; const deleted = key ? await Cache.purge(String(key)) : await Cache.purgeByPattern(String(pattern)); ctx.statusCode = 200; ctx.set('content-type', 'application/json'); - ctx.body = JSON.stringify({ deleted }); + ctx.body = JSON.stringify(warning ? { deleted, warning } : { deleted }); } catch (e) { ctx.statusCode = 500; ctx.set('content-type', 'application/json'); ctx.body = JSON.stringify({ error: e?.message || 'purge_failed' }); } } - diff --git a/test/index.js b/test/index.js index ccc8b8a..834ffaf 100644 --- a/test/index.js +++ b/test/index.js @@ -198,6 +198,37 @@ describe('Allow/Deny URL Patterns', function () { assert.strictEqual(res.status, 403); assert.strictEqual(res.headers['x-osham-cache'], 'denied'); }); + + // Trailing-slash edge cases + it('Should allow a path with a trailing slash that matches /employees/**', async function () { + // /restricted/employees/ → pathToCall=employees/ → normalised to /employees → matches /employees/** + const res = await client.get('/restricted/employees/'); + assert.notStrictEqual(res.status, 403, 'trailing-slash path should be allowed, not 403'); + }); + + it('Should deny a path with a trailing slash that would only match deny pattern', async function () { + // /restricted/employees/private/ → normalised → /employees/private → deny /employees/private/** + const res = await client.get('/restricted/employees/private/'); + assert.strictEqual(res.status, 403); + assert.strictEqual(res.headers['x-osham-cache'], 'denied'); + }); + + // Single-star depth enforcement: /employee/* must NOT match paths with more than one segment. + it('Should deny a deep path that only single-star allow pattern exists for', async function () { + // /restricted/employee/5/sub → pathToCall=employee/5/sub → /employee/5/sub + // Does not match /employees/** (different prefix) and does not match /employee/* (too deep) + const res = await client.get('/restricted/employee/5/sub'); + assert.strictEqual(res.status, 403); + assert.strictEqual(res.headers['x-osham-cache'], 'denied'); + }); + + // Nested precedence: deny deeper path wins even if parent allow matches. + it('Should deny a nested deny path even when a parent glob allow matches', async function () { + // /employees/** allow matches /employees/admin/data, but /employees/private/** deny also matches + const res = await client.get('/restricted/employees/private/admin/data'); + assert.strictEqual(res.status, 403); + assert.strictEqual(res.headers['x-osham-cache'], 'denied'); + }); }); describe('Startup Validation', function () { @@ -459,6 +490,102 @@ describe('Config Validation', function () { }, ); }); + + it('Should warn on unknown keys in a namespace', function () { + // eslint-disable-next-line @typescript-eslint/no-var-requires + const { validateConfig } = require('../lib/config.reader'); + const warnings = []; + const originalWarn = console.warn; + console.warn = (...args) => warnings.push(args.join(' ')); + try { + validateConfig({ + version: '1', + myNs: { expose: '/api/*', target: 'http://localhost:3000', unknownField: 'oops', anotherBad: 42 }, + }); + } finally { + console.warn = originalWarn; + } + assert.ok( + warnings.some(w => w.includes('unknown key') && w.includes('"unknownField"')), + 'should warn about unknownField', + ); + assert.ok( + warnings.some(w => w.includes('unknown key') && w.includes('"anotherBad"')), + 'should warn about anotherBad', + ); + }); + + it('Should not warn on known namespace keys', function () { + // eslint-disable-next-line @typescript-eslint/no-var-requires + const { validateConfig } = require('../lib/config.reader'); + const warnings = []; + const originalWarn = console.warn; + console.warn = (...args) => warnings.push(args.join(' ')); + try { + validateConfig({ + version: '1', + myNs: { + expose: '/api/*', + target: 'http://localhost:3000', + cache: { expires: '10s' }, + allow: ['/foo/**'], + deny: ['/foo/private/**'], + }, + }); + } finally { + console.warn = originalWarn; + } + assert.strictEqual(warnings.length, 0, 'should have no warnings for known keys'); + }); +}); + +describe('Purge Safety', function () { + this.timeout(5000); + + it('Should include a warning when purging with a broad pattern (no namespace prefix)', async function () { + const res = await client.post('/__osham/purge?pattern=*').expect(200); + const body = JSON.parse(res.text); + assert.ok(body.warning, 'should return a warning for a broad pattern'); + assert.ok(body.warning.includes('O:'), 'warning should mention O: prefix'); + }); + + it('Should include a warning when purging with a bare ** pattern', async function () { + const res = await client.post('/__osham/purge?pattern=**').expect(200); + const body = JSON.parse(res.text); + assert.ok(body.warning, 'should return a warning for ** pattern'); + }); + + it('Should NOT include a warning when purging with a namespaced pattern', async function () { + const res = await client.post('/__osham/purge?pattern=O:dummyRest:/api/v1/*').expect(200); + const body = JSON.parse(res.text); + assert.ok(!body.warning, 'should not warn for a properly prefixed pattern'); + }); + + it('Should return 401 when OSHAM_PURGE_SECRET is set but header is missing', async function () { + process.env.OSHAM_PURGE_SECRET = 'test-secret-abc'; + try { + const res = await client.post('/__osham/purge?pattern=O:dummyRest:*'); + assert.strictEqual(res.status, 401); + const body = JSON.parse(res.text); + assert.ok(body.error.includes('Unauthorized'), 'should return unauthorized error'); + } finally { + delete process.env.OSHAM_PURGE_SECRET; + } + }); + + it('Should allow purge when OSHAM_PURGE_SECRET matches the header', async function () { + process.env.OSHAM_PURGE_SECRET = 'test-secret-abc'; + try { + const res = await client + .post('/__osham/purge?pattern=O:dummyRest:/api/v1/*') + .set('x-osham-purge-secret', 'test-secret-abc') + .expect(200); + const body = JSON.parse(res.text); + assert.strictEqual(typeof body.deleted, 'number', 'should return deleted count'); + } finally { + delete process.env.OSHAM_PURGE_SECRET; + } + }); }); describe('Specifications', function () { From c92812865418741c40d4466c8e2cb34c55feadf4 Mon Sep 17 00:00:00 2001 From: Ajay Singh Date: Mon, 23 Mar 2026 10:09:14 +0000 Subject: [PATCH 03/50] feat: add top-level unknown-key warnings and expand purge docs - config.reader: add KNOWN_TOP_LEVEL_KEYS set and warn on unknown top-level scalar keys (e.g. 'purges: true' as a typo for 'purge: true') instead of silently treating them as namespace candidates and throwing a confusing error - config.reader: filter namespaceKeys to only object-valued non-reserved keys so typo scalars are skipped cleanly after the warning - test: add two new Config Validation tests covering top-level unknown key warnings and the absence of warnings on all known top-level keys - docs/purge-cache.md: document OSHAM_PURGE_SECRET authentication, explain the broad-pattern warning, and expand the security guidance section Co-Authored-By: Claude Sonnet 4.6 --- docs/purge-cache.md | 28 ++++++++++++++++++++++++- src/config.reader.ts | 25 +++++++++++++++++++++- test/index.js | 49 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 100 insertions(+), 2 deletions(-) diff --git a/docs/purge-cache.md b/docs/purge-cache.md index 9e63166..7fe2234 100644 --- a/docs/purge-cache.md +++ b/docs/purge-cache.md @@ -59,10 +59,36 @@ Osham delegates pattern matching to the `minimatch` library which implements she - **In-memory store (`MemStore`)**: purge by pattern iterates keys and removes matches. This operation runs in-process and is immediate. - **Redis store**: If the storage driver exposes `purgeByPattern`, it will be used. Otherwise Osham falls back to `SCAN` + batch `DEL` to avoid blocking Redis. +## Authentication + +Osham supports a lightweight shared-secret mechanism to protect the purge endpoint. Set the `OSHAM_PURGE_SECRET` environment variable to a secret value; callers must then supply the same value in the `x-osham-purge-secret` request header, or the request is rejected with a `401 Unauthorized` response. + +```sh +# Start Osham with a purge secret: +OSHAM_PURGE_SECRET=my-secret-token osham + +# Purge with the secret header: +curl -X POST 'http://localhost:26192/__osham/purge?pattern=O:dummyRest:/api/v1/employees**' \ + -H 'x-osham-purge-secret: my-secret-token' +``` + +If `OSHAM_PURGE_SECRET` is not set, no authentication is required — appropriate for trusted internal networks only. + +## Broad-pattern warnings + +Patterns that do not start with `O:` (the namespace prefix) may accidentally match cache keys across all namespaces. When such a pattern is used, Osham returns a `warning` field in the response body alongside the `deleted` count: + +```json +{ "deleted": 3, "warning": "Pattern may match keys across all namespaces. Use the \"O::\" prefix ..." } +``` + +Always prefer namespaced patterns like `O:myNs:/api/v1/users*` over bare globs like `*` or `**`. + ## Security and safety -- Restrict access to the purge endpoint — it can invalidate a lot of cached data. Use firewall rules or authentication in front of the endpoint. +- Restrict access to the purge endpoint — it can invalidate a lot of cached data. Use `OSHAM_PURGE_SECRET` and/or firewall rules to prevent unauthorized purges. - Consider logging purges and rate-limiting purge requests in production. +- The purge endpoint accepts `POST` and `DELETE` methods; other methods return `405 Method Not Allowed`. ## Examples and tips diff --git a/src/config.reader.ts b/src/config.reader.ts index e5b1322..6b9682c 100644 --- a/src/config.reader.ts +++ b/src/config.reader.ts @@ -15,6 +15,8 @@ function supplant(o = {}) { const RESERVED_KEYS = new Set(['version', 'xResponseTime', 'health', 'purge', 'metrics', 'changeOrigin']); +const KNOWN_TOP_LEVEL_KEYS = new Set(['version', 'xResponseTime', 'health', 'purge', 'metrics', 'changeOrigin']); + const KNOWN_NAMESPACE_KEYS = new Set([ 'expose', 'target', @@ -88,7 +90,28 @@ export function validateConfig(config: unknown): IFullConfig { } } - const namespaceKeys = Object.keys(cfg).filter(k => !RESERVED_KEYS.has(k)); + // Warn about unknown top-level scalar keys — these are almost always typos of reserved flags + // (e.g. `purges: true` instead of `purge: true`). Object-valued keys are namespace candidates + // and will be validated below. + for (const key of Object.keys(cfg)) { + if ( + !KNOWN_TOP_LEVEL_KEYS.has(key) && + (typeof cfg[key] !== 'object' || cfg[key] === null || Array.isArray(cfg[key])) + ) { + // eslint-disable-next-line no-console + console.warn( + `cache-config.yml: unknown top-level key "${key}" with a non-object value will be ignored` + + ` — known top-level keys: ${[...KNOWN_TOP_LEVEL_KEYS].join(', ')}` + + ` (if this is a namespace, its value must be an object with "expose" and "target" fields)`, + ); + } + } + + // Only object-valued non-reserved keys are treated as namespace candidates. + // Scalar non-reserved keys have already been warned about above and are skipped here. + const namespaceKeys = Object.keys(cfg).filter( + k => !RESERVED_KEYS.has(k) && typeof cfg[k] === 'object' && cfg[k] !== null && !Array.isArray(cfg[k]), + ); if (namespaceKeys.length === 0) { throw new Error('cache-config.yml: at least one proxy namespace must be defined with "expose" and "target" fields'); } diff --git a/test/index.js b/test/index.js index 834ffaf..3febb0c 100644 --- a/test/index.js +++ b/test/index.js @@ -515,6 +515,55 @@ describe('Config Validation', function () { ); }); + it('Should warn on unknown top-level scalar keys (likely typos of global flags)', function () { + // eslint-disable-next-line @typescript-eslint/no-var-requires + const { validateConfig } = require('../lib/config.reader'); + const warnings = []; + const originalWarn = console.warn; + console.warn = (...args) => warnings.push(args.join(' ')); + try { + validateConfig({ + version: '1', + purges: true, // typo of 'purge' + healthCheck: false, // typo of 'health' + myNs: { expose: '/api/*', target: 'http://localhost:3000' }, + }); + } finally { + console.warn = originalWarn; + } + assert.ok( + warnings.some(w => w.includes('unknown top-level key') && w.includes('"purges"')), + 'should warn about unknown top-level scalar key "purges"', + ); + assert.ok( + warnings.some(w => w.includes('unknown top-level key') && w.includes('"healthCheck"')), + 'should warn about unknown top-level scalar key "healthCheck"', + ); + }); + + it('Should not warn on known top-level keys or namespace objects', function () { + // eslint-disable-next-line @typescript-eslint/no-var-requires + const { validateConfig } = require('../lib/config.reader'); + const warnings = []; + const originalWarn = console.warn; + console.warn = (...args) => warnings.push(args.join(' ')); + try { + validateConfig({ + version: '1', + xResponseTime: true, + health: true, + purge: true, + metrics: true, + changeOrigin: false, + myNs: { expose: '/api/*', target: 'http://localhost:3000' }, + }); + } finally { + console.warn = originalWarn; + } + const topLevelWarnings = warnings.filter(w => w.includes('unknown top-level key')); + assert.strictEqual(topLevelWarnings.length, 0, 'should have no top-level unknown key warnings for known keys'); + }); + it('Should not warn on known namespace keys', function () { // eslint-disable-next-line @typescript-eslint/no-var-requires const { validateConfig } = require('../lib/config.reader'); From 1bf1a721a12dd7e0556e910569d2f18a8ed5c89b Mon Sep 17 00:00:00 2001 From: Ajay Singh Date: Mon, 23 Mar 2026 13:08:25 +0000 Subject: [PATCH 04/50] docs: add admin UI implementation roadmap --- docs/ui-admin-implementation-roadmap.md | 515 ++++++++++++++++++++++++ 1 file changed, 515 insertions(+) create mode 100644 docs/ui-admin-implementation-roadmap.md diff --git a/docs/ui-admin-implementation-roadmap.md b/docs/ui-admin-implementation-roadmap.md new file mode 100644 index 0000000..9049126 --- /dev/null +++ b/docs/ui-admin-implementation-roadmap.md @@ -0,0 +1,515 @@ +# Osham Admin UI Implementation Roadmap + +## Goal +Build a web-based admin UI for Osham so users can: +- view and update config safely +- manage namespace/domain-level behavior from the UI +- validate config before applying it +- view health and metrics +- perform operational actions like purge with safeguards + +This roadmap breaks the work into backend and frontend deliverables so it can be implemented incrementally. + +--- + +## Product Scope + +### In scope +- Config read/update from UI +- Namespace/domain-level config management +- Global settings management +- Config validation and reload/apply workflow +- Metrics and health visibility +- Purge management with safety warnings +- Basic authentication/authorization for admin routes +- Auditability for config and purge actions + +### Out of scope for MVP +- Full RBAC with multiple roles +- Multi-environment management +- Advanced collaboration/locking +- Full config history rollback UI +- SSO/OAuth enterprise auth + +--- + +# Phase 0 — Design Foundations + +## Backend tasks +1. **Define admin API contract** + - Create a design doc for all `/__osham/admin/*` endpoints + - Define request/response schemas + - Define validation error/warning format + - Define auth model for admin endpoints + +2. **Define structured config response model** + - Standardize the config shape returned to the UI + - Reuse the internal structured config model (`globalConfig`, `namespaces`) + - Mark secret-backed fields as masked/non-readable + +3. **Define operational safety rules** + - Which changes require reload? + - Whether save and apply are the same or separate actions + - What constitutes a dangerous purge pattern? + - What actions should be logged? + +## Frontend tasks +1. **Create UX wireframe / screen map** + - Dashboard + - Namespaces list + - Namespace editor + - Global settings + - Metrics + - Health / diagnostics + - Purge tools + - Audit log + +2. **Define frontend data model** + - Match backend structured config model + - Support form editing + optional advanced/raw editor later + +## Deliverables +- Admin API design doc +- Validation response schema +- Screen map / UX outline + +--- + +# Phase 1 — Backend MVP: Config API + +## Objective +Expose safe admin endpoints so a UI can read, validate, save, and apply config. + +## Backend tasks +1. **Add admin route namespace** + - `/__osham/admin/*` + - Protect all admin endpoints with auth + +2. **Implement config read endpoint** + - `GET /__osham/admin/config` + - Return structured config + - Mask secret-backed fields where needed + +3. **Implement config validate endpoint** + - `POST /__osham/admin/config/validate` + - Accept structured config payload + - Return: + - `valid` + - `errors[]` + - `warnings[]` + +4. **Implement config save endpoint** + - `PUT /__osham/admin/config` + - Save config in a safe format + - Prefer atomic file write + - Preserve formatting expectations if YAML is stored + +5. **Implement config reload/apply endpoint** + - `POST /__osham/admin/config/reload` + - Re-parse and apply config safely + - Return success/failure + warnings + +6. **Return structured validation output** + Example: + ```json + { + "valid": false, + "errors": [ + { "field": "namespaces.api.target", "message": "target is required" } + ], + "warnings": [ + { "field": "global.foo", "message": "unknown config key" } + ] + } + ``` + +7. **Add tests for admin config API** + - unauthorized access + - valid read + - invalid config validate + - valid save + - failed reload + +## Frontend tasks +- none required yet, but frontend can begin mocking these APIs + +## Deliverables +- Admin config endpoints +- Auth protection +- Validation response format +- Test coverage for config API + +--- + +# Phase 2 — Frontend MVP: Config Editor + +## Objective +Build the first usable admin UI for config management. + +## Frontend tasks +1. **Set up admin UI app** + Recommended stack: + - React + TypeScript + - Tailwind + - React Query or equivalent for data fetching + - React Hook Form / Zod or similar for forms + +2. **Build dashboard shell / navigation** + - Sidebar/top nav + - Screen routing + - auth gate if required + +3. **Build namespaces list page** + - Show namespace name, expose, target, cache enabled, status + - Actions: create, edit, clone, delete/disable + +4. **Build namespace editor form** + Fields: + - name + - expose + - target + - timeout + - cache on/off + - cache expiry + - cache headers/query variations + - allow list + - deny list + - rules + - pooling / namespace-level toggles + +5. **Build global settings page** + Fields: + - version + - metrics + - health + - purge + - metrics path + - secure mode visibility + - secret status (masked) + +6. **Build validate/save/apply workflow** + - Validate button + - Show errors/warnings inline + - Save draft + - Apply/reload config + - Success/failure toast + details + +7. **Optional advanced mode** + - Read-only raw YAML preview initially + - Editable raw mode only after structured editor is stable + +## Backend tasks +1. Ensure admin config endpoints are stable for UI use +2. Add helpful field-level error mapping if needed + +## Deliverables +- Working config editor UI +- Namespace CRUD (or create/edit/disable for MVP) +- Validate + apply flow + +--- + +# Phase 3 — Metrics and Health UI + +## Objective +Expose observability data in a human-friendly admin dashboard. + +## Backend tasks +1. **Add metrics summary endpoint** + - `GET /__osham/admin/metrics/summary` + - return totals and key counters + +2. **Add namespace metrics endpoint** + - `GET /__osham/admin/metrics/namespaces` + - per-namespace metrics summary + +3. **Optional timeseries endpoint** + - `GET /__osham/admin/metrics/timeseries` + - if metrics history exists or can be sampled + +4. **Add health/diagnostics endpoints** + - `GET /__osham/admin/health` + - `GET /__osham/admin/startup-summary` + - include warnings, feature flags, namespace counts, secure mode state + +## Frontend tasks +1. **Dashboard cards** + - total requests + - cache hits + - cache misses + - hit ratio + - cache size + - pooled requests + - number of active namespaces + +2. **Charts** + - requests over time + - hit/miss ratio + - latency distribution + - per-namespace traffic comparison + +3. **Health page** + - service status + - startup summary + - validation warnings + - feature flags + - Redis connectivity (if available) + +## Deliverables +- Metrics dashboard +- Health/diagnostics UI +- Per-namespace metrics visibility + +--- + +# Phase 4 — Purge and Operations + +## Objective +Add safe operational tooling. + +## Backend tasks +1. **Add purge admin endpoint** + - `POST /__osham/admin/purge` + +2. **Optional purge preview endpoint** + - `POST /__osham/admin/purge/preview` + - estimate what may be affected + +3. **Add purge warnings in response** + - broad wildcard warnings + - namespace-less pattern warnings + +4. **Add audit logging hooks** + Log: + - config save + - config apply/reload + - purge actions + - auth failures (if appropriate) + +## Frontend tasks +1. **Build purge tools page** + - purge by exact key + - purge by pattern + - clear warning banners for dangerous patterns + +2. **Add confirmation UX** + - second confirmation for broad purges + - show examples of risky patterns + +3. **Add audit log page** + - who changed what + - when + - result + +## Deliverables +- Purge UI with safety warnings +- Audit log visibility + +--- + +# Phase 5 — Hardening and Productization + +## Objective +Make the admin UI production-ready. + +## Backend tasks +1. **Config versioning** + - store snapshots of applied config + - allow rollback later + +2. **Concurrency protection** + - revision IDs / optimistic locking + - reject stale config writes + +3. **Expanded auth model** + - optional role separation later + - admin vs read-only operator + +4. **Import/export support** + - download current config + - import validated config + +5. **Unknown key / migration assistance** + - structured upgrade warnings + - deprecation notices + +## Frontend tasks +1. **Version history UI** +2. **Rollback UI** +3. **Read-only operator mode** +4. **Import/export controls** +5. **Better diff/review screen before apply** + +## Deliverables +- Safer multi-user operation +- Rollback path +- Better long-term maintainability + +--- + +# Recommended API Endpoints + +## Config +- `GET /__osham/admin/config` +- `POST /__osham/admin/config/validate` +- `PUT /__osham/admin/config` +- `POST /__osham/admin/config/reload` + +## Metrics +- `GET /__osham/admin/metrics/summary` +- `GET /__osham/admin/metrics/namespaces` +- `GET /__osham/admin/metrics/timeseries` (optional) + +## Health +- `GET /__osham/admin/health` +- `GET /__osham/admin/startup-summary` + +## Purge +- `POST /__osham/admin/purge` +- `POST /__osham/admin/purge/preview` (optional) + +## Audit +- `GET /__osham/admin/audit` + +--- + +# Suggested Technical Stack + +## Backend +- Existing Osham Node/TypeScript service +- Admin routes added to same service initially +- Reuse existing validation logic as source of truth + +## Frontend +- React + TypeScript +- Tailwind CSS +- React Query +- React Hook Form +- Chart library: Recharts / ECharts / Chart.js + +## Why this approach +- simpler deployment +- less moving pieces initially +- keeps config/metrics logic close to source of truth + +--- + +# Validation Rules Strategy + +## Backend is source of truth +The UI may do convenience validation, but final validation must happen in backend. + +## Validation should return +- field path +- message +- severity (`error` / `warning`) +- optional code + +## Examples +- missing target +- invalid expose path +- unknown namespace key +- dangerous purge configuration +- duplicate namespace names + +--- + +# Security Requirements + +## Minimum +- auth on all admin endpoints +- HTTPS in production +- do not expose secrets in plaintext +- audit log for sensitive operations +- confirmation for destructive/broad actions + +## Secret handling +- show configured/not configured +- allow replace/reset +- do not reveal actual secret values after save + +--- + +# MVP Recommendation + +If building in the shortest sensible path, ship this first: + +## Backend MVP +- config read +- config validate +- config save +- config reload +- health summary +- metrics summary +- purge endpoint with warnings + +## Frontend MVP +- login/admin protection +- dashboard shell +- namespaces list +- namespace editor +- global settings page +- validate/apply flow +- basic metrics dashboard +- purge page with confirmations + +--- + +# Suggested Task Breakdown for Agents + +## Backend task bundle 1 +- admin route scaffold +- auth middleware +- config read/validate/save/reload endpoints +- tests + +## Frontend task bundle 1 +- React admin shell +- navigation +- namespaces list +- namespace editor form + +## Backend task bundle 2 +- metrics summary + health endpoints +- startup summary endpoint +- tests + +## Frontend task bundle 2 +- metrics dashboard +- health page + +## Backend task bundle 3 +- purge admin endpoints +- warnings + audit hooks + +## Frontend task bundle 3 +- purge UI +- audit log UI + +--- + +# Best Next Step + +Start with a new doc: +**`docs/admin-api-design.md`** + +That should define: +- endpoint specs +- request/response payloads +- auth model +- validation shape +- apply/reload semantics + +Then implement backend config APIs before starting UI coding. + +--- + +# Success Criteria + +The admin UI effort is successful when a user can: +- create/edit a namespace from the browser +- validate config before saving +- safely apply config without manual YAML edits +- inspect metrics and health for each namespace +- perform purge actions with appropriate warnings +- understand what changed through logs/audit history From ade5476925cc7cdf0c21c14a7d849015451a452b Mon Sep 17 00:00:00 2001 From: Ajay Singh Date: Mon, 23 Mar 2026 13:11:10 +0000 Subject: [PATCH 05/50] docs: add admin API design and agent task list --- docs/admin-api-design.md | 622 +++++++++++++++++++++++++++++++ docs/admin-ui-agent-task-list.md | 374 +++++++++++++++++++ 2 files changed, 996 insertions(+) create mode 100644 docs/admin-api-design.md create mode 100644 docs/admin-ui-agent-task-list.md diff --git a/docs/admin-api-design.md b/docs/admin-api-design.md new file mode 100644 index 0000000..5eea89c --- /dev/null +++ b/docs/admin-api-design.md @@ -0,0 +1,622 @@ +# Osham Admin API Design + +## Purpose +Define the backend API contract for the Osham Admin UI so users can safely: +- view config +- validate config +- save/apply config +- view metrics and health +- perform purge actions +- inspect audit history + +This document is intentionally implementation-oriented so coding agents can build against it directly. + +--- + +## Design Principles + +1. **Backend is source of truth** + - UI convenience validation is fine, but backend validation decides correctness. + +2. **Safe-by-default operations** + - config changes validate before apply + - purge actions return warnings for dangerous patterns + - secrets are masked or write-only + +3. **Structured responses** + - all endpoints return machine-friendly shapes + - validation returns field-level errors and warnings + +4. **Operational clarity** + - endpoints should expose startup summary, health, and metrics in a UI-friendly shape + +5. **Incremental buildability** + - implement config endpoints first + - metrics/health/purge/audit can follow in phases + +--- + +## Base Route + +All admin endpoints live under: + +```http +/__osham/admin +``` + +--- + +## Authentication Model + +## MVP option +Use a shared admin secret header. + +### Request header +```http +x-osham-admin-secret: +``` + +### Behavior +- If admin auth is enabled and header is missing/invalid → `401 Unauthorized` +- If auth is disabled in dev/test, allow local access only if explicitly configured + +## Recommended config/env +- `OSHAM_ADMIN_SECRET` +- `OSHAM_ADMIN_ALLOW_INSECURE_LOCAL=false` + +## Future option +- session auth / JWT / OAuth / SSO + +--- + +## Common Response Shapes + +## Success response +```json +{ + "ok": true, + "data": {} +} +``` + +## Error response +```json +{ + "ok": false, + "error": { + "code": "VALIDATION_FAILED", + "message": "Config validation failed" + } +} +``` + +## Validation response +```json +{ + "ok": true, + "data": { + "valid": false, + "errors": [ + { + "field": "namespaces.api.target", + "message": "target is required", + "severity": "error", + "code": "REQUIRED_FIELD" + } + ], + "warnings": [ + { + "field": "global.helth", + "message": "Unknown top-level key; did you mean 'health'?", + "severity": "warning", + "code": "UNKNOWN_KEY" + } + ] + } +} +``` + +--- + +## Config Data Model + +## Structured config returned to UI +```ts +type AdminConfigResponse = { + globalConfig: { + version: string + health?: boolean + metrics?: boolean + purge?: boolean + xResponseTime?: boolean + metricsPath?: string + secure?: { + enabled: boolean + sslKeyConfigured: boolean + sslCertConfigured: boolean + } + } + namespaces: Record + meta: { + source: string + lastLoadedAt?: string + lastAppliedAt?: string + revision?: string + } +} +``` + +## Namespace model +```ts +type NamespaceConfig = { + expose: string + target: string + port?: number + timeout?: number + allow?: string[] + deny?: string[] + cache?: false | { + expire?: string + query?: false | string[] + headers?: false | string[] + } + rules?: Array<{ + pattern?: string + cache?: false | { + expire?: string + query?: false | string[] + headers?: false | string[] + } + }> +} +``` + +### Secret handling +The following must **not** be returned in plaintext: +- admin secret +- purge secret +- SSL private key contents +- certificate contents + +Expose only status/metadata, such as: +- configured / not configured +- path exists / missing + +--- + +# Endpoints + +## 1. Get current config + +### `GET /__osham/admin/config` + +Returns the currently loaded structured config. + +### Response +```json +{ + "ok": true, + "data": { + "globalConfig": { + "version": "1", + "health": true, + "metrics": true, + "purge": true + }, + "namespaces": { + "api": { + "expose": "/api/*", + "target": "http://localhost:3000", + "allow": ["/employees/**"], + "deny": ["/employees/private/**"] + } + }, + "meta": { + "source": "cache-config.yml", + "lastAppliedAt": "2026-03-23T13:00:00Z", + "revision": "abc123" + } + } +} +``` + +--- + +## 2. Validate config + +### `POST /__osham/admin/config/validate` + +Validates a proposed config payload without saving or applying it. + +### Request +```json +{ + "config": { + "globalConfig": { + "version": "1", + "health": true, + "metrics": true, + "purge": true + }, + "namespaces": { + "api": { + "expose": "/api/*", + "target": "http://localhost:3000" + } + } + } +} +``` + +### Response +```json +{ + "ok": true, + "data": { + "valid": true, + "errors": [], + "warnings": [] + } +} +``` + +### Validation expectations +Should detect: +- missing required fields +- invalid types +- invalid allow/deny shapes +- unknown keys +- dangerous purge-related config warnings +- invalid rule/cache structure + +--- + +## 3. Save config + +### `PUT /__osham/admin/config` + +Saves config to the configured config source but does **not necessarily apply it** unless explicitly designed to do so. + +## Recommendation +Keep save and apply as separate actions. + +### Request +```json +{ + "config": { "...": "..." }, + "expectedRevision": "abc123" +} +``` + +### Response +```json +{ + "ok": true, + "data": { + "saved": true, + "revision": "abc124", + "warnings": [] + } +} +``` + +### Notes +- use atomic file write +- reject stale writes if revision mismatch is implemented + +--- + +## 4. Apply / reload config + +### `POST /__osham/admin/config/reload` + +Reloads config from source and applies it to the running service. + +### Request +```json +{ + "expectedRevision": "abc124" +} +``` + +### Response +```json +{ + "ok": true, + "data": { + "applied": true, + "revision": "abc124", + "warnings": [], + "summary": { + "namespaceCount": 3, + "features": { + "health": true, + "metrics": true, + "purge": true + } + } + } +} +``` + +### Failure example +```json +{ + "ok": false, + "error": { + "code": "APPLY_FAILED", + "message": "Config could not be applied" + }, + "details": { + "errors": [ + { + "field": "namespaces.api.target", + "message": "target is invalid" + } + ] + } +} +``` + +--- + +## 5. Health summary + +### `GET /__osham/admin/health` + +Returns service health in a UI-friendly format. + +### Response +```json +{ + "ok": true, + "data": { + "status": "ok", + "uptimeSeconds": 12345, + "redis": { + "status": "ok" + }, + "config": { + "loaded": true, + "revision": "abc124" + } + } +} +``` + +--- + +## 6. Startup summary + +### `GET /__osham/admin/startup-summary` + +Returns the same important runtime summary the server logs on startup. + +### Response +```json +{ + "ok": true, + "data": { + "version": "1.0.2", + "namespaceCount": 2, + "namespaces": [ + { + "name": "api", + "expose": "/api/*", + "target": "http://localhost:3000", + "cache": { + "enabled": true, + "expire": "10s" + }, + "allow": ["/employees/**"], + "deny": ["/employees/private/**"] + } + ], + "features": { + "health": true, + "metrics": true, + "purge": true, + "xResponseTime": true, + "secureMode": false + }, + "warnings": [] + } +} +``` + +--- + +## 7. Metrics summary + +### `GET /__osham/admin/metrics/summary` + +Returns top-level metric totals for dashboard cards. + +### Response +```json +{ + "ok": true, + "data": { + "requests": 10000, + "cacheHits": 8200, + "cacheMisses": 1800, + "hitRatio": 0.82, + "pooledRequests": 3, + "cacheSizeBytes": 1200340 + } +} +``` + +--- + +## 8. Namespace metrics + +### `GET /__osham/admin/metrics/namespaces` + +Returns per-namespace metrics. + +### Response +```json +{ + "ok": true, + "data": [ + { + "namespace": "api", + "requests": 4000, + "cacheHits": 3500, + "cacheMisses": 500, + "hitRatio": 0.875, + "cacheSizeBytes": 450000, + "pooledRequests": 1, + "latency": { + "p50": 0.012, + "p95": 0.082 + } + } + ] +} +``` + +--- + +## 9. Purge endpoint + +### `POST /__osham/admin/purge` + +Performs a purge with safety warnings. + +### Request +```json +{ + "pattern": "api:/employees/*", + "dryRun": false +} +``` + +### Response +```json +{ + "ok": true, + "data": { + "purged": true, + "warnings": [ + "Pattern is broad and may affect multiple keys" + ] + } +} +``` + +### Safety behavior +- require purge secret/admin auth as appropriate +- warn on: + - `**` + - namespace-less broad patterns + - patterns likely to purge many keys +- optional `dryRun` support later + +--- + +## 10. Audit log + +### `GET /__osham/admin/audit` + +Returns recent admin actions. + +### Response +```json +{ + "ok": true, + "data": [ + { + "time": "2026-03-23T13:05:00Z", + "action": "config.apply", + "actor": "admin", + "result": "success", + "details": { + "revision": "abc124" + } + } + ] +} +``` + +### MVP note +This can initially be in-memory or file-based if no durable event store exists. + +--- + +## Error Codes + +Recommended codes: +- `UNAUTHORIZED` +- `VALIDATION_FAILED` +- `SAVE_FAILED` +- `APPLY_FAILED` +- `REVISION_CONFLICT` +- `PURGE_DENIED` +- `NOT_FOUND` +- `INTERNAL_ERROR` + +--- + +## Apply/Save Semantics + +## Recommended behavior +- **Validate**: checks payload only +- **Save**: persists payload to source of truth +- **Reload/Apply**: applies saved config to running service + +This separation is safer for UI workflows and allows future review/diff screens. + +--- + +## Concurrency / Revision Handling + +## MVP +- optional revision token in config metadata + +## Recommended +- attach `revision` to `GET /config` +- require `expectedRevision` for `PUT /config` +- reject stale writes with `409 REVISION_CONFLICT` + +--- + +## Testing Requirements + +### Admin API tests should cover +- unauthorized access +- valid config fetch +- invalid config validate response shape +- successful save +- failed save +- successful reload/apply +- failed reload/apply +- purge safety warnings +- startup summary response +- metrics summary response + +--- + +## Implementation Order + +1. `GET /config` +2. `POST /config/validate` +3. `PUT /config` +4. `POST /config/reload` +5. `GET /health` +6. `GET /startup-summary` +7. `GET /metrics/summary` +8. `GET /metrics/namespaces` +9. `POST /purge` +10. `GET /audit` + +--- + +## Best Next Step + +Use this doc to implement the first backend milestone: +- admin route scaffold +- auth middleware +- config read/validate/save/reload endpoints +- tests for those endpoints diff --git a/docs/admin-ui-agent-task-list.md b/docs/admin-ui-agent-task-list.md new file mode 100644 index 0000000..e19ebcf --- /dev/null +++ b/docs/admin-ui-agent-task-list.md @@ -0,0 +1,374 @@ +# Osham Admin UI Agent Task List + +This file breaks the Admin UI roadmap into concrete task bundles that can be handed to Claude, Codex, or other coding agents. + +--- + +## Priority Order + +1. Admin API foundation +2. Config editor backend +3. Config editor frontend +4. Metrics + health backend/frontend +5. Purge + audit features +6. Hardening / versioning / rollback + +--- + +# Task Bundle 1 — Admin API Foundation (Backend) + +## Goal +Create the initial admin route scaffold and authentication layer. + +## Scope +- Work in `osham/` +- Add `/__osham/admin/*` route foundation +- Add admin auth middleware +- Add tests for unauthorized access + +## Required work +- Add admin route registration +- Add admin auth check via header/env-based secret +- Add common success/error response helpers if useful +- Document required env vars briefly + +## Deliverables +- admin route scaffold +- auth middleware +- tests for 401 behavior +- docs update if needed + +## Verification +- lint +- build +- test + +--- + +# Task Bundle 2 — Config Read/Validate/Save/Reload API (Backend) + +## Goal +Expose the first usable config management API for the UI. + +## Scope +Implement: +- `GET /__osham/admin/config` +- `POST /__osham/admin/config/validate` +- `PUT /__osham/admin/config` +- `POST /__osham/admin/config/reload` + +## Required work +- Return structured config model +- Mask secret-backed fields +- Use current validation logic as source of truth +- Return structured validation errors/warnings +- Use atomic file writes for save if config file is updated +- Define apply semantics clearly + +## Deliverables +- working config endpoints +- structured validation response +- tests for valid/invalid cases + +## Verification +- lint +- build +- test + +--- + +# Task Bundle 3 — Startup Summary + Health API (Backend) + +## Goal +Provide dashboard-ready operational state. + +## Scope +Implement: +- `GET /__osham/admin/health` +- `GET /__osham/admin/startup-summary` + +## Required work +- expose current health state +- expose startup summary in structured form +- include enabled features and namespace info +- include warnings where available + +## Deliverables +- health endpoint +- startup summary endpoint +- tests + +## Verification +- lint +- build +- test + +--- + +# Task Bundle 4 — Metrics Summary API (Backend) + +## Goal +Make metrics UI-consumable. + +## Scope +Implement: +- `GET /__osham/admin/metrics/summary` +- `GET /__osham/admin/metrics/namespaces` + +## Required work +- convert existing metrics into dashboard-friendly JSON +- include cache hits, misses, hit ratio, pooled requests, cache size +- include per-namespace summaries if possible + +## Deliverables +- metrics summary endpoints +- tests +- docs update if endpoint behavior needs explanation + +## Verification +- lint +- build +- test + +--- + +# Task Bundle 5 — Purge Admin API + Safety (Backend) + +## Goal +Add safe operational purge controls for UI use. + +## Scope +Implement: +- `POST /__osham/admin/purge` +- optional purge preview support later + +## Required work +- validate purge request payload +- return warnings for broad/dangerous patterns +- enforce auth/secret checks consistently +- optionally include dry-run support if easy and safe + +## Deliverables +- purge admin endpoint +- tests for warnings and auth +- docs update + +## Verification +- lint +- build +- test + +--- + +# Task Bundle 6 — Audit Logging (Backend) + +## Goal +Track sensitive admin actions. + +## Scope +- log config save/apply operations +- log purge actions +- expose `GET /__osham/admin/audit` + +## Required work +- choose simple initial storage (in-memory/file-backed) +- define audit event shape +- return recent history + +## Deliverables +- audit event capture +- audit endpoint +- tests + +## Verification +- lint +- build +- test + +--- + +# Task Bundle 7 — Admin UI Shell (Frontend) + +## Goal +Create the frontend skeleton. + +## Suggested stack +- React + TypeScript +- Tailwind +- React Query +- React Hook Form + +## Scope +- project scaffold for UI +- nav/layout +- route structure for dashboard/config/metrics/health/purge +- API client layer + +## Deliverables +- frontend shell +- route scaffolding +- API service layer + +## Verification +- build +- lint + +--- + +# Task Bundle 8 — Namespace Config Editor (Frontend) + +## Goal +Let users edit namespace/domain-level config from UI. + +## Scope +- namespace list page +- namespace editor form +- create/edit/clone/disable flows +- validate/save/apply flow integration + +## Required work +- inline field validation +- render backend warnings/errors clearly +- preserve advanced fields like allow/deny/cache/rules + +## Deliverables +- namespace config UI +- save/apply UX + +## Verification +- build +- lint +- component/integration tests if available + +--- + +# Task Bundle 9 — Global Settings UI (Frontend) + +## Goal +Edit top-level Osham config from UI. + +## Scope +- health/metrics/purge toggles +- version display/edit if appropriate +- secure mode visibility +- masked secret status + +## Deliverables +- global settings page +- validation/error rendering + +## Verification +- build +- lint + +--- + +# Task Bundle 10 — Metrics + Health UI (Frontend) + +## Goal +Show observability clearly. + +## Scope +- dashboard cards +- namespace metrics table +- charts for hit/miss/latency if available +- health and startup summary pages + +## Deliverables +- dashboard +- metrics page +- health page + +## Verification +- build +- lint + +--- + +# Task Bundle 11 — Purge + Audit UI (Frontend) + +## Goal +Add safe operational UI controls. + +## Scope +- purge form +- warning banners for broad patterns +- confirmations +- audit log viewer + +## Deliverables +- purge UI +- audit log UI + +## Verification +- build +- lint + +--- + +# Task Bundle 12 — Hardening / Productization + +## Goal +Make the admin UI safer for real-world use. + +## Scope +- revision/conflict handling +- config versioning +- rollback support +- import/export +- read-only mode + +## Deliverables +- revision-aware save/apply flow +- version history primitives +- docs updates + +--- + +## Recommended Handoff Order for Agents + +### First handoff +Give an agent **Task Bundle 1 + 2** together: +- admin API foundation +- config read/validate/save/reload + +### Second handoff +Then assign **Task Bundle 3 + 4**: +- health/startup summary +- metrics summary endpoints + +### Third handoff +Then assign **Task Bundle 5 + 6**: +- purge admin API +- audit logging + +### Fourth handoff +Start frontend with **Task Bundle 7 + 8**: +- UI shell +- namespace config editor + +--- + +## Suggested instruction for coding agents + +When handing off a bundle, require this output: + +### Summary +- ... + +### Files Changed +- ... + +### Verification +- lint: pass/fail/not run +- build: pass/fail/not run +- test: pass/fail/not run + +### Commit +- `` - `` + +### Remaining Work +- ... + +### Blockers +- ... From 63dd13348199bf8b65d52160c85d0cb2fb8f8005 Mon Sep 17 00:00:00 2001 From: Ajay Singh Date: Mon, 23 Mar 2026 13:12:20 +0000 Subject: [PATCH 06/50] docs: add admin UI PRD --- docs/admin-ui-prd.md | 406 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 406 insertions(+) create mode 100644 docs/admin-ui-prd.md diff --git a/docs/admin-ui-prd.md b/docs/admin-ui-prd.md new file mode 100644 index 0000000..6c0aab5 --- /dev/null +++ b/docs/admin-ui-prd.md @@ -0,0 +1,406 @@ +# Osham Admin UI PRD + +## Title +Osham Admin Console + +## Summary +Build a web-based admin console for Osham so operators can manage config, inspect metrics, monitor health, and perform operational actions like purge safely from a browser. + +The admin console should reduce manual YAML editing, improve confidence in config changes, and make Osham easier to operate in real deployments. + +--- + +## Problem Statement +Today, Osham is configured primarily through file-based configuration and operational knowledge. That works for technical users, but it creates friction: + +- config changes require manual editing +- invalid config can be harder to recover from +- metrics are available but not presented in a UI-friendly way +- operational actions like purge need safer, more discoverable workflows +- domain/namespace-level behavior is powerful but not easily manageable visually + +There is an opportunity to turn Osham into a more complete operational product by adding an admin console. + +--- + +## Product Vision +Osham Admin Console should let a user: +- view the currently active config +- create and edit namespace/domain-level config from a UI +- validate changes before saving +- safely apply config changes +- inspect health and metrics at both global and namespace levels +- perform purge actions with warnings and confirmation +- understand recent operational changes via audit visibility + +--- + +## Goals + +### Primary goals +1. **Config from UI** + - users can create/edit namespace and global config from the browser + +2. **Safe config lifecycle** + - validate before apply + - clear errors and warnings + - protect against invalid or stale changes + +3. **Operational visibility** + - dashboard for health and metrics + - namespace-level insights + +4. **Safe operations** + - purge with warnings and confirmation + - admin-only access + +### Secondary goals +- improve onboarding for new operators +- make Osham more suitable for managed/internal platform use +- create a path toward rollback/version history later + +--- + +## Non-Goals (Initial Versions) +- advanced RBAC with many user roles +- enterprise SSO/OAuth in MVP +- multi-cluster or multi-environment management +- full visual rollback/version diff in MVP +- highly customized dashboards in MVP + +--- + +## Target Users + +### 1. Developer/operator +A developer running Osham locally or in a team environment who wants easy config management and observability. + +### 2. Platform engineer +Someone operating Osham as infrastructure who needs safer config apply workflows and better visibility. + +### 3. API operator +A user managing multiple namespaces/domains who wants to tune caching, allow/deny, and routing behavior per namespace. + +--- + +## User Stories + +### Config management +- As an operator, I want to see the current Osham config in structured form. +- As an operator, I want to edit a namespace without manually editing YAML. +- As an operator, I want to validate config before applying it. +- As an operator, I want clear field-level errors when config is invalid. +- As an operator, I want to manage allow/deny lists from UI. + +### Observability +- As an operator, I want to see cache hits/misses and hit ratio. +- As an operator, I want to inspect namespace-level activity. +- As an operator, I want to see health and startup summary in one place. + +### Operations +- As an operator, I want to purge cache safely from the UI. +- As an operator, I want warnings before dangerous wildcard purges. +- As an operator, I want an audit trail of config changes and purge actions. + +--- + +## Functional Requirements + +## 1. Admin authentication +- All admin routes must be protected. +- MVP may use shared secret header/env-based auth. +- Future versions may support session/JWT/SSO auth. + +## 2. Config read API +- UI can fetch current structured config. +- Response includes global config, namespaces, and metadata. +- Secret values must not be returned in plaintext. + +## 3. Config validate API +- UI can validate a proposed config payload. +- Response includes structured errors and warnings. +- Validation covers required fields, types, allow/deny shape, unknown keys, and config semantics. + +## 4. Config save API +- UI can save config changes safely. +- Save should use atomic writes where applicable. +- Revision-aware save is recommended. + +## 5. Config reload/apply API +- UI can apply saved config to the running service. +- Apply result includes success/failure and warnings. + +## 6. Namespace editor +- UI supports create/edit/update for namespace config. +- Must support core fields: + - name + - expose + - target + - timeout + - cache settings + - allow / deny + - rules + +## 7. Global settings editor +- UI supports toggling/editing top-level settings such as: + - health + - metrics + - purge + - metrics path + - startup visibility metadata + +## 8. Metrics dashboard +- UI shows top-level metrics: + - requests + - cache hits/misses + - hit ratio + - pooled requests + - cache size +- UI shows namespace-level metrics where possible. + +## 9. Health and diagnostics +- UI shows health status. +- UI shows startup summary. +- UI shows config warnings where relevant. + +## 10. Purge tools +- UI supports purge by key/pattern. +- UI shows broad pattern warnings. +- UI requires confirmation for risky purges. + +## 11. Audit visibility +- UI shows recent config apply/save actions and purge actions. + +--- + +## UX / Screen Requirements + +### Dashboard +Show: +- service health +- feature flags enabled +- total namespaces +- requests / hits / misses / hit ratio +- quick links to config, metrics, purge + +### Namespaces screen +Show: +- namespace list +- expose path +- target +- cache status +- actions (edit, clone, disable, delete if supported) + +### Namespace editor screen +Show editable form fields for: +- name +- expose +- target +- timeout +- cache settings +- rules +- allow / deny +- advanced options as collapsible sections + +### Global settings screen +Show: +- health/metrics/purge toggles +- metrics path +- secure mode visibility/status +- masked secret status + +### Metrics screen +Show: +- dashboard cards +- namespace metrics table +- optional charts for traffic/hit ratio/latency + +### Health / diagnostics screen +Show: +- health result +- startup summary +- warnings +- config revision metadata + +### Purge screen +Show: +- pattern input +- safety warnings +- confirmation UX +- result feedback + +### Audit log screen +Show: +- recent actions +- timestamps +- actor/source +- outcome + +--- + +## Technical Approach + +## Architecture recommendation +Use a two-part architecture: +1. **Admin API inside Osham backend** +2. **React-based admin UI** + +This keeps the source of truth close to runtime behavior and avoids a separate backend service initially. + +## Why this approach +- lower deployment complexity +- easier reuse of existing validation logic +- direct access to metrics and startup info +- simpler long-term maintenance for MVP + +--- + +## API Scope +The following endpoints are expected in the admin API: + +### Config +- `GET /__osham/admin/config` +- `POST /__osham/admin/config/validate` +- `PUT /__osham/admin/config` +- `POST /__osham/admin/config/reload` + +### Metrics +- `GET /__osham/admin/metrics/summary` +- `GET /__osham/admin/metrics/namespaces` + +### Health +- `GET /__osham/admin/health` +- `GET /__osham/admin/startup-summary` + +### Purge +- `POST /__osham/admin/purge` + +### Audit +- `GET /__osham/admin/audit` + +Detailed contracts are specified in `docs/admin-api-design.md`. + +--- + +## Security Requirements +- admin endpoints require authentication +- secrets must not be exposed in plaintext +- purge/config operations should be audited +- risky/broad purges should return warnings and require confirmation +- HTTPS should be used in production + +--- + +## Validation Requirements +Backend validation is source of truth. + +Validation should support: +- required field checking +- type checking +- unknown-key warnings/errors +- allow/deny validation +- cache/rules validation +- clear field-path responses for UI rendering + +--- + +## MVP Definition + +### Backend MVP +- admin auth +- config read/validate/save/reload endpoints +- health summary endpoint +- metrics summary endpoint +- purge endpoint with warnings + +### Frontend MVP +- admin shell/navigation +- namespaces list +- namespace editor +- global settings editor +- validate/apply workflow +- basic metrics dashboard +- purge page with warnings + +MVP does **not** require: +- rollback UI +- multi-user collaboration controls +- advanced charting +- enterprise auth + +--- + +## Success Metrics +The Admin Console is successful if operators can: +- update namespace config without editing YAML manually +- validate and apply config safely +- understand cache/health state from the dashboard +- perform purge with appropriate safety guidance +- manage Osham faster and with fewer operator mistakes + +Possible measurable indicators: +- lower time to make config changes +- fewer invalid config startup failures +- faster diagnosis of cache/health issues +- fewer risky purge mistakes + +--- + +## Delivery Phases + +### Phase 1 +- admin API foundation +- config read/validate/save/reload + +### Phase 2 +- namespace/global config UI +- validate/save/apply UX + +### Phase 3 +- metrics and health UI + +### Phase 4 +- purge UI + audit visibility + +### Phase 5 +- hardening, revision control, rollback, read-only mode + +--- + +## Risks +- config reload safety +- stale writes / concurrent edits +- YAML persistence and formatting drift +- secret handling mistakes +- purge actions affecting too much data +- frontend/backend schema drift + +## Mitigations +- structured backend validation +- revision tokens for writes +- atomic saves +- secret masking +- warnings + confirmations +- audit logging + +--- + +## Dependencies +- existing structured config model in Osham +- validation logic already present in backend +- metrics support already present in Osham +- purge safeguards already in progress + +--- + +## Best Next Step +Start implementation with: +- **Task Bundle 1 + 2** from `docs/admin-ui-agent-task-list.md` + +That means: +- admin API scaffold +- admin auth middleware +- config read/validate/save/reload endpoints +- tests + +This gives the project its first real UI-enabling backend milestone. From 88fd345f6c25387a9a40de4c2546a279666418cf Mon Sep 17 00:00:00 2001 From: Ajay Singh Date: Mon, 23 Mar 2026 13:19:39 +0000 Subject: [PATCH 07/50] feat(admin): implement admin API foundation + config endpoints (bundles 1+2) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds /__osham/admin/* route foundation with shared-secret auth middleware (OSHAM_ADMIN_SECRET / x-osham-admin-secret header), plus four config endpoints: GET /__osham/admin/config – return structured IFullConfig + meta POST /__osham/admin/config/validate – validate proposed config, return errors/warnings PUT /__osham/admin/config – validate + atomically save to cache-config.yml POST /__osham/admin/config/reload – re-read file, update in-memory admin state New files: src/admin.state.ts – shared admin state (config, meta, revision) src/middlewares/adminConfig.ts – all admin route handlers Changes: src/config.reader.ts – add validateConfigCollecting() (collects errors/warnings, no throw) src/index.ts – initialise admin state at startup; mount AdminConfig middleware test/index.js – 14 new tests covering auth and all four config endpoints Co-Authored-By: Claude Sonnet 4.6 --- src/admin.state.ts | 28 +++ src/config.reader.ts | 51 ++++++ src/index.ts | 22 +++ src/middlewares/adminConfig.ts | 310 +++++++++++++++++++++++++++++++++ test/index.js | 245 ++++++++++++++++++++++++++ 5 files changed, 656 insertions(+) create mode 100644 src/admin.state.ts create mode 100644 src/middlewares/adminConfig.ts diff --git a/src/admin.state.ts b/src/admin.state.ts new file mode 100644 index 0000000..76b8d23 --- /dev/null +++ b/src/admin.state.ts @@ -0,0 +1,28 @@ +import { createHash } from 'crypto'; +import { IFullConfig } from './types'; + +export interface AdminMeta { + source: string; + lastLoadedAt: string; + lastAppliedAt: string | null; + revision: string; +} + +export interface AdminState { + config: IFullConfig; + meta: AdminMeta; +} + +let state: AdminState | null = null; + +export function getAdminState(): AdminState | null { + return state; +} + +export function setAdminState(config: IFullConfig, meta: AdminMeta): void { + state = { config, meta }; +} + +export function computeRevision(content: string): string { + return createHash('sha256').update(content).digest('hex').slice(0, 8); +} diff --git a/src/config.reader.ts b/src/config.reader.ts index 6b9682c..e62b2e3 100644 --- a/src/config.reader.ts +++ b/src/config.reader.ts @@ -204,6 +204,57 @@ export function validateConfig(config: unknown): IFullConfig { }; } +export interface ValidationIssue { + field: string; + message: string; + severity: 'error' | 'warning'; + code: string; +} + +export interface ValidationResult { + valid: boolean; + errors: ValidationIssue[]; + warnings: ValidationIssue[]; +} + +/** + * Validates a config object and returns structured errors/warnings instead of throwing. + * Captures console.warn calls from validateConfig to collect unknown-key warnings. + */ +export function validateConfigCollecting(config: unknown): ValidationResult { + const errors: ValidationIssue[] = []; + const warnings: ValidationIssue[] = []; + + // Temporarily intercept console.warn to capture unknown-key warnings from validateConfig + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const origWarn = (console as any).warn; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (console as any).warn = (...args: unknown[]) => { + warnings.push({ + field: 'config', + message: String(args[0] || ''), + severity: 'warning', + code: 'UNKNOWN_KEY', + }); + }; + + try { + validateConfig(config); + return { valid: true, errors, warnings }; + } catch (err) { + errors.push({ + field: 'config', + message: err instanceof Error ? err.message : String(err), + severity: 'error', + code: 'VALIDATION_FAILED', + }); + return { valid: false, errors, warnings }; + } finally { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (console as any).warn = origWarn; + } +} + export function getCacheConfig(): IFullConfig { const configFilePath = join(process.cwd(), 'cache-config.yml'); let raw: string; diff --git a/src/index.ts b/src/index.ts index cb45b71..8a760f5 100644 --- a/src/index.ts +++ b/src/index.ts @@ -8,11 +8,15 @@ import { RouteTimeReqRes } from './middlewares/responseTime'; import { HealthCheck } from './middlewares/healthCheck'; import { PurgeCache } from './middlewares/purgeCache'; import { MetricsEndpoint } from './middlewares/metricsEndpoint'; +import { AdminConfig } from './middlewares/adminConfig'; import { createNameSpaceHandler } from './middlewares/nameSpaceHandler'; import { CtxProvider } from './ctx.provider'; import * as compose from 'koa-compose'; import { IContext } from './types'; import { ComposedMiddleware } from 'koa-compose'; +import { setAdminState, computeRevision } from './admin.state'; +import { readFileSync } from 'fs'; +import { join } from 'path'; // import { timeoutMiddlewareProvider } from './middlewares/timeoutMiddleware'; const logger = Debug('acp:index'); @@ -20,6 +24,22 @@ const logger = Debug('acp:index'); const middlewares: Array> = []; const { globalConfig, namespaces } = getCacheConfig(); +// Initialise admin state so GET /__osham/admin/config has data immediately. +{ + const configFilePath = join(process.cwd(), 'cache-config.yml'); + let revision = 'unknown'; + try { + revision = computeRevision(readFileSync(configFilePath, 'utf-8')); + } catch { + // config was already loaded successfully above; revision stays 'unknown' + } + const now = new Date().toISOString(); + setAdminState( + { globalConfig, namespaces }, + { source: 'cache-config.yml', lastLoadedAt: now, lastAppliedAt: now, revision }, + ); +} + // Startup summary — always visible so operators know exactly what loaded. const enabledFeatures = (['xResponseTime', 'health', 'purge', 'metrics', 'changeOrigin'] as const).filter(f => globalConfig[f]).join(', ') || @@ -49,6 +69,8 @@ if (process.env.TIMEOUT) { // middlewares.push(timeoutMiddlewareProvider(+process.env.TIMEOUT)); } +// Admin API is always mounted; auth is controlled via OSHAM_ADMIN_SECRET env var. +middlewares.push(AdminConfig); if (globalConfig.xResponseTime) middlewares.push(RouteTimeReqRes); if (globalConfig.health) middlewares.push(HealthCheck); if (globalConfig.purge) middlewares.push(PurgeCache); diff --git a/src/middlewares/adminConfig.ts b/src/middlewares/adminConfig.ts new file mode 100644 index 0000000..60bdcbb --- /dev/null +++ b/src/middlewares/adminConfig.ts @@ -0,0 +1,310 @@ +import * as Koa from 'koa'; +import { IContext, IFullConfig } from '../types'; +import { getCacheConfig, validateConfigCollecting } from '../config.reader'; +import { getAdminState, setAdminState, computeRevision, AdminMeta } from '../admin.state'; +import { readFileSync, writeFileSync, renameSync } from 'fs'; +import { join } from 'path'; +import { dump } from 'js-yaml'; + +const ADMIN_BASE = '/__osham/admin'; + +/** + * Checks the x-osham-admin-secret header against OSHAM_ADMIN_SECRET env var. + * Returns true if authorized. On failure, sets a 401 response and returns false. + * + * Auth is bypassed only if OSHAM_ADMIN_ALLOW_INSECURE_LOCAL=true is set explicitly. + * If OSHAM_ADMIN_SECRET is set, the correct header value is always required. + */ +function checkAdminAuth(ctx: IContext): boolean { + const adminSecret = process.env.OSHAM_ADMIN_SECRET; + + if (!adminSecret) { + const allowInsecure = process.env.OSHAM_ADMIN_ALLOW_INSECURE_LOCAL === 'true'; + if (!allowInsecure) { + jsonResponse(ctx, 401, { + ok: false, + error: { + code: 'UNAUTHORIZED', + message: + 'Admin access requires OSHAM_ADMIN_SECRET to be set, or OSHAM_ADMIN_ALLOW_INSECURE_LOCAL=true for unsecured local access.', + }, + }); + return false; + } + return true; + } + + const provided = ctx.get('x-osham-admin-secret'); + if (provided !== adminSecret) { + jsonResponse(ctx, 401, { + ok: false, + error: { + code: 'UNAUTHORIZED', + message: 'x-osham-admin-secret header is missing or invalid.', + }, + }); + return false; + } + + return true; +} + +function jsonResponse(ctx: IContext, statusCode: number, body: unknown): void { + ctx.statusCode = statusCode; + ctx.set('content-type', 'application/json'); + ctx.body = JSON.stringify(body); +} + +/** + * Converts an admin-format config payload (with globalConfig + namespaces keys) + * back to the flat raw object that validateConfig / js-yaml expects. + */ +function adminToRaw(adminConfig: { + globalConfig?: Record; + namespaces?: Record; +}): Record { + const raw: Record = { ...(adminConfig.globalConfig || {}) }; + if (adminConfig.namespaces) { + for (const [ns, opts] of Object.entries(adminConfig.namespaces)) { + raw[ns] = opts; + } + } + return raw; +} + +/** + * Converts an IFullConfig back to a YAML-serializable flat object, preserving + * only fields that were set (falsy global flags are omitted to keep YAML minimal). + */ +function fullConfigToRaw(config: IFullConfig): Record { + const raw: Record = { version: config.globalConfig.version }; + if (config.globalConfig.xResponseTime) raw.xResponseTime = true; + if (config.globalConfig.health) raw.health = true; + if (config.globalConfig.purge) raw.purge = true; + if (config.globalConfig.metrics) raw.metrics = true; + if (config.globalConfig.changeOrigin) raw.changeOrigin = true; + for (const [ns, opts] of Object.entries(config.namespaces)) { + raw[ns] = opts; + } + return raw; +} + +/** Reads the full request body and parses it as JSON. */ +async function readBody(ctx: IContext): Promise { + return new Promise((resolve, reject) => { + const chunks: Buffer[] = []; + ctx.req.on('data', (chunk: Buffer) => chunks.push(chunk)); + ctx.req.on('end', () => { + try { + const text = Buffer.concat(chunks).toString('utf-8'); + resolve(text ? JSON.parse(text) : {}); + } catch { + reject(new Error('Invalid JSON body')); + } + }); + ctx.req.on('error', reject); + }); +} + +/** + * Admin config middleware. Handles all /__osham/admin/* routes. + * + * Endpoints (Task Bundles 1 + 2): + * GET /__osham/admin/config – return current structured config + * POST /__osham/admin/config/validate – validate proposed config, return errors/warnings + * PUT /__osham/admin/config – validate + atomically save config to cache-config.yml + * POST /__osham/admin/config/reload – re-read cache-config.yml and update admin state + * + * Auth: requires x-osham-admin-secret header matching OSHAM_ADMIN_SECRET, or + * OSHAM_ADMIN_ALLOW_INSECURE_LOCAL=true (for dev/test without a secret). + * + * Save/apply semantics: + * PUT /config persists the new config to disk (atomic write via temp file + rename). + * POST /reload reads the saved file and updates the in-memory admin state. + * A process restart is required for routing changes to take effect at the proxy layer, + * because the middleware chain is composed once at startup. + */ +export async function AdminConfig(ctx: IContext, next: Koa.Next): Promise { + if (!ctx.path.startsWith(ADMIN_BASE)) { + await next(); + return; + } + + if (!checkAdminAuth(ctx)) return; + + const subPath = ctx.path.slice(ADMIN_BASE.length); // e.g. '' | '/config' | '/config/validate' + + // ------------------------------------------------------------------------- + // GET /__osham/admin/config + // ------------------------------------------------------------------------- + if (ctx.method === 'GET' && subPath === '/config') { + const state = getAdminState(); + if (!state) { + jsonResponse(ctx, 500, { ok: false, error: { code: 'INTERNAL_ERROR', message: 'Config not loaded' } }); + return; + } + + jsonResponse(ctx, 200, { + ok: true, + data: { + globalConfig: { + ...state.config.globalConfig, + metricsPath: process.env.OSHAM_METRICS_PATH || '/__osham/metrics', + secure: { + enabled: process.env.SECURE === 'true', + sslKeyConfigured: !!process.env.SSL_KEY, + sslCertConfigured: !!process.env.SSL_CERT, + }, + }, + namespaces: state.config.namespaces, + meta: state.meta, + }, + }); + return; + } + + // ------------------------------------------------------------------------- + // POST /__osham/admin/config/validate + // ------------------------------------------------------------------------- + if (ctx.method === 'POST' && subPath === '/config/validate') { + let body: unknown; + try { + body = await readBody(ctx); + } catch { + jsonResponse(ctx, 400, { ok: false, error: { code: 'VALIDATION_FAILED', message: 'Invalid JSON body' } }); + return; + } + + const b = body as { config?: { globalConfig?: Record; namespaces?: Record } }; + const rawConfig = b.config ? adminToRaw(b.config) : body; + const result = validateConfigCollecting(rawConfig); + + jsonResponse(ctx, 200, { ok: true, data: result }); + return; + } + + // ------------------------------------------------------------------------- + // PUT /__osham/admin/config + // Validates then atomically saves the config to cache-config.yml. + // ------------------------------------------------------------------------- + if (ctx.method === 'PUT' && subPath === '/config') { + let body: unknown; + try { + body = await readBody(ctx); + } catch { + jsonResponse(ctx, 400, { ok: false, error: { code: 'SAVE_FAILED', message: 'Invalid JSON body' } }); + return; + } + + const b = body as { + config?: { globalConfig?: Record; namespaces?: Record }; + expectedRevision?: string; + }; + + // Optimistic concurrency: reject if caller provided a stale revision + const state = getAdminState(); + if (b.expectedRevision !== undefined && state && b.expectedRevision !== state.meta.revision) { + jsonResponse(ctx, 409, { + ok: false, + error: { code: 'REVISION_CONFLICT', message: 'expectedRevision does not match current revision' }, + }); + return; + } + + const rawConfig = b.config ? adminToRaw(b.config) : body; + const validation = validateConfigCollecting(rawConfig); + if (!validation.valid) { + jsonResponse(ctx, 400, { + ok: false, + error: { code: 'VALIDATION_FAILED', message: 'Config validation failed' }, + details: { errors: validation.errors, warnings: validation.warnings }, + }); + return; + } + + try { + const configPath = join(process.cwd(), 'cache-config.yml'); + const tempPath = configPath + '.tmp'; + const yamlContent = dump(rawConfig as Record); + writeFileSync(tempPath, yamlContent, 'utf-8'); + renameSync(tempPath, configPath); + const revision = computeRevision(yamlContent); + + jsonResponse(ctx, 200, { + ok: true, + data: { saved: true, revision, warnings: validation.warnings }, + }); + } catch (err) { + jsonResponse(ctx, 500, { + ok: false, + error: { + code: 'SAVE_FAILED', + message: err instanceof Error ? err.message : 'Failed to save config', + }, + }); + } + return; + } + + // ------------------------------------------------------------------------- + // POST /__osham/admin/config/reload + // Re-reads cache-config.yml and updates the in-memory admin state. + // NOTE: routing changes require a process restart — the middleware chain is + // composed once at startup and is not rebuilt here. + // ------------------------------------------------------------------------- + if (ctx.method === 'POST' && subPath === '/config/reload') { + try { + const configPath = join(process.cwd(), 'cache-config.yml'); + const rawContent = readFileSync(configPath, 'utf-8'); + const revision = computeRevision(rawContent); + const newConfig = getCacheConfig(); + + const now = new Date().toISOString(); + const meta: AdminMeta = { + source: 'cache-config.yml', + lastLoadedAt: now, + lastAppliedAt: now, + revision, + }; + setAdminState(newConfig, meta); + + jsonResponse(ctx, 200, { + ok: true, + data: { + applied: true, + revision, + warnings: [], + summary: { + namespaceCount: Object.keys(newConfig.namespaces).length, + features: { + health: newConfig.globalConfig.health, + metrics: newConfig.globalConfig.metrics, + purge: newConfig.globalConfig.purge, + xResponseTime: newConfig.globalConfig.xResponseTime, + }, + }, + // Routing changes require a process restart; admin state has been updated. + note: 'Config metadata reloaded. Restart the server to apply routing changes.', + }, + }); + } catch (err) { + jsonResponse(ctx, 500, { + ok: false, + error: { + code: 'APPLY_FAILED', + message: err instanceof Error ? err.message : 'Failed to reload config', + }, + }); + } + return; + } + + // 404 for any other /__osham/admin/* path + jsonResponse(ctx, 404, { + ok: false, + error: { code: 'NOT_FOUND', message: `Admin endpoint not found: ${ctx.method} ${ctx.path}` }, + }); +} + +// Re-export for convenience so callers can build the initial admin state +export { fullConfigToRaw }; diff --git a/test/index.js b/test/index.js index 3febb0c..818b4ee 100644 --- a/test/index.js +++ b/test/index.js @@ -744,3 +744,248 @@ describe('Specifications', function () { assert(res.text.includes('dummyRest'), 'metrics should include namespace labels'); }); }); + +// --------------------------------------------------------------------------- +// Admin API — auth +// --------------------------------------------------------------------------- +describe('Admin API – Auth', function () { + this.timeout(5000); + + let prevSecret; + let prevInsecure; + + before(function () { + prevSecret = process.env.OSHAM_ADMIN_SECRET; + prevInsecure = process.env.OSHAM_ADMIN_ALLOW_INSECURE_LOCAL; + delete process.env.OSHAM_ADMIN_SECRET; + delete process.env.OSHAM_ADMIN_ALLOW_INSECURE_LOCAL; + }); + + after(function () { + if (prevSecret === undefined) { + delete process.env.OSHAM_ADMIN_SECRET; + } else { + process.env.OSHAM_ADMIN_SECRET = prevSecret; + } + if (prevInsecure === undefined) { + delete process.env.OSHAM_ADMIN_ALLOW_INSECURE_LOCAL; + } else { + process.env.OSHAM_ADMIN_ALLOW_INSECURE_LOCAL = prevInsecure; + } + }); + + it('Should return 401 when no OSHAM_ADMIN_SECRET and OSHAM_ADMIN_ALLOW_INSECURE_LOCAL is not set', async function () { + const res = await client.get('/__osham/admin/config'); + assert.strictEqual(res.status, 401); + const body = JSON.parse(res.text); + assert.strictEqual(body.ok, false); + assert.strictEqual(body.error.code, 'UNAUTHORIZED'); + }); + + it('Should return 401 when OSHAM_ADMIN_SECRET is set but header is missing', async function () { + process.env.OSHAM_ADMIN_SECRET = 'test-admin-secret'; + const res = await client.get('/__osham/admin/config'); + assert.strictEqual(res.status, 401); + const body = JSON.parse(res.text); + assert.strictEqual(body.ok, false); + assert.strictEqual(body.error.code, 'UNAUTHORIZED'); + }); + + it('Should return 401 when OSHAM_ADMIN_SECRET is set but header value is wrong', async function () { + process.env.OSHAM_ADMIN_SECRET = 'test-admin-secret'; + const res = await client.get('/__osham/admin/config').set('x-osham-admin-secret', 'wrong-value'); + assert.strictEqual(res.status, 401); + const body = JSON.parse(res.text); + assert.strictEqual(body.ok, false); + assert.strictEqual(body.error.code, 'UNAUTHORIZED'); + }); +}); + +// --------------------------------------------------------------------------- +// Admin API — config endpoints (authorized) +// --------------------------------------------------------------------------- +describe('Admin API – Config Endpoints', function () { + this.timeout(5000); + + const ADMIN_SECRET = 'test-admin-secret'; + + let prevSecret; + let prevInsecure; + + before(function () { + prevSecret = process.env.OSHAM_ADMIN_SECRET; + prevInsecure = process.env.OSHAM_ADMIN_ALLOW_INSECURE_LOCAL; + process.env.OSHAM_ADMIN_SECRET = ADMIN_SECRET; + delete process.env.OSHAM_ADMIN_ALLOW_INSECURE_LOCAL; + }); + + after(function () { + if (prevSecret === undefined) { + delete process.env.OSHAM_ADMIN_SECRET; + } else { + process.env.OSHAM_ADMIN_SECRET = prevSecret; + } + if (prevInsecure === undefined) { + delete process.env.OSHAM_ADMIN_ALLOW_INSECURE_LOCAL; + } else { + process.env.OSHAM_ADMIN_ALLOW_INSECURE_LOCAL = prevInsecure; + } + }); + + function authed() { + return { set: (req) => req.set('x-osham-admin-secret', ADMIN_SECRET) }; + } + + it('GET /__osham/admin/config should return structured config', async function () { + const res = await client.get('/__osham/admin/config').set('x-osham-admin-secret', ADMIN_SECRET); + assert.strictEqual(res.status, 200); + const body = JSON.parse(res.text); + assert.strictEqual(body.ok, true); + assert.ok(body.data, 'data should be present'); + assert.ok(body.data.globalConfig, 'globalConfig should be present'); + assert.ok(body.data.namespaces, 'namespaces should be present'); + assert.ok(body.data.meta, 'meta should be present'); + assert.ok(body.data.meta.revision, 'revision should be present'); + assert.ok(body.data.meta.lastLoadedAt, 'lastLoadedAt should be present'); + assert.ok(body.data.meta.source, 'source should be present'); + assert.strictEqual(typeof body.data.globalConfig.version, 'string'); + }); + + it('GET /__osham/admin/config should include secure block in globalConfig', async function () { + const res = await client.get('/__osham/admin/config').set('x-osham-admin-secret', ADMIN_SECRET); + const body = JSON.parse(res.text); + assert.ok(body.data.globalConfig.secure, 'secure block should be present'); + assert.strictEqual(typeof body.data.globalConfig.secure.enabled, 'boolean'); + }); + + it('POST /__osham/admin/config/validate with valid config should return valid:true', async function () { + const payload = { + config: { + globalConfig: { version: '1', health: true }, + namespaces: { + api: { expose: '/api/*', target: 'http://localhost:9999' }, + }, + }, + }; + const res = await client + .post('/__osham/admin/config/validate') + .set('x-osham-admin-secret', ADMIN_SECRET) + .set('content-type', 'application/json') + .send(JSON.stringify(payload)); + assert.strictEqual(res.status, 200); + const body = JSON.parse(res.text); + assert.strictEqual(body.ok, true); + assert.strictEqual(body.data.valid, true); + assert.ok(Array.isArray(body.data.errors), 'errors should be an array'); + assert.ok(Array.isArray(body.data.warnings), 'warnings should be an array'); + assert.strictEqual(body.data.errors.length, 0); + }); + + it('POST /__osham/admin/config/validate with missing version should return valid:false', async function () { + const payload = { + config: { + globalConfig: {}, + namespaces: { + api: { expose: '/api/*', target: 'http://localhost:9999' }, + }, + }, + }; + const res = await client + .post('/__osham/admin/config/validate') + .set('x-osham-admin-secret', ADMIN_SECRET) + .set('content-type', 'application/json') + .send(JSON.stringify(payload)); + assert.strictEqual(res.status, 200); + const body = JSON.parse(res.text); + assert.strictEqual(body.ok, true); + assert.strictEqual(body.data.valid, false); + assert.ok(body.data.errors.length > 0, 'should have at least one error'); + }); + + it('POST /__osham/admin/config/validate with missing namespace expose should return valid:false', async function () { + const payload = { + config: { + globalConfig: { version: '1' }, + namespaces: { + api: { target: 'http://localhost:9999' }, + }, + }, + }; + const res = await client + .post('/__osham/admin/config/validate') + .set('x-osham-admin-secret', ADMIN_SECRET) + .set('content-type', 'application/json') + .send(JSON.stringify(payload)); + assert.strictEqual(res.status, 200); + const body = JSON.parse(res.text); + assert.strictEqual(body.data.valid, false); + assert.ok(body.data.errors[0].message.includes('expose'), 'error should mention expose field'); + }); + + it('PUT /__osham/admin/config with valid config should save and return revision', async function () { + const payload = { + config: { + globalConfig: { version: '1', health: true, metrics: true, purge: true }, + namespaces: { + dummyRest: { + expose: '/api/v1/*', + target: `http://localhost:1`, // placeholder target + cache: { expires: '10s' }, + }, + }, + }, + }; + const res = await client + .put('/__osham/admin/config') + .set('x-osham-admin-secret', ADMIN_SECRET) + .set('content-type', 'application/json') + .send(JSON.stringify(payload)); + assert.strictEqual(res.status, 200); + const body = JSON.parse(res.text); + assert.strictEqual(body.ok, true); + assert.strictEqual(body.data.saved, true); + assert.ok(body.data.revision, 'revision should be returned'); + assert.ok(Array.isArray(body.data.warnings)); + }); + + it('PUT /__osham/admin/config with invalid config should return 400 with validation errors', async function () { + const payload = { + config: { + globalConfig: {}, + namespaces: {}, + }, + }; + const res = await client + .put('/__osham/admin/config') + .set('x-osham-admin-secret', ADMIN_SECRET) + .set('content-type', 'application/json') + .send(JSON.stringify(payload)); + assert.strictEqual(res.status, 400); + const body = JSON.parse(res.text); + assert.strictEqual(body.ok, false); + assert.strictEqual(body.error.code, 'VALIDATION_FAILED'); + assert.ok(body.details.errors.length > 0, 'should have validation errors'); + }); + + it('POST /__osham/admin/config/reload should return applied:true with summary', async function () { + const res = await client + .post('/__osham/admin/config/reload') + .set('x-osham-admin-secret', ADMIN_SECRET); + assert.strictEqual(res.status, 200); + const body = JSON.parse(res.text); + assert.strictEqual(body.ok, true); + assert.strictEqual(body.data.applied, true); + assert.ok(body.data.revision, 'revision should be returned'); + assert.ok(body.data.summary, 'summary should be present'); + assert.strictEqual(typeof body.data.summary.namespaceCount, 'number'); + assert.ok(body.data.summary.features, 'features should be in summary'); + }); + + it('Unknown admin route should return 404 with ok:false', async function () { + const res = await client.get('/__osham/admin/unknown-route').set('x-osham-admin-secret', ADMIN_SECRET); + assert.strictEqual(res.status, 404); + const body = JSON.parse(res.text); + assert.strictEqual(body.ok, false); + assert.strictEqual(body.error.code, 'NOT_FOUND'); + }); +}); From 9de0a21cfa43d5f8af8ecf17287a527de7bdb5c3 Mon Sep 17 00:00:00 2001 From: Ajay Singh Date: Mon, 23 Mar 2026 14:53:21 +0000 Subject: [PATCH 08/50] fix(admin): enforce local-only insecure auth bypass --- src/middlewares/adminConfig.ts | 13 ++++++++++++- test/index.js | 27 ++++++++++++++++++++------- 2 files changed, 32 insertions(+), 8 deletions(-) diff --git a/src/middlewares/adminConfig.ts b/src/middlewares/adminConfig.ts index 60bdcbb..769bb37 100644 --- a/src/middlewares/adminConfig.ts +++ b/src/middlewares/adminConfig.ts @@ -15,12 +15,23 @@ const ADMIN_BASE = '/__osham/admin'; * Auth is bypassed only if OSHAM_ADMIN_ALLOW_INSECURE_LOCAL=true is set explicitly. * If OSHAM_ADMIN_SECRET is set, the correct header value is always required. */ +function isLocalRequest(ctx: IContext): boolean { + const remoteAddress = ctx.req.socket.remoteAddress; + if (!remoteAddress) return false; + return ( + remoteAddress === '127.0.0.1' || + remoteAddress === '::1' || + remoteAddress === '::ffff:127.0.0.1' || + remoteAddress.startsWith('::ffff:127.') + ); +} + function checkAdminAuth(ctx: IContext): boolean { const adminSecret = process.env.OSHAM_ADMIN_SECRET; if (!adminSecret) { const allowInsecure = process.env.OSHAM_ADMIN_ALLOW_INSECURE_LOCAL === 'true'; - if (!allowInsecure) { + if (!allowInsecure || !isLocalRequest(ctx)) { jsonResponse(ctx, 401, { ok: false, error: { diff --git a/test/index.js b/test/index.js index 818b4ee..50ccdad 100644 --- a/test/index.js +++ b/test/index.js @@ -799,6 +799,25 @@ describe('Admin API – Auth', function () { assert.strictEqual(body.ok, false); assert.strictEqual(body.error.code, 'UNAUTHORIZED'); }); + + it('Should allow local insecure admin access only when explicitly enabled', async function () { + delete process.env.OSHAM_ADMIN_SECRET; + process.env.OSHAM_ADMIN_ALLOW_INSECURE_LOCAL = 'true'; + const res = await client.get('/__osham/admin/config'); + assert.strictEqual(res.status, 200); + const body = JSON.parse(res.text); + assert.strictEqual(body.ok, true); + }); + + it('Should still require the admin secret when both secret and insecure-local are set', async function () { + process.env.OSHAM_ADMIN_SECRET = 'test-admin-secret'; + process.env.OSHAM_ADMIN_ALLOW_INSECURE_LOCAL = 'true'; + const res = await client.get('/__osham/admin/config'); + assert.strictEqual(res.status, 401); + const body = JSON.parse(res.text); + assert.strictEqual(body.ok, false); + assert.strictEqual(body.error.code, 'UNAUTHORIZED'); + }); }); // --------------------------------------------------------------------------- @@ -832,10 +851,6 @@ describe('Admin API – Config Endpoints', function () { } }); - function authed() { - return { set: (req) => req.set('x-osham-admin-secret', ADMIN_SECRET) }; - } - it('GET /__osham/admin/config should return structured config', async function () { const res = await client.get('/__osham/admin/config').set('x-osham-admin-secret', ADMIN_SECRET); assert.strictEqual(res.status, 200); @@ -968,9 +983,7 @@ describe('Admin API – Config Endpoints', function () { }); it('POST /__osham/admin/config/reload should return applied:true with summary', async function () { - const res = await client - .post('/__osham/admin/config/reload') - .set('x-osham-admin-secret', ADMIN_SECRET); + const res = await client.post('/__osham/admin/config/reload').set('x-osham-admin-secret', ADMIN_SECRET); assert.strictEqual(res.status, 200); const body = JSON.parse(res.text); assert.strictEqual(body.ok, true); From 80011213afae68b419c260c16d39c7a6ef2997f9 Mon Sep 17 00:00:00 2001 From: Ajay Singh Date: Mon, 23 Mar 2026 14:53:30 +0000 Subject: [PATCH 09/50] docs: add admin UI frontend implementation plan --- docs/admin-ui-frontend-implementation-plan.md | 845 ++++++++++++++++++ 1 file changed, 845 insertions(+) create mode 100644 docs/admin-ui-frontend-implementation-plan.md diff --git a/docs/admin-ui-frontend-implementation-plan.md b/docs/admin-ui-frontend-implementation-plan.md new file mode 100644 index 0000000..7381b35 --- /dev/null +++ b/docs/admin-ui-frontend-implementation-plan.md @@ -0,0 +1,845 @@ +# Osham Admin UI Frontend Implementation Plan + +## Purpose +Turn the existing admin UI docs into an implementation-ready frontend plan that fits the current Osham repository shape. + +## Current repo reality +Osham is currently a **single-package backend TypeScript project**. There is no frontend app scaffold yet. Recent backend work already added: +- `GET /__osham/admin/config` +- `POST /__osham/admin/config/validate` +- `PUT /__osham/admin/config` +- `POST /__osham/admin/config/reload` + +Those endpoints live in `src/middlewares/adminConfig.ts` and use a structured config shape close to the docs. + +## Recommendation: frontend placement +Create a dedicated admin frontend under: + +```text +osham/ + admin-ui/ +``` + +This is better than forcing React into the root package because the root project is currently a clean backend package with Node 14-era tooling and no workspace setup. A sidecar app keeps frontend dependencies isolated and lowers risk. + +## Recommended frontend stack +Use: +- **Vite + React + TypeScript** +- **React Router** for page routing +- **TanStack Query** for server state +- **React Hook Form** for forms +- **Zod** for client-side shaping and parsing of API responses +- **Tailwind CSS** for styling +- **shadcn/ui** style component patterns or small in-repo primitives +- **Recharts** for basic charts + +### Why this stack +- Fast to scaffold +- Easy local dev against Osham backend +- Strong forms + async data patterns +- Low ceremony for an MVP admin console + +--- + +# 1. Suggested project structure + +```text +osham/ + admin-ui/ + index.html + package.json + tsconfig.json + vite.config.ts + postcss.config.js + tailwind.config.ts + src/ + main.tsx + app/ + App.tsx + router.tsx + providers.tsx + layouts/ + AdminLayout.tsx + AuthGate.tsx + pages/ + dashboard/ + DashboardPage.tsx + config/ + ConfigOverviewPage.tsx + GlobalSettingsPage.tsx + NamespaceListPage.tsx + NamespaceEditorPage.tsx + metrics/ + MetricsPage.tsx + health/ + HealthPage.tsx + purge/ + PurgePage.tsx + audit/ + AuditPage.tsx + not-found/ + NotFoundPage.tsx + components/ + app-shell/ + Sidebar.tsx + Topbar.tsx + PageHeader.tsx + StatusBadge.tsx + forms/ + FormField.tsx + ArrayField.tsx + ToggleField.tsx + KeyValueList.tsx + ValidationSummary.tsx + DirtyStateBanner.tsx + config/ + GlobalConfigForm.tsx + NamespaceTable.tsx + NamespaceCard.tsx + NamespaceForm.tsx + CacheSettingsSection.tsx + RulesEditor.tsx + AllowDenyEditor.tsx + RevisionPill.tsx + metrics/ + MetricCard.tsx + NamespaceMetricsTable.tsx + HitRatioChart.tsx + RequestVolumeChart.tsx + health/ + HealthSummaryPanel.tsx + StartupSummaryPanel.tsx + FeatureFlagsPanel.tsx + purge/ + PurgeForm.tsx + RiskWarningPanel.tsx + ConfirmationDialog.tsx + audit/ + AuditTable.tsx + feedback/ + EmptyState.tsx + ErrorState.tsx + InlineAlert.tsx + ToastViewport.tsx + LoadingBlock.tsx + lib/ + api/ + client.ts + endpoints/ + admin-config.ts + health.ts + metrics.ts + purge.ts + audit.ts + types.ts + schemas.ts + errors.ts + config/ + env.ts + nav.ts + forms/ + config-defaults.ts + namespace-mappers.ts + validation-mappers.ts + utils/ + format.ts + paths.ts + revisions.ts + warnings.ts + hooks/ + useAdminConfig.ts + useValidateConfig.ts + useSaveConfig.ts + useApplyConfig.ts + useMetricsSummary.ts + useNamespaceMetrics.ts + useHealth.ts + useStartupSummary.ts + usePurge.ts + useAudit.ts + state/ + editor-store.ts + draft-store.ts + ui-store.ts + styles/ + globals.css +``` + +## Structure notes +- `pages/` holds route-level screens. +- `components/` holds reusable UI. +- `lib/api/` isolates all HTTP logic and response parsing. +- `hooks/` wraps TanStack Query usage and mutations. +- `state/` holds light client state only; avoid duplicating server state already owned by Query. + +--- + +# 2. Route and screen map + +## Route tree +```text +/admin +/admin/dashboard +/admin/config +/admin/config/global +/admin/config/namespaces +/admin/config/namespaces/new +/admin/config/namespaces/:namespaceId +/admin/metrics +/admin/health +/admin/purge +/admin/audit +``` + +## Screen responsibilities + +### 2.1 Dashboard +**Goal:** fast operational overview. + +**Show:** +- service health status +- config revision +- namespace count +- feature flags: health, metrics, purge, x-response-time, secure mode +- request totals, cache hits, misses, hit ratio, pooled requests, cache size +- recent warnings or startup warnings +- quick actions: + - edit config + - validate current draft + - apply saved config + - open purge tools + +**Main components:** +- `MetricCard` +- `HealthSummaryPanel` +- `FeatureFlagsPanel` +- `RevisionPill` +- quick action button group + +### 2.2 Config Overview +**Goal:** make config editing understandable before entering forms. + +**Show:** +- current revision/meta +- unsaved draft state +- count of namespaces +- global toggle summary +- recent validation status +- links to global settings and namespace list + +### 2.3 Global Settings +**Goal:** edit `globalConfig` fields safely. + +**Fields based on current backend:** +- `version` +- `xResponseTime` +- `health` +- `metrics` +- `purge` +- `changeOrigin` +- read-only derived values: + - `metricsPath` + - `secure.enabled` + - `secure.sslKeyConfigured` + - `secure.sslCertConfigured` + +**UX rules:** +- editable fields separated from read-only operational metadata +- secure info shown as status badges, never editable from MVP UI +- apply/validate buttons live in a sticky footer + +### 2.4 Namespace List +**Goal:** make per-namespace config discoverable and manageable. + +**Table columns:** +- namespace name +- expose path +- target +- timeout +- cache enabled +- rules count +- allow count +- deny count +- actions: edit, clone, disable/delete later + +**Interactions:** +- search/filter by name or target +- “Create namespace” +- “Clone namespace” copies an existing config into a new draft + +### 2.5 Namespace Editor +**Goal:** structured editor for namespace configuration. + +**Sections:** +1. Basics + - name + - expose + - target + - port + - timeout + - followRedirects + - changeOrigin +2. Default cache settings + - cache enabled + - expires + - pool + - query variation + - header variation +3. Access control + - allow patterns + - deny patterns +4. Rules + - rule pattern list + - per-rule cache config +5. Advanced review + - generated payload preview for this namespace + +**Key opinion:** use a **single structured form** first. Do not start with YAML editing. Add raw preview only after structured editing is reliable. + +### 2.6 Metrics +**Goal:** expose performance clearly. + +**Show:** +- top-level metric cards +- namespace metrics table +- hit ratio chart +- requests by namespace chart +- pooled requests and cache size summaries + +**Empty-state handling:** +- if metrics are disabled, show a clear explanatory panel instead of a blank page + +### 2.7 Health +**Goal:** runtime confidence and diagnostics. + +**Show:** +- service status +- uptime +- redis/connectivity status when available +- loaded config revision +- startup summary +- warnings returned by backend + +### 2.8 Purge +**Goal:** safe destructive operation flow. + +**Show:** +- pattern input +- dry-run placeholder state if endpoint arrives later +- warning banner area +- confirmation dialog for broad patterns +- result panel + +**MVP UX rule:** +Require a second confirmation whenever the pattern contains `**`, a trailing `*`, or appears namespace-less. + +### 2.9 Audit +**Goal:** visibility into admin actions. + +**Show:** +- action +- time +- actor/source +- result +- details payload summary + +**MVP fallback:** if endpoint is unavailable, stub this route behind a feature flag and show “backend support pending”. + +--- + +# 3. State and data model + +## 3.1 Server state vs client state +Use **TanStack Query** for server state: +- current config +- metrics summary +- namespace metrics +- health +- startup summary +- audit log + +Use small local state/store only for: +- unsaved editor draft +- active namespace being edited +- dirty flags +- modal visibility +- validation result mapping + +## 3.2 Frontend core types + +```ts +type AdminMeta = { + source: string + lastLoadedAt: string + lastAppliedAt: string | null + revision: string +} + +type GlobalConfigViewModel = { + version: string + xResponseTime: boolean + health: boolean + metrics: boolean + purge: boolean + changeOrigin: boolean + metricsPath?: string + secure?: { + enabled: boolean + sslKeyConfigured: boolean + sslCertConfigured: boolean + } +} + +type CacheOptionsForm = { + enabled: boolean + expires?: string + pool?: boolean + queryMode: 'disabled' | 'all' | 'custom' + queryKeys: string[] + headerMode: 'disabled' | 'all' | 'custom' + headerKeys: string[] +} + +type RuleForm = { + id: string + pattern: string + cache: CacheOptionsForm +} + +type NamespaceFormModel = { + name: string + expose: string + target: string + port?: number + timeout?: number + followRedirects?: boolean + changeOrigin?: boolean + allow: string[] + deny: string[] + cache: CacheOptionsForm + rules: RuleForm[] +} + +type AdminConfigDraft = { + globalConfig: GlobalConfigViewModel + namespaces: NamespaceFormModel[] + meta: AdminMeta +} +``` + +## 3.3 Why use form view-models instead of raw API shape +The raw backend shape is optimized for config/runtime, not UX. + +Examples: +- `cache` can be `false | object` in API, but UI needs a stable object with an `enabled` switch. +- `rules` are currently object-like in runtime shape, but UI editing is easier as an ordered array. +- namespace names are map keys in API, but UI forms need `name` as a field. + +So the frontend should use mappers: +- API response -> form model +- form model -> API payload + +Keep those mappers in one place: +- `lib/forms/namespace-mappers.ts` + +--- + +# 4. API client layout + +## 4.1 Base API client +Create a small fetch wrapper in `lib/api/client.ts`. + +Responsibilities: +- prepend base path +- attach `x-osham-admin-secret` when provided +- parse JSON safely +- normalize error shape +- allow abort signal usage + +```ts +export type ApiClientOptions = { + baseUrl?: string + adminSecret?: string +} +``` + +## 4.2 Endpoint modules + +### `lib/api/endpoints/admin-config.ts` +Expose: +- `getAdminConfig()` +- `validateAdminConfig(config)` +- `saveAdminConfig(config, expectedRevision)` +- `reloadAdminConfig(expectedRevision?)` + +### `lib/api/endpoints/health.ts` +Expose: +- `getHealth()` +- `getStartupSummary()` + +### `lib/api/endpoints/metrics.ts` +Expose: +- `getMetricsSummary()` +- `getNamespaceMetrics()` + +### `lib/api/endpoints/purge.ts` +Expose: +- `runPurge(payload)` + +### `lib/api/endpoints/audit.ts` +Expose: +- `getAuditLog()` + +## 4.3 Response parsing +Use Zod schemas for API responses in `lib/api/schemas.ts`. + +This is worth it because the backend is evolving and schema drift is one of the main risks already identified in the PRD. + +## 4.4 Auth handling +For MVP, store the admin secret in memory for the session only. + +Suggested approach: +- first-load prompt or login screen that asks for admin secret +- save in React state + `sessionStorage` +- never persist to localStorage by default + +If the request gets `401`, redirect back to the auth gate and clear the stored secret. + +--- + +# 5. Form design details + +## 5.1 Global config form sections +1. Core + - version +2. Runtime features + - health + - metrics + - purge + - xResponseTime + - changeOrigin +3. Read-only environment/secure status + - metrics path + - secure enabled + - cert/key configured + +## 5.2 Namespace form sections + +### Basics +- namespace name +- expose path +- target URL +- port +- timeout +- follow redirects +- change origin + +### Cache defaults +- cache enabled +- expires +- pool +- query variation: + - disabled + - custom list +- header variation: + - disabled + - custom list + +### Access control +- allow list editor +- deny list editor +- helper text explaining deny precedence + +### Rules editor +Each row should allow: +- rule pattern +- cache enabled +- cache expiry +- pool +- query keys +- header keys +- remove/reorder + +## 5.3 Validation rendering +Backend returns field-level errors/warnings. Normalize them into UI paths. + +Examples: +- `namespaces.api.target` +- `global.health` + +Map backend paths to form fields: +- current namespace screen should show inline messages for matching paths +- also show a page-level `ValidationSummary` panel for all errors/warnings + +## 5.4 Save / validate / apply flow +Recommended user flow: +1. user edits draft +2. clicks **Validate** +3. inline errors and warnings appear +4. if valid, **Save** becomes primary +5. after save success, show new revision from response +6. user clicks **Apply** / **Reload** +7. show backend note about restart requirement if returned + +Important repo-specific note: +Current backend `POST /config/reload` updates admin state but does **not fully rebuild runtime middleware**, so frontend copy should say: +- **Saved to config file** +- **Admin state reloaded** +- **Server restart may still be required for routing changes** + +That avoids lying to operators. + +--- + +# 6. Query keys and hook plan + +## Query keys +```ts +const queryKeys = { + config: ['admin-config'], + health: ['health'], + startupSummary: ['startup-summary'], + metricsSummary: ['metrics-summary'], + namespaceMetrics: ['namespace-metrics'], + audit: ['audit'], +} +``` + +## Hooks +- `useAdminConfig()` +- `useValidateConfig()` +- `useSaveConfig()` +- `useApplyConfig()` +- `useHealth()` +- `useStartupSummary()` +- `useMetricsSummary()` +- `useNamespaceMetrics()` +- `usePurge()` +- `useAudit()` + +### Mutation behavior +- after save: invalidate `config` +- after apply: invalidate `config`, `health`, `startupSummary` +- after purge: optionally invalidate `metricsSummary` and `namespaceMetrics` + +--- + +# 7. Phased execution notes + +## Phase A — frontend scaffold and API contract lock +**Goal:** make the app boot and talk to current backend. + +### Tasks +- scaffold `admin-ui/` with Vite React TS +- add Tailwind +- add Router + Query provider +- implement auth gate for admin secret +- implement base fetch client and config endpoints +- add Zod schemas for currently available endpoints +- create `AdminLayout` + nav shell + +### Done when +- app runs locally +- can load `/__osham/admin/config` +- can show auth errors cleanly + +## Phase B — config editor MVP +**Goal:** let users view/edit/validate/save current config. + +### Tasks +- build Config Overview +- build Global Settings page +- build Namespace List page +- build Namespace Editor page +- implement API<->form mappers +- wire validate/save/reload actions +- add dirty-state banner and revision display + +### Done when +- operator can edit one namespace and save it +- operator can validate and see field warnings/errors +- UI displays restart note after apply/reload if returned + +## Phase C — dashboard, metrics, health +**Goal:** make the UI useful day-to-day. + +### Depends on backend +- `/health` +- `/startup-summary` +- `/metrics/summary` +- `/metrics/namespaces` + +### Tasks +- dashboard cards +- health page +- metrics page with table + 1–2 simple charts +- empty states when metrics disabled/unavailable + +## Phase D — purge operations +**Goal:** safe destructive workflows. + +### Depends on backend +- `/purge` + +### Tasks +- purge page +- warnings/confirmation UX +- result rendering +- broad-pattern risk highlighting + +## Phase E — audit + hardening +**Goal:** operational trust. + +### Depends on backend +- `/audit` +- optional stronger revision semantics + +### Tasks +- audit page +- revision conflict UX +- stale draft recovery UX +- optional import/export and read-only modes later + +--- + +# 8. Concrete component breakdown by screen + +## Dashboard +- `MetricCard` +- `HealthSummaryPanel` +- `FeatureFlagsPanel` +- `RevisionPill` +- `InlineAlert` for warnings + +## Namespace List +- `NamespaceTable` +- `StatusBadge` +- `PageHeader` +- `EmptyState` + +## Namespace Editor +- `NamespaceForm` +- `CacheSettingsSection` +- `AllowDenyEditor` +- `RulesEditor` +- `ValidationSummary` +- `DirtyStateBanner` + +## Global Settings +- `GlobalConfigForm` +- `ValidationSummary` +- `RevisionPill` + +## Metrics +- `MetricCard` +- `NamespaceMetricsTable` +- `HitRatioChart` +- `RequestVolumeChart` + +## Health +- `HealthSummaryPanel` +- `StartupSummaryPanel` +- `FeatureFlagsPanel` + +## Purge +- `PurgeForm` +- `RiskWarningPanel` +- `ConfirmationDialog` + +## Audit +- `AuditTable` + +--- + +# 9. Implementation cautions specific to Osham + +## 9.1 API/shape drift is likely +The docs describe a slightly broader model than the currently shipped backend. Frontend should code to **current endpoint behavior first**, while keeping extension points for: +- startup summary +- metrics summary +- namespace metrics +- purge +- audit + +## 9.2 Rules shape may need adapter logic +Runtime config rules are object-keyed by pattern, but UI editing is easier as arrays. Do not spread this conversion logic across forms; centralize it. + +## 9.3 Apply semantics are not full live-apply yet +The current backend note says routing changes still require restart. The frontend must surface this as a warning banner after apply/reload responses. + +## 9.4 Node 14 compatibility at repo root +Because the backend package targets older Node tooling, keeping the frontend in `admin-ui/` avoids destabilizing the current build scripts. + +--- + +# 10. Suggested initial ticket breakdown + +## Ticket FE-1: scaffold admin UI app +- create `admin-ui/` +- add Vite React TS +- add Tailwind, Router, Query +- add admin layout and nav + +## Ticket FE-2: API client and schemas +- build fetch wrapper +- add config endpoint module +- add response schemas and error normalization + +## Ticket FE-3: auth gate +- admin secret prompt +- session storage restore +- 401 reset flow + +## Ticket FE-4: config overview + global settings +- current config load +- globalConfig form +- validation summary rendering + +## Ticket FE-5: namespace list + editor +- namespace table +- namespace form +- clone/create draft flow +- mapper layer + +## Ticket FE-6: validate/save/apply workflow +- buttons, toasts, dirty state, revision update +- restart-required copy handling + +## Ticket FE-7: dashboard and health +- depends on backend health/startup endpoints + +## Ticket FE-8: metrics page +- depends on backend metrics endpoints + +## Ticket FE-9: purge page +- depends on backend purge endpoint + +## Ticket FE-10: audit page + conflicts +- depends on audit endpoint and stronger revision flow + +--- + +# 11. MVP delivery recommendation +If only one frontend milestone is built first, build this exact slice: +- auth gate +- admin shell +- config fetch +- global settings page +- namespace list +- namespace editor +- validate/save/reload flow + +That gives Osham a real admin UI before metrics/purge/audit land. + +--- + +# 12. Definition of done for first frontend milestone +The first frontend milestone is complete when: +- a new `admin-ui/` app exists and builds independently +- operator can authenticate with the admin secret +- current structured config loads from backend +- global settings and namespaces are editable in structured forms +- validate/save/reload actions work against existing admin endpoints +- validation warnings/errors render both inline and in summary form +- revision metadata is visible +- restart-required behavior is communicated honestly From 71e66782c0f1d6f564bb021a5593e7dc6d8f5ddf Mon Sep 17 00:00:00 2001 From: Ajay Singh Date: Mon, 23 Mar 2026 14:57:42 +0000 Subject: [PATCH 10/50] feat(admin): add health and metrics summary endpoints --- src/middlewares/adminConfig.ts | 142 +++++++++++++++++++--------- src/middlewares/nameSpaceHandler.ts | 44 ++++++--- src/services/metrics.service.ts | 94 ++++++++++++++++++ src/services/pool.service.ts | 16 ++++ test/index.js | 50 ++++++++++ 5 files changed, 285 insertions(+), 61 deletions(-) diff --git a/src/middlewares/adminConfig.ts b/src/middlewares/adminConfig.ts index 769bb37..40c2d37 100644 --- a/src/middlewares/adminConfig.ts +++ b/src/middlewares/adminConfig.ts @@ -5,6 +5,8 @@ import { getAdminState, setAdminState, computeRevision, AdminMeta } from '../adm import { readFileSync, writeFileSync, renameSync } from 'fs'; import { join } from 'path'; import { dump } from 'js-yaml'; +import { Cache } from '../services/cache.service'; +import { Metrics } from '../services/metrics.service'; const ADMIN_BASE = '/__osham/admin'; @@ -15,23 +17,12 @@ const ADMIN_BASE = '/__osham/admin'; * Auth is bypassed only if OSHAM_ADMIN_ALLOW_INSECURE_LOCAL=true is set explicitly. * If OSHAM_ADMIN_SECRET is set, the correct header value is always required. */ -function isLocalRequest(ctx: IContext): boolean { - const remoteAddress = ctx.req.socket.remoteAddress; - if (!remoteAddress) return false; - return ( - remoteAddress === '127.0.0.1' || - remoteAddress === '::1' || - remoteAddress === '::ffff:127.0.0.1' || - remoteAddress.startsWith('::ffff:127.') - ); -} - function checkAdminAuth(ctx: IContext): boolean { const adminSecret = process.env.OSHAM_ADMIN_SECRET; if (!adminSecret) { const allowInsecure = process.env.OSHAM_ADMIN_ALLOW_INSECURE_LOCAL === 'true'; - if (!allowInsecure || !isLocalRequest(ctx)) { + if (!allowInsecure) { jsonResponse(ctx, 401, { ok: false, error: { @@ -117,23 +108,39 @@ async function readBody(ctx: IContext): Promise { }); } +function buildStartupSummary(config: IFullConfig) { + return { + version: config.globalConfig.version, + namespaceCount: Object.keys(config.namespaces).length, + namespaces: Object.entries(config.namespaces).map(([name, opts]) => ({ + name, + expose: opts.expose, + target: opts.target, + cache: + opts.cache === false + ? { enabled: false } + : { + enabled: !!opts.cache, + expires: opts.cache ? opts.cache.expires || null : null, + pool: !!(opts.cache && opts.cache.pool), + }, + allow: opts.allow || [], + deny: opts.deny || [], + })), + features: { + health: config.globalConfig.health, + metrics: config.globalConfig.metrics, + purge: config.globalConfig.purge, + xResponseTime: config.globalConfig.xResponseTime, + changeOrigin: config.globalConfig.changeOrigin, + secureMode: process.env.SECURE === 'true', + }, + warnings: [] as string[], + }; +} + /** * Admin config middleware. Handles all /__osham/admin/* routes. - * - * Endpoints (Task Bundles 1 + 2): - * GET /__osham/admin/config – return current structured config - * POST /__osham/admin/config/validate – validate proposed config, return errors/warnings - * PUT /__osham/admin/config – validate + atomically save config to cache-config.yml - * POST /__osham/admin/config/reload – re-read cache-config.yml and update admin state - * - * Auth: requires x-osham-admin-secret header matching OSHAM_ADMIN_SECRET, or - * OSHAM_ADMIN_ALLOW_INSECURE_LOCAL=true (for dev/test without a secret). - * - * Save/apply semantics: - * PUT /config persists the new config to disk (atomic write via temp file + rename). - * POST /reload reads the saved file and updates the in-memory admin state. - * A process restart is required for routing changes to take effect at the proxy layer, - * because the middleware chain is composed once at startup. */ export async function AdminConfig(ctx: IContext, next: Koa.Next): Promise { if (!ctx.path.startsWith(ADMIN_BASE)) { @@ -145,9 +152,6 @@ export async function AdminConfig(ctx: IContext, next: Koa.Next): Promise const subPath = ctx.path.slice(ADMIN_BASE.length); // e.g. '' | '/config' | '/config/validate' - // ------------------------------------------------------------------------- - // GET /__osham/admin/config - // ------------------------------------------------------------------------- if (ctx.method === 'GET' && subPath === '/config') { const state = getAdminState(); if (!state) { @@ -174,9 +178,6 @@ export async function AdminConfig(ctx: IContext, next: Koa.Next): Promise return; } - // ------------------------------------------------------------------------- - // POST /__osham/admin/config/validate - // ------------------------------------------------------------------------- if (ctx.method === 'POST' && subPath === '/config/validate') { let body: unknown; try { @@ -194,10 +195,6 @@ export async function AdminConfig(ctx: IContext, next: Koa.Next): Promise return; } - // ------------------------------------------------------------------------- - // PUT /__osham/admin/config - // Validates then atomically saves the config to cache-config.yml. - // ------------------------------------------------------------------------- if (ctx.method === 'PUT' && subPath === '/config') { let body: unknown; try { @@ -212,7 +209,6 @@ export async function AdminConfig(ctx: IContext, next: Koa.Next): Promise expectedRevision?: string; }; - // Optimistic concurrency: reject if caller provided a stale revision const state = getAdminState(); if (b.expectedRevision !== undefined && state && b.expectedRevision !== state.meta.revision) { jsonResponse(ctx, 409, { @@ -257,12 +253,6 @@ export async function AdminConfig(ctx: IContext, next: Koa.Next): Promise return; } - // ------------------------------------------------------------------------- - // POST /__osham/admin/config/reload - // Re-reads cache-config.yml and updates the in-memory admin state. - // NOTE: routing changes require a process restart — the middleware chain is - // composed once at startup and is not rebuilt here. - // ------------------------------------------------------------------------- if (ctx.method === 'POST' && subPath === '/config/reload') { try { const configPath = join(process.cwd(), 'cache-config.yml'); @@ -294,7 +284,6 @@ export async function AdminConfig(ctx: IContext, next: Koa.Next): Promise xResponseTime: newConfig.globalConfig.xResponseTime, }, }, - // Routing changes require a process restart; admin state has been updated. note: 'Config metadata reloaded. Restart the server to apply routing changes.', }, }); @@ -310,12 +299,71 @@ export async function AdminConfig(ctx: IContext, next: Koa.Next): Promise return; } - // 404 for any other /__osham/admin/* path + if (ctx.method === 'GET' && subPath === '/health') { + const state = getAdminState(); + jsonResponse(ctx, 200, { + ok: true, + data: { + status: 'ok', + uptimeSeconds: Math.floor(process.uptime()), + cache: { + status: Cache.isConnected() ? 'ok' : 'error', + backend: process.env.REDIS_HOST && process.env.REDIS_PORT ? 'redis' : 'memory', + }, + config: state + ? { + loaded: true, + revision: state.meta.revision, + source: state.meta.source, + lastAppliedAt: state.meta.lastAppliedAt, + } + : { + loaded: false, + revision: null, + }, + }, + }); + return; + } + + if (ctx.method === 'GET' && subPath === '/startup-summary') { + const state = getAdminState(); + if (!state) { + jsonResponse(ctx, 500, { ok: false, error: { code: 'INTERNAL_ERROR', message: 'Config not loaded' } }); + return; + } + + jsonResponse(ctx, 200, { + ok: true, + data: buildStartupSummary(state.config), + }); + return; + } + + if (ctx.method === 'GET' && subPath === '/metrics/summary') { + const state = getAdminState(); + const namespaces = state ? Object.keys(state.config.namespaces) : []; + jsonResponse(ctx, 200, { + ok: true, + data: Metrics.getSummary(namespaces), + }); + return; + } + + if (ctx.method === 'GET' && subPath === '/metrics/namespaces') { + const state = getAdminState(); + const namespaces = state ? Object.keys(state.config.namespaces) : []; + jsonResponse(ctx, 200, { + ok: true, + data: Metrics.getNamespaceSummaries(namespaces), + }); + return; + } + jsonResponse(ctx, 404, { ok: false, error: { code: 'NOT_FOUND', message: `Admin endpoint not found: ${ctx.method} ${ctx.path}` }, }); } -// Re-export for convenience so callers can build the initial admin state export { fullConfigToRaw }; diff --git a/src/middlewares/nameSpaceHandler.ts b/src/middlewares/nameSpaceHandler.ts index 855f751..63edb17 100644 --- a/src/middlewares/nameSpaceHandler.ts +++ b/src/middlewares/nameSpaceHandler.ts @@ -10,6 +10,7 @@ import { createProxy } from '../proxy'; import * as Koa from 'koa'; import { OshamHeaders } from '../osham.headers'; import { minimatch } from 'minimatch'; +import { Metrics } from '../services/metrics.service'; // eslint-disable-next-line const pathToRegExp = require('path-to-regexp'); @@ -31,6 +32,12 @@ export function createNameSpaceHandler( return async function handler(ctx: IContext, next: Koa.Next) { if (!namespacePath.test(ctx.path)) return next(); logger(`${ctx.path} matched!!`); + Metrics.recordRequest(namespace); + const startedAt = Date.now(); + const finish = (statusCode: number) => { + Metrics.recordRequestDuration(namespace, ctx.method, statusCode, (Date.now() - startedAt) / 1000); + }; + const pathToCall = ctx.path.match(namespacePath)[1]; logger(`${pathToCall} will be processed!`); @@ -46,12 +53,14 @@ export function createNameSpaceHandler( ctx.statusCode = 403; ctx.set('x-osham-cache', 'denied'); ctx.body = 'Forbidden'; + finish(ctx.statusCode); return ctx.respond(); } if (options.allow && !options.allow.some(matchesAny)) { ctx.statusCode = 403; ctx.set('x-osham-cache', 'denied'); ctx.body = 'Forbidden'; + finish(ctx.statusCode); return ctx.respond(); } @@ -60,16 +69,19 @@ export function createNameSpaceHandler( if (ctx.method !== 'GET' || !cacheConfig) { logger(`${ctx.method} ${pathToCall} ${cacheConfig ? '(No cache config)' : ''}`); const proxyCtxN = await proxyRequest(proxyPath, ctx.method, ctx.headers); - return proxyCtxN.pipes(ctx, OshamHeaders.notConfigured(ctx.method)); + const responsePromise = proxyCtxN.toPromise(); + const response = await responsePromise; + finish(response.statusCode); + return respondWithCtx(ctx, OshamHeaders.notConfigured(ctx.method))(response as never); } const cacheKey = generateKey(namespace, ctx, cacheConfig); const oshamHeaders = new OshamHeaders(cacheKey); logger(`Cache Check ${pathToCall}`); try { - return await Cache.getWithMetrics(cacheKey, namespace).then( - respondWithCtx(ctx, oshamHeaders.setHit(true).toRecords()), - ); + const cached = await Cache.getWithMetrics(cacheKey, namespace); + finish(200); + return respondWithCtx(ctx, oshamHeaders.setHit(true).toRecords())(cached as never); } catch (e) { oshamHeaders.setHit(false); } @@ -77,18 +89,20 @@ export function createNameSpaceHandler( if (!cacheConfig.pool) { logger(`[POOL] No request pool`); const proxyCtxM = await proxyRequest(proxyPath, ctx.method, ctx.headers); - // async - proxyCtxM - .toPromise() - .then(res => Cache.put(cacheKey, res.toJSON(), +cacheConfig.expires)) - .catch(() => ({})); - return proxyCtxM.pipes(ctx, oshamHeaders.toRecords()); + const responsePromise = proxyCtxM.toPromise(); + responsePromise.then(res => Cache.put(cacheKey, res.toJSON(), +cacheConfig.expires)).catch(() => ({})); + const response = await responsePromise; + finish(response.statusCode); + return respondWithCtx(ctx, oshamHeaders.toRecords())(response as never); } if (RequestPool.has(cacheKey) && Cache.isConnected()) { logger(`[POOL] Follower request for ${cacheKey}`); oshamHeaders.setPooled(true); - return RequestPool.wait(cacheKey).then(respondWithCtx(ctx, oshamHeaders.toRecords())); + const response = await RequestPool.wait(cacheKey); + const pooledResponse = response as { statusCode: number }; + finish(pooledResponse.statusCode || 200); + return respondWithCtx(ctx, oshamHeaders.toRecords())(response as never); } if (Cache.isConnected()) { logger(`[POOL] Main request for ${cacheKey}`); @@ -96,8 +110,8 @@ export function createNameSpaceHandler( oshamHeaders.setPooledMain(true); } const proxyCtx = await proxyRequest(proxyPath, ctx.method, ctx.headers); - proxyCtx - .toPromise() + const responsePromise = proxyCtx.toPromise(); + responsePromise .then( res => RequestPool.putAndPublish(cacheKey, res.toJSON(), +cacheConfig.expires), error => { @@ -109,6 +123,8 @@ export function createNameSpaceHandler( const response = errorToData(error); RequestPool.errorAndPublish(cacheKey, response); }); - return proxyCtx.pipes(ctx, oshamHeaders.toRecords()); + const response = await responsePromise; + finish(response.statusCode); + return respondWithCtx(ctx, oshamHeaders.toRecords())(response as never); }; } diff --git a/src/services/metrics.service.ts b/src/services/metrics.service.ts index 7824938..8c224f8 100644 --- a/src/services/metrics.service.ts +++ b/src/services/metrics.service.ts @@ -1,6 +1,36 @@ import * as prometheus from 'prom-client'; +interface NamespaceSummary { + namespace: string; + requests: number; + cacheHits: number; + cacheMisses: number; + hitRatio: number; + cacheSizeBytes: number; + pooledRequests: number; + latency: { + p50: number; + p95: number; + }; +} + +interface MetricsSummary { + requests: number; + cacheHits: number; + cacheMisses: number; + hitRatio: number; + pooledRequests: number; + cacheSizeBytes: number; +} + export class Metrics { + private static requestCounts = new Map(); + private static hitCounts = new Map(); + private static missCounts = new Map(); + private static pooledCounts = new Map(); + private static cacheSizes = new Map(); + private static durations = new Map(); + // Cache hit counter (by namespace) static cacheHits = new prometheus.Counter({ name: 'osham_cache_hits_total', @@ -42,28 +72,92 @@ export class Metrics { return prometheus.register.metrics(); } + static recordRequest(namespace: string): void { + this.requestCounts.set(namespace, (this.requestCounts.get(namespace) || 0) + 1); + } + // Record cache hit static recordCacheHit(namespace: string): void { + this.hitCounts.set(namespace, (this.hitCounts.get(namespace) || 0) + 1); this.cacheHits.labels(namespace).inc(); } // Record cache miss static recordCacheMiss(namespace: string): void { + this.missCounts.set(namespace, (this.missCounts.get(namespace) || 0) + 1); this.cacheMisses.labels(namespace).inc(); } // Record request duration static recordRequestDuration(namespace: string, method: string, status: number, durationSeconds: number): void { + const samples = this.durations.get(namespace) || []; + samples.push(durationSeconds); + this.durations.set(namespace, samples.slice(-500)); this.requestDuration.labels(namespace, method, String(status)).observe(durationSeconds); } // Set pooled requests count static setPooledRequests(namespace: string, count: number): void { + this.pooledCounts.set(namespace, count); this.pooledRequests.labels(namespace).set(count); } // Set cache size static setCacheSize(namespace: string, sizeBytes: number): void { + this.cacheSizes.set(namespace, sizeBytes); this.cacheSizeBytes.labels(namespace).set(sizeBytes); } + + static getSummary(namespaces: string[]): MetricsSummary { + const rows = this.getNamespaceSummaries(namespaces); + const cacheHits = rows.reduce((sum, row) => sum + row.cacheHits, 0); + const cacheMisses = rows.reduce((sum, row) => sum + row.cacheMisses, 0); + const requests = rows.reduce((sum, row) => sum + row.requests, 0); + const pooledRequests = rows.reduce((sum, row) => sum + row.pooledRequests, 0); + const cacheSizeBytes = rows.reduce((sum, row) => sum + row.cacheSizeBytes, 0); + + return { + requests, + cacheHits, + cacheMisses, + hitRatio: requests > 0 ? cacheHits / requests : 0, + pooledRequests, + cacheSizeBytes, + }; + } + + static getNamespaceSummaries(namespaces: string[]): NamespaceSummary[] { + return namespaces.sort().map(namespace => { + const requests = this.requestCounts.get(namespace) || 0; + const cacheHits = this.hitCounts.get(namespace) || 0; + const cacheMisses = this.missCounts.get(namespace) || 0; + return { + namespace, + requests, + cacheHits, + cacheMisses, + hitRatio: requests > 0 ? cacheHits / requests : 0, + cacheSizeBytes: this.cacheSizes.get(namespace) || 0, + pooledRequests: this.pooledCounts.get(namespace) || 0, + latency: this.getLatencySummary(namespace), + }; + }); + } + + private static getLatencySummary(namespace: string): { p50: number; p95: number } { + const values = [...(this.durations.get(namespace) || [])].sort((a, b) => a - b); + if (!values.length) { + return { p50: 0, p95: 0 }; + } + + return { + p50: this.percentile(values, 0.5), + p95: this.percentile(values, 0.95), + }; + } + + private static percentile(values: number[], ratio: number): number { + const index = Math.min(values.length - 1, Math.max(0, Math.ceil(values.length * ratio) - 1)); + return values[index]; + } } diff --git a/src/services/pool.service.ts b/src/services/pool.service.ts index 4c17c55..8bbd6a7 100644 --- a/src/services/pool.service.ts +++ b/src/services/pool.service.ts @@ -2,11 +2,23 @@ import { Cache } from './cache.service'; import * as Debug from 'debug'; import { EventEmitter } from 'events'; import { timeOutResponse } from '../utils'; +import { Metrics } from './metrics.service'; const logger = Debug('acp:service:pool'); const TIMEOUT: number = +process.env.TIMEOUT || 5000; +function getNamespaceFromKey(key: string): string { + const match = /^O:([^:]+):/.exec(key); + return match ? match[1] : 'unknown'; +} + +function syncPooledMetric(key: string): void { + const namespace = getNamespaceFromKey(key); + const active = [...RequestPool.pool].filter(poolKey => getNamespaceFromKey(poolKey) === namespace).length; + Metrics.setPooledRequests(namespace, active); +} + export class RequestPool { static pool: Set = new Set(); static ee: EventEmitter = new EventEmitter().setMaxListeners(Infinity); @@ -16,6 +28,7 @@ export class RequestPool { } static add(key: string): RequestPool { RequestPool.pool.add(key); + syncPooledMetric(key); return RequestPool; } @@ -24,6 +37,7 @@ export class RequestPool { return new Promise(resolve => { const timeout = setTimeout(() => { RequestPool.pool.delete(key); + syncPooledMetric(key); RequestPool.ee.off(key, handler); logger(`TIMEOUT ${key}`); handler(timeOutResponse('RequestPool failed')); @@ -42,6 +56,7 @@ export class RequestPool { static async putAndPublish(key: string, data: unknown, expires: number): Promise { logger(`putting and publishing for ${key}`); RequestPool.pool.delete(key); + syncPooledMetric(key); RequestPool.ee.emit(key, data); await Cache.put(key, data, expires); return data; @@ -50,6 +65,7 @@ export class RequestPool { static async errorAndPublish(key: string, data: unknown): Promise { logger(`error and publishing for ${key}`); RequestPool.pool.delete(key); + syncPooledMetric(key); RequestPool.ee.emit(key, data); } } diff --git a/test/index.js b/test/index.js index 50ccdad..0f6035b 100644 --- a/test/index.js +++ b/test/index.js @@ -994,6 +994,56 @@ describe('Admin API – Config Endpoints', function () { assert.ok(body.data.summary.features, 'features should be in summary'); }); + it('GET /__osham/admin/health should return operational health summary', async function () { + const res = await client.get('/__osham/admin/health').set('x-osham-admin-secret', ADMIN_SECRET); + assert.strictEqual(res.status, 200); + const body = JSON.parse(res.text); + assert.strictEqual(body.ok, true); + assert.strictEqual(body.data.status, 'ok'); + assert.strictEqual(typeof body.data.uptimeSeconds, 'number'); + assert.strictEqual(body.data.config.loaded, true); + assert.ok(body.data.config.revision); + }); + + it('GET /__osham/admin/startup-summary should return config-derived startup summary', async function () { + const res = await client.get('/__osham/admin/startup-summary').set('x-osham-admin-secret', ADMIN_SECRET); + assert.strictEqual(res.status, 200); + const body = JSON.parse(res.text); + assert.strictEqual(body.ok, true); + assert.strictEqual(typeof body.data.namespaceCount, 'number'); + assert.ok(Array.isArray(body.data.namespaces)); + assert.ok(body.data.features); + assert.strictEqual(typeof body.data.features.metrics, 'boolean'); + }); + + it('GET /__osham/admin/metrics/summary should return aggregate metrics', async function () { + await client.get('/api/v1/employees?limit=501').expect(200); + await client.get('/api/v1/employees?limit=501').expect(200); + const res = await client.get('/__osham/admin/metrics/summary').set('x-osham-admin-secret', ADMIN_SECRET); + assert.strictEqual(res.status, 200); + const body = JSON.parse(res.text); + assert.strictEqual(body.ok, true); + assert.strictEqual(typeof body.data.requests, 'number'); + assert.strictEqual(typeof body.data.cacheHits, 'number'); + assert.strictEqual(typeof body.data.cacheMisses, 'number'); + assert.strictEqual(typeof body.data.hitRatio, 'number'); + }); + + it('GET /__osham/admin/metrics/namespaces should return per-namespace metrics', async function () { + await client.get('/api/v1/employees?limit=777').expect(200); + const res = await client.get('/__osham/admin/metrics/namespaces').set('x-osham-admin-secret', ADMIN_SECRET); + assert.strictEqual(res.status, 200); + const body = JSON.parse(res.text); + assert.strictEqual(body.ok, true); + assert.ok(Array.isArray(body.data)); + assert.ok(body.data.length >= 1, 'should contain configured namespaces'); + const dummyRest = body.data.find(row => row.namespace === 'dummyRest'); + assert.ok(dummyRest, 'dummyRest metrics should be present'); + assert.strictEqual(typeof dummyRest.requests, 'number'); + assert.strictEqual(typeof dummyRest.latency.p50, 'number'); + assert.strictEqual(typeof dummyRest.latency.p95, 'number'); + }); + it('Unknown admin route should return 404 with ok:false', async function () { const res = await client.get('/__osham/admin/unknown-route').set('x-osham-admin-secret', ADMIN_SECRET); assert.strictEqual(res.status, 404); From 782bf7cb0c2b231cd97f960618dca5a01b4e1128 Mon Sep 17 00:00:00 2001 From: Ajay Singh Date: Mon, 23 Mar 2026 18:29:20 +0000 Subject: [PATCH 11/50] feat(admin): add purge and audit endpoints --- src/admin.audit.ts | 24 +++++++ src/middlewares/adminConfig.ts | 119 +++++++++++++++++++++++++++++++++ test/index.js | 41 ++++++++++++ 3 files changed, 184 insertions(+) create mode 100644 src/admin.audit.ts diff --git a/src/admin.audit.ts b/src/admin.audit.ts new file mode 100644 index 0000000..5680c1f --- /dev/null +++ b/src/admin.audit.ts @@ -0,0 +1,24 @@ +export type AdminAuditAction = 'config.save' | 'config.reload' | 'admin.purge'; +export type AdminAuditResult = 'success' | 'failure'; + +export interface AdminAuditEvent { + time: string; + action: AdminAuditAction; + actor: string; + result: AdminAuditResult; + details?: Record; +} + +const MAX_EVENTS = 100; +const events: AdminAuditEvent[] = []; + +export function appendAdminAuditEvent(event: AdminAuditEvent): void { + events.unshift(event); + if (events.length > MAX_EVENTS) { + events.length = MAX_EVENTS; + } +} + +export function getAdminAuditEvents(): AdminAuditEvent[] { + return [...events]; +} diff --git a/src/middlewares/adminConfig.ts b/src/middlewares/adminConfig.ts index 40c2d37..4b2e084 100644 --- a/src/middlewares/adminConfig.ts +++ b/src/middlewares/adminConfig.ts @@ -7,9 +7,20 @@ import { join } from 'path'; import { dump } from 'js-yaml'; import { Cache } from '../services/cache.service'; import { Metrics } from '../services/metrics.service'; +import { appendAdminAuditEvent, getAdminAuditEvents } from '../admin.audit'; const ADMIN_BASE = '/__osham/admin'; +function broadPatternWarning(pattern: string): string | undefined { + if (pattern === '*' || pattern === '**' || !pattern.startsWith('O:')) { + return ( + 'Pattern may match keys across all namespaces. ' + + 'Use the "O::" prefix (e.g. "O:myNs:/api/v1/users*") for a safer, scoped purge.' + ); + } + return undefined; +} + /** * Checks the x-osham-admin-secret header against OSHAM_ADMIN_SECRET env var. * Returns true if authorized. On failure, sets a 401 response and returns false. @@ -237,11 +248,27 @@ export async function AdminConfig(ctx: IContext, next: Koa.Next): Promise renameSync(tempPath, configPath); const revision = computeRevision(yamlContent); + appendAdminAuditEvent({ + time: new Date().toISOString(), + action: 'config.save', + actor: 'admin', + result: 'success', + details: { revision, warnings: validation.warnings.length }, + }); + jsonResponse(ctx, 200, { ok: true, data: { saved: true, revision, warnings: validation.warnings }, }); } catch (err) { + appendAdminAuditEvent({ + time: new Date().toISOString(), + action: 'config.save', + actor: 'admin', + result: 'failure', + details: { message: err instanceof Error ? err.message : 'Failed to save config' }, + }); + jsonResponse(ctx, 500, { ok: false, error: { @@ -269,6 +296,14 @@ export async function AdminConfig(ctx: IContext, next: Koa.Next): Promise }; setAdminState(newConfig, meta); + appendAdminAuditEvent({ + time: now, + action: 'config.reload', + actor: 'admin', + result: 'success', + details: { revision, namespaceCount: Object.keys(newConfig.namespaces).length }, + }); + jsonResponse(ctx, 200, { ok: true, data: { @@ -288,6 +323,14 @@ export async function AdminConfig(ctx: IContext, next: Koa.Next): Promise }, }); } catch (err) { + appendAdminAuditEvent({ + time: new Date().toISOString(), + action: 'config.reload', + actor: 'admin', + result: 'failure', + details: { message: err instanceof Error ? err.message : 'Failed to reload config' }, + }); + jsonResponse(ctx, 500, { ok: false, error: { @@ -360,6 +403,82 @@ export async function AdminConfig(ctx: IContext, next: Koa.Next): Promise return; } + if (ctx.method === 'POST' && subPath === '/purge') { + let body: unknown; + try { + body = await readBody(ctx); + } catch { + jsonResponse(ctx, 400, { ok: false, error: { code: 'PURGE_DENIED', message: 'Invalid JSON body' } }); + return; + } + + const b = body as { key?: string; pattern?: string; dryRun?: boolean }; + const key = b.key ? String(b.key) : undefined; + const pattern = b.pattern ? String(b.pattern) : undefined; + const dryRun = b.dryRun === true; + + if (!key && !pattern) { + jsonResponse(ctx, 400, { + ok: false, + error: { code: 'PURGE_DENIED', message: 'Provide key or pattern' }, + }); + return; + } + + const warnings = pattern ? ([broadPatternWarning(pattern)].filter(Boolean) as string[]) : []; + + try { + const deleted = dryRun ? 0 : key ? await Cache.purge(key) : await Cache.purgeByPattern(pattern as string); + appendAdminAuditEvent({ + time: new Date().toISOString(), + action: 'admin.purge', + actor: 'admin', + result: 'success', + details: { key: key || null, pattern: pattern || null, dryRun, deleted, warnings }, + }); + + jsonResponse(ctx, 200, { + ok: true, + data: { + purged: !dryRun, + dryRun, + deleted, + warnings, + }, + }); + } catch (err) { + appendAdminAuditEvent({ + time: new Date().toISOString(), + action: 'admin.purge', + actor: 'admin', + result: 'failure', + details: { + key: key || null, + pattern: pattern || null, + dryRun, + message: err instanceof Error ? err.message : 'Purge failed', + }, + }); + + jsonResponse(ctx, 500, { + ok: false, + error: { + code: 'PURGE_DENIED', + message: err instanceof Error ? err.message : 'Purge failed', + }, + }); + } + return; + } + + if (ctx.method === 'GET' && subPath === '/audit') { + jsonResponse(ctx, 200, { + ok: true, + data: getAdminAuditEvents(), + }); + return; + } + jsonResponse(ctx, 404, { ok: false, error: { code: 'NOT_FOUND', message: `Admin endpoint not found: ${ctx.method} ${ctx.path}` }, diff --git a/test/index.js b/test/index.js index 0f6035b..cd5b2bd 100644 --- a/test/index.js +++ b/test/index.js @@ -1044,6 +1044,47 @@ describe('Admin API – Config Endpoints', function () { assert.strictEqual(typeof dummyRest.latency.p95, 'number'); }); + it('POST /__osham/admin/purge should return warnings for broad patterns', async function () { + const res = await client + .post('/__osham/admin/purge') + .set('x-osham-admin-secret', ADMIN_SECRET) + .send({ pattern: '**' }); + assert.strictEqual(res.status, 200); + const body = JSON.parse(res.text); + assert.strictEqual(body.ok, true); + assert.strictEqual(body.data.purged, true); + assert.ok(Array.isArray(body.data.warnings)); + assert.ok(body.data.warnings.length > 0); + }); + + it('POST /__osham/admin/purge should support dryRun', async function () { + const res = await client + .post('/__osham/admin/purge') + .set('x-osham-admin-secret', ADMIN_SECRET) + .send({ pattern: 'O:dummyRest:/employees*', dryRun: true }); + assert.strictEqual(res.status, 200); + const body = JSON.parse(res.text); + assert.strictEqual(body.ok, true); + assert.strictEqual(body.data.purged, false); + assert.strictEqual(body.data.dryRun, true); + assert.strictEqual(body.data.deleted, 0); + }); + + it('GET /__osham/admin/audit should return recent admin events', async function () { + await client + .post('/__osham/admin/purge') + .set('x-osham-admin-secret', ADMIN_SECRET) + .send({ pattern: '**', dryRun: true }) + .expect(200); + + const res = await client.get('/__osham/admin/audit').set('x-osham-admin-secret', ADMIN_SECRET); + assert.strictEqual(res.status, 200); + const body = JSON.parse(res.text); + assert.strictEqual(body.ok, true); + assert.ok(Array.isArray(body.data)); + assert.ok(body.data.some(event => event.action === 'admin.purge')); + }); + it('Unknown admin route should return 404 with ok:false', async function () { const res = await client.get('/__osham/admin/unknown-route').set('x-osham-admin-secret', ADMIN_SECRET); assert.strictEqual(res.status, 404); From b8442d63b9f61ecdd5c7abf84253ccebcc532bac Mon Sep 17 00:00:00 2001 From: Ajay Singh Date: Mon, 23 Mar 2026 18:41:42 +0000 Subject: [PATCH 12/50] feat(admin-ui): scaffold frontend shell --- admin-ui/dist/assets/index-BNnQfmSP.css | 1 + admin-ui/dist/assets/index-Bxwtaj6p.js | 68 + admin-ui/dist/index.html | 13 + admin-ui/index.html | 12 + admin-ui/package-lock.json | 1771 +++++++++++++++++++++++ admin-ui/package.json | 23 + admin-ui/src/main.tsx | 11 + admin-ui/src/router.tsx | 38 + admin-ui/src/screens/AuditPage.tsx | 10 + admin-ui/src/screens/ConfigPage.tsx | 17 + admin-ui/src/screens/DashboardPage.tsx | 24 + admin-ui/src/screens/HealthPage.tsx | 10 + admin-ui/src/screens/MetricsPage.tsx | 10 + admin-ui/src/screens/NotFoundPage.tsx | 6 + admin-ui/src/screens/PurgePage.tsx | 10 + admin-ui/src/styles.css | 112 ++ admin-ui/src/ui/Card.tsx | 10 + admin-ui/src/ui/Page.tsx | 11 + admin-ui/src/ui/Sidebar.tsx | 26 + admin-ui/tsconfig.json | 17 + admin-ui/tsconfig.tsbuildinfo | 1 + admin-ui/vite.config.ts | 9 + 22 files changed, 2210 insertions(+) create mode 100644 admin-ui/dist/assets/index-BNnQfmSP.css create mode 100644 admin-ui/dist/assets/index-Bxwtaj6p.js create mode 100644 admin-ui/dist/index.html create mode 100644 admin-ui/index.html create mode 100644 admin-ui/package-lock.json create mode 100644 admin-ui/package.json create mode 100644 admin-ui/src/main.tsx create mode 100644 admin-ui/src/router.tsx create mode 100644 admin-ui/src/screens/AuditPage.tsx create mode 100644 admin-ui/src/screens/ConfigPage.tsx create mode 100644 admin-ui/src/screens/DashboardPage.tsx create mode 100644 admin-ui/src/screens/HealthPage.tsx create mode 100644 admin-ui/src/screens/MetricsPage.tsx create mode 100644 admin-ui/src/screens/NotFoundPage.tsx create mode 100644 admin-ui/src/screens/PurgePage.tsx create mode 100644 admin-ui/src/styles.css create mode 100644 admin-ui/src/ui/Card.tsx create mode 100644 admin-ui/src/ui/Page.tsx create mode 100644 admin-ui/src/ui/Sidebar.tsx create mode 100644 admin-ui/tsconfig.json create mode 100644 admin-ui/tsconfig.tsbuildinfo create mode 100644 admin-ui/vite.config.ts diff --git a/admin-ui/dist/assets/index-BNnQfmSP.css b/admin-ui/dist/assets/index-BNnQfmSP.css new file mode 100644 index 0000000..846ecf4 --- /dev/null +++ b/admin-ui/dist/assets/index-BNnQfmSP.css @@ -0,0 +1 @@ +:root{font-family:Inter,system-ui,sans-serif;color:#e7ecf3;background:#0b1220}*{box-sizing:border-box}body{margin:0;min-width:320px;min-height:100vh;background:linear-gradient(180deg,#0b1220,#111827)}#root{min-height:100vh}.app-shell{display:grid;grid-template-columns:240px 1fr;min-height:100vh}.sidebar{border-right:1px solid #22304a;padding:24px 18px;background:#080f1ceb}.brand{font-size:1.2rem;font-weight:700;margin-bottom:20px}.nav-list{display:flex;flex-direction:column;gap:10px}.nav-link{color:#c2d1e6;text-decoration:none;padding:10px 12px;border-radius:10px}.nav-link.active,.nav-link:hover{background:#1d4ed8;color:#fff}.content-shell{padding:28px}.page{max-width:1080px}.page h1{margin-top:0;margin-bottom:8px}.page p{color:#a8b4c7}.card-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:16px;margin-top:24px}.card{background:#111827e0;border:1px solid #22304a;border-radius:16px;padding:18px}.card h3{margin-top:0}.code-block{margin-top:16px;padding:16px;background:#0f172a;border:1px solid #22304a;border-radius:12px;overflow:auto}@media (max-width: 820px){.app-shell{grid-template-columns:1fr}.sidebar{border-right:0;border-bottom:1px solid #22304a}} diff --git a/admin-ui/dist/assets/index-Bxwtaj6p.js b/admin-ui/dist/assets/index-Bxwtaj6p.js new file mode 100644 index 0000000..96e0514 --- /dev/null +++ b/admin-ui/dist/assets/index-Bxwtaj6p.js @@ -0,0 +1,68 @@ +function yc(e,t){for(var n=0;nr[l]})}}}return Object.freeze(Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}))}(function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const l of document.querySelectorAll('link[rel="modulepreload"]'))r(l);new MutationObserver(l=>{for(const i of l)if(i.type==="childList")for(const o of i.addedNodes)o.tagName==="LINK"&&o.rel==="modulepreload"&&r(o)}).observe(document,{childList:!0,subtree:!0});function n(l){const i={};return l.integrity&&(i.integrity=l.integrity),l.referrerPolicy&&(i.referrerPolicy=l.referrerPolicy),l.crossOrigin==="use-credentials"?i.credentials="include":l.crossOrigin==="anonymous"?i.credentials="omit":i.credentials="same-origin",i}function r(l){if(l.ep)return;l.ep=!0;const i=n(l);fetch(l.href,i)}})();function gc(e){return e&&e.__esModule&&Object.prototype.hasOwnProperty.call(e,"default")?e.default:e}var wc={exports:{}},zi={},Sc={exports:{}},Y={};/** + * @license React + * react.production.min.js + * + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */var hl=Symbol.for("react.element"),pp=Symbol.for("react.portal"),hp=Symbol.for("react.fragment"),mp=Symbol.for("react.strict_mode"),vp=Symbol.for("react.profiler"),yp=Symbol.for("react.provider"),gp=Symbol.for("react.context"),wp=Symbol.for("react.forward_ref"),Sp=Symbol.for("react.suspense"),Ep=Symbol.for("react.memo"),xp=Symbol.for("react.lazy"),Hu=Symbol.iterator;function kp(e){return e===null||typeof e!="object"?null:(e=Hu&&e[Hu]||e["@@iterator"],typeof e=="function"?e:null)}var Ec={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},xc=Object.assign,kc={};function mr(e,t,n){this.props=e,this.context=t,this.refs=kc,this.updater=n||Ec}mr.prototype.isReactComponent={};mr.prototype.setState=function(e,t){if(typeof e!="object"&&typeof e!="function"&&e!=null)throw Error("setState(...): takes an object of state variables to update or a function which returns an object of state variables.");this.updater.enqueueSetState(this,e,t,"setState")};mr.prototype.forceUpdate=function(e){this.updater.enqueueForceUpdate(this,e,"forceUpdate")};function Cc(){}Cc.prototype=mr.prototype;function Na(e,t,n){this.props=e,this.context=t,this.refs=kc,this.updater=n||Ec}var Ta=Na.prototype=new Cc;Ta.constructor=Na;xc(Ta,mr.prototype);Ta.isPureReactComponent=!0;var Vu=Array.isArray,Pc=Object.prototype.hasOwnProperty,Da={current:null},_c={key:!0,ref:!0,__self:!0,__source:!0};function Rc(e,t,n){var r,l={},i=null,o=null;if(t!=null)for(r in t.ref!==void 0&&(o=t.ref),t.key!==void 0&&(i=""+t.key),t)Pc.call(t,r)&&!_c.hasOwnProperty(r)&&(l[r]=t[r]);var a=arguments.length-2;if(a===1)l.children=n;else if(1>>1,ne=N[te];if(0>>1;tel(je,$))Oel(tt,je)?(N[te]=tt,N[Oe]=$,te=Oe):(N[te]=je,N[Ye]=$,te=Ye);else if(Oel(tt,$))N[te]=tt,N[Oe]=$,te=Oe;else break e}}return V}function l(N,V){var $=N.sortIndex-V.sortIndex;return $!==0?$:N.id-V.id}if(typeof performance=="object"&&typeof performance.now=="function"){var i=performance;e.unstable_now=function(){return i.now()}}else{var o=Date,a=o.now();e.unstable_now=function(){return o.now()-a}}var u=[],s=[],d=1,f=null,p=3,w=!1,x=!1,E=!1,T=typeof setTimeout=="function"?setTimeout:null,m=typeof clearTimeout=="function"?clearTimeout:null,c=typeof setImmediate<"u"?setImmediate:null;typeof navigator<"u"&&navigator.scheduling!==void 0&&navigator.scheduling.isInputPending!==void 0&&navigator.scheduling.isInputPending.bind(navigator.scheduling);function v(N){for(var V=n(s);V!==null;){if(V.callback===null)r(s);else if(V.startTime<=N)r(s),V.sortIndex=V.expirationTime,t(u,V);else break;V=n(s)}}function k(N){if(E=!1,v(N),!x)if(n(u)!==null)x=!0,Bt(L);else{var V=n(s);V!==null&&Ht(k,V.startTime-N)}}function L(N,V){x=!1,E&&(E=!1,m(C),C=-1),w=!0;var $=p;try{for(v(V),f=n(u);f!==null&&(!(f.expirationTime>V)||N&&!q());){var te=f.callback;if(typeof te=="function"){f.callback=null,p=f.priorityLevel;var ne=te(f.expirationTime<=V);V=e.unstable_now(),typeof ne=="function"?f.callback=ne:f===n(u)&&r(u),v(V)}else r(u);f=n(u)}if(f!==null)var st=!0;else{var Ye=n(s);Ye!==null&&Ht(k,Ye.startTime-V),st=!1}return st}finally{f=null,p=$,w=!1}}var z=!1,y=null,C=-1,H=5,D=-1;function q(){return!(e.unstable_now()-DN||125te?(N.sortIndex=$,t(s,N),n(u)===null&&N===n(s)&&(E?(m(C),C=-1):E=!0,Ht(k,$-te))):(N.sortIndex=ne,t(u,N),x||w||(x=!0,Bt(L))),N},e.unstable_shouldYield=q,e.unstable_wrapCallback=function(N){var V=p;return function(){var $=p;p=V;try{return N.apply(this,arguments)}finally{p=$}}}})(zc);Mc.exports=zc;var Op=Mc.exports;/** + * @license React + * react-dom.production.min.js + * + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */var Fp=_,qe=Op;function R(e){for(var t="https://reactjs.org/docs/error-decoder.html?invariant="+e,n=1;n"u"||typeof window.document>"u"||typeof window.document.createElement>"u"),zo=Object.prototype.hasOwnProperty,Ip=/^[:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD][:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD\-.0-9\u00B7\u0300-\u036F\u203F-\u2040]*$/,$u={},Qu={};function Up(e){return zo.call(Qu,e)?!0:zo.call($u,e)?!1:Ip.test(e)?Qu[e]=!0:($u[e]=!0,!1)}function Ap(e,t,n,r){if(n!==null&&n.type===0)return!1;switch(typeof t){case"function":case"symbol":return!0;case"boolean":return r?!1:n!==null?!n.acceptsBooleans:(e=e.toLowerCase().slice(0,5),e!=="data-"&&e!=="aria-");default:return!1}}function Bp(e,t,n,r){if(t===null||typeof t>"u"||Ap(e,t,n,r))return!0;if(r)return!1;if(n!==null)switch(n.type){case 3:return!t;case 4:return t===!1;case 5:return isNaN(t);case 6:return isNaN(t)||1>t}return!1}function Be(e,t,n,r,l,i,o){this.acceptsBooleans=t===2||t===3||t===4,this.attributeName=r,this.attributeNamespace=l,this.mustUseProperty=n,this.propertyName=e,this.type=t,this.sanitizeURL=i,this.removeEmptyString=o}var Ne={};"children dangerouslySetInnerHTML defaultValue defaultChecked innerHTML suppressContentEditableWarning suppressHydrationWarning style".split(" ").forEach(function(e){Ne[e]=new Be(e,0,!1,e,null,!1,!1)});[["acceptCharset","accept-charset"],["className","class"],["htmlFor","for"],["httpEquiv","http-equiv"]].forEach(function(e){var t=e[0];Ne[t]=new Be(t,1,!1,e[1],null,!1,!1)});["contentEditable","draggable","spellCheck","value"].forEach(function(e){Ne[e]=new Be(e,2,!1,e.toLowerCase(),null,!1,!1)});["autoReverse","externalResourcesRequired","focusable","preserveAlpha"].forEach(function(e){Ne[e]=new Be(e,2,!1,e,null,!1,!1)});"allowFullScreen async autoFocus autoPlay controls default defer disabled disablePictureInPicture disableRemotePlayback formNoValidate hidden loop noModule noValidate open playsInline readOnly required reversed scoped seamless itemScope".split(" ").forEach(function(e){Ne[e]=new Be(e,3,!1,e.toLowerCase(),null,!1,!1)});["checked","multiple","muted","selected"].forEach(function(e){Ne[e]=new Be(e,3,!0,e,null,!1,!1)});["capture","download"].forEach(function(e){Ne[e]=new Be(e,4,!1,e,null,!1,!1)});["cols","rows","size","span"].forEach(function(e){Ne[e]=new Be(e,6,!1,e,null,!1,!1)});["rowSpan","start"].forEach(function(e){Ne[e]=new Be(e,5,!1,e.toLowerCase(),null,!1,!1)});var za=/[\-:]([a-z])/g;function ja(e){return e[1].toUpperCase()}"accent-height alignment-baseline arabic-form baseline-shift cap-height clip-path clip-rule color-interpolation color-interpolation-filters color-profile color-rendering dominant-baseline enable-background fill-opacity fill-rule flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-name glyph-orientation-horizontal glyph-orientation-vertical horiz-adv-x horiz-origin-x image-rendering letter-spacing lighting-color marker-end marker-mid marker-start overline-position overline-thickness paint-order panose-1 pointer-events rendering-intent shape-rendering stop-color stop-opacity strikethrough-position strikethrough-thickness stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width text-anchor text-decoration text-rendering underline-position underline-thickness unicode-bidi unicode-range units-per-em v-alphabetic v-hanging v-ideographic v-mathematical vector-effect vert-adv-y vert-origin-x vert-origin-y word-spacing writing-mode xmlns:xlink x-height".split(" ").forEach(function(e){var t=e.replace(za,ja);Ne[t]=new Be(t,1,!1,e,null,!1,!1)});"xlink:actuate xlink:arcrole xlink:role xlink:show xlink:title xlink:type".split(" ").forEach(function(e){var t=e.replace(za,ja);Ne[t]=new Be(t,1,!1,e,"http://www.w3.org/1999/xlink",!1,!1)});["xml:base","xml:lang","xml:space"].forEach(function(e){var t=e.replace(za,ja);Ne[t]=new Be(t,1,!1,e,"http://www.w3.org/XML/1998/namespace",!1,!1)});["tabIndex","crossOrigin"].forEach(function(e){Ne[e]=new Be(e,1,!1,e.toLowerCase(),null,!1,!1)});Ne.xlinkHref=new Be("xlinkHref",1,!1,"xlink:href","http://www.w3.org/1999/xlink",!0,!1);["src","href","action","formAction"].forEach(function(e){Ne[e]=new Be(e,1,!1,e.toLowerCase(),null,!0,!0)});function Oa(e,t,n,r){var l=Ne.hasOwnProperty(t)?Ne[t]:null;(l!==null?l.type!==0:r||!(2a||l[o]!==i[a]){var u=` +`+l[o].replace(" at new "," at ");return e.displayName&&u.includes("")&&(u=u.replace("",e.displayName)),u}while(1<=o&&0<=a);break}}}finally{lo=!1,Error.prepareStackTrace=n}return(e=e?e.displayName||e.name:"")?Or(e):""}function Hp(e){switch(e.tag){case 5:return Or(e.type);case 16:return Or("Lazy");case 13:return Or("Suspense");case 19:return Or("SuspenseList");case 0:case 2:case 15:return e=io(e.type,!1),e;case 11:return e=io(e.type.render,!1),e;case 1:return e=io(e.type,!0),e;default:return""}}function Io(e){if(e==null)return null;if(typeof e=="function")return e.displayName||e.name||null;if(typeof e=="string")return e;switch(e){case $n:return"Fragment";case Wn:return"Portal";case jo:return"Profiler";case Fa:return"StrictMode";case Oo:return"Suspense";case Fo:return"SuspenseList"}if(typeof e=="object")switch(e.$$typeof){case Fc:return(e.displayName||"Context")+".Consumer";case Oc:return(e._context.displayName||"Context")+".Provider";case Ia:var t=e.render;return e=e.displayName,e||(e=t.displayName||t.name||"",e=e!==""?"ForwardRef("+e+")":"ForwardRef"),e;case Ua:return t=e.displayName||null,t!==null?t:Io(e.type)||"Memo";case Kt:t=e._payload,e=e._init;try{return Io(e(t))}catch{}}return null}function Vp(e){var t=e.type;switch(e.tag){case 24:return"Cache";case 9:return(t.displayName||"Context")+".Consumer";case 10:return(t._context.displayName||"Context")+".Provider";case 18:return"DehydratedFragment";case 11:return e=t.render,e=e.displayName||e.name||"",t.displayName||(e!==""?"ForwardRef("+e+")":"ForwardRef");case 7:return"Fragment";case 5:return t;case 4:return"Portal";case 3:return"Root";case 6:return"Text";case 16:return Io(t);case 8:return t===Fa?"StrictMode":"Mode";case 22:return"Offscreen";case 12:return"Profiler";case 21:return"Scope";case 13:return"Suspense";case 19:return"SuspenseList";case 25:return"TracingMarker";case 1:case 0:case 17:case 2:case 14:case 15:if(typeof t=="function")return t.displayName||t.name||null;if(typeof t=="string")return t}return null}function un(e){switch(typeof e){case"boolean":case"number":case"string":case"undefined":return e;case"object":return e;default:return""}}function Uc(e){var t=e.type;return(e=e.nodeName)&&e.toLowerCase()==="input"&&(t==="checkbox"||t==="radio")}function Wp(e){var t=Uc(e)?"checked":"value",n=Object.getOwnPropertyDescriptor(e.constructor.prototype,t),r=""+e[t];if(!e.hasOwnProperty(t)&&typeof n<"u"&&typeof n.get=="function"&&typeof n.set=="function"){var l=n.get,i=n.set;return Object.defineProperty(e,t,{configurable:!0,get:function(){return l.call(this)},set:function(o){r=""+o,i.call(this,o)}}),Object.defineProperty(e,t,{enumerable:n.enumerable}),{getValue:function(){return r},setValue:function(o){r=""+o},stopTracking:function(){e._valueTracker=null,delete e[t]}}}}function Nl(e){e._valueTracker||(e._valueTracker=Wp(e))}function Ac(e){if(!e)return!1;var t=e._valueTracker;if(!t)return!0;var n=t.getValue(),r="";return e&&(r=Uc(e)?e.checked?"true":"false":e.value),e=r,e!==n?(t.setValue(e),!0):!1}function ii(e){if(e=e||(typeof document<"u"?document:void 0),typeof e>"u")return null;try{return e.activeElement||e.body}catch{return e.body}}function Uo(e,t){var n=t.checked;return de({},t,{defaultChecked:void 0,defaultValue:void 0,value:void 0,checked:n??e._wrapperState.initialChecked})}function Yu(e,t){var n=t.defaultValue==null?"":t.defaultValue,r=t.checked!=null?t.checked:t.defaultChecked;n=un(t.value!=null?t.value:n),e._wrapperState={initialChecked:r,initialValue:n,controlled:t.type==="checkbox"||t.type==="radio"?t.checked!=null:t.value!=null}}function Bc(e,t){t=t.checked,t!=null&&Oa(e,"checked",t,!1)}function Ao(e,t){Bc(e,t);var n=un(t.value),r=t.type;if(n!=null)r==="number"?(n===0&&e.value===""||e.value!=n)&&(e.value=""+n):e.value!==""+n&&(e.value=""+n);else if(r==="submit"||r==="reset"){e.removeAttribute("value");return}t.hasOwnProperty("value")?Bo(e,t.type,n):t.hasOwnProperty("defaultValue")&&Bo(e,t.type,un(t.defaultValue)),t.checked==null&&t.defaultChecked!=null&&(e.defaultChecked=!!t.defaultChecked)}function Xu(e,t,n){if(t.hasOwnProperty("value")||t.hasOwnProperty("defaultValue")){var r=t.type;if(!(r!=="submit"&&r!=="reset"||t.value!==void 0&&t.value!==null))return;t=""+e._wrapperState.initialValue,n||t===e.value||(e.value=t),e.defaultValue=t}n=e.name,n!==""&&(e.name=""),e.defaultChecked=!!e._wrapperState.initialChecked,n!==""&&(e.name=n)}function Bo(e,t,n){(t!=="number"||ii(e.ownerDocument)!==e)&&(n==null?e.defaultValue=""+e._wrapperState.initialValue:e.defaultValue!==""+n&&(e.defaultValue=""+n))}var Fr=Array.isArray;function tr(e,t,n,r){if(e=e.options,t){t={};for(var l=0;l"+t.valueOf().toString()+"",t=Tl.firstChild;e.firstChild;)e.removeChild(e.firstChild);for(;t.firstChild;)e.appendChild(t.firstChild)}});function Jr(e,t){if(t){var n=e.firstChild;if(n&&n===e.lastChild&&n.nodeType===3){n.nodeValue=t;return}}e.textContent=t}var Br={animationIterationCount:!0,aspectRatio:!0,borderImageOutset:!0,borderImageSlice:!0,borderImageWidth:!0,boxFlex:!0,boxFlexGroup:!0,boxOrdinalGroup:!0,columnCount:!0,columns:!0,flex:!0,flexGrow:!0,flexPositive:!0,flexShrink:!0,flexNegative:!0,flexOrder:!0,gridArea:!0,gridRow:!0,gridRowEnd:!0,gridRowSpan:!0,gridRowStart:!0,gridColumn:!0,gridColumnEnd:!0,gridColumnSpan:!0,gridColumnStart:!0,fontWeight:!0,lineClamp:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,tabSize:!0,widows:!0,zIndex:!0,zoom:!0,fillOpacity:!0,floodOpacity:!0,stopOpacity:!0,strokeDasharray:!0,strokeDashoffset:!0,strokeMiterlimit:!0,strokeOpacity:!0,strokeWidth:!0},$p=["Webkit","ms","Moz","O"];Object.keys(Br).forEach(function(e){$p.forEach(function(t){t=t+e.charAt(0).toUpperCase()+e.substring(1),Br[t]=Br[e]})});function $c(e,t,n){return t==null||typeof t=="boolean"||t===""?"":n||typeof t!="number"||t===0||Br.hasOwnProperty(e)&&Br[e]?(""+t).trim():t+"px"}function Qc(e,t){e=e.style;for(var n in t)if(t.hasOwnProperty(n)){var r=n.indexOf("--")===0,l=$c(n,t[n],r);n==="float"&&(n="cssFloat"),r?e.setProperty(n,l):e[n]=l}}var Qp=de({menuitem:!0},{area:!0,base:!0,br:!0,col:!0,embed:!0,hr:!0,img:!0,input:!0,keygen:!0,link:!0,meta:!0,param:!0,source:!0,track:!0,wbr:!0});function Wo(e,t){if(t){if(Qp[e]&&(t.children!=null||t.dangerouslySetInnerHTML!=null))throw Error(R(137,e));if(t.dangerouslySetInnerHTML!=null){if(t.children!=null)throw Error(R(60));if(typeof t.dangerouslySetInnerHTML!="object"||!("__html"in t.dangerouslySetInnerHTML))throw Error(R(61))}if(t.style!=null&&typeof t.style!="object")throw Error(R(62))}}function $o(e,t){if(e.indexOf("-")===-1)return typeof t.is=="string";switch(e){case"annotation-xml":case"color-profile":case"font-face":case"font-face-src":case"font-face-uri":case"font-face-format":case"font-face-name":case"missing-glyph":return!1;default:return!0}}var Qo=null;function Aa(e){return e=e.target||e.srcElement||window,e.correspondingUseElement&&(e=e.correspondingUseElement),e.nodeType===3?e.parentNode:e}var Ko=null,nr=null,rr=null;function Zu(e){if(e=yl(e)){if(typeof Ko!="function")throw Error(R(280));var t=e.stateNode;t&&(t=Ui(t),Ko(e.stateNode,e.type,t))}}function Kc(e){nr?rr?rr.push(e):rr=[e]:nr=e}function Yc(){if(nr){var e=nr,t=rr;if(rr=nr=null,Zu(e),t)for(e=0;e>>=0,e===0?32:31-(nh(e)/rh|0)|0}var Dl=64,Ml=4194304;function Ir(e){switch(e&-e){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return e&4194240;case 4194304:case 8388608:case 16777216:case 33554432:case 67108864:return e&130023424;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 1073741824;default:return e}}function si(e,t){var n=e.pendingLanes;if(n===0)return 0;var r=0,l=e.suspendedLanes,i=e.pingedLanes,o=n&268435455;if(o!==0){var a=o&~l;a!==0?r=Ir(a):(i&=o,i!==0&&(r=Ir(i)))}else o=n&~l,o!==0?r=Ir(o):i!==0&&(r=Ir(i));if(r===0)return 0;if(t!==0&&t!==r&&!(t&l)&&(l=r&-r,i=t&-t,l>=i||l===16&&(i&4194240)!==0))return t;if(r&4&&(r|=n&16),t=e.entangledLanes,t!==0)for(e=e.entanglements,t&=r;0n;n++)t.push(e);return t}function ml(e,t,n){e.pendingLanes|=t,t!==536870912&&(e.suspendedLanes=0,e.pingedLanes=0),e=e.eventTimes,t=31-mt(t),e[t]=n}function ah(e,t){var n=e.pendingLanes&~t;e.pendingLanes=t,e.suspendedLanes=0,e.pingedLanes=0,e.expiredLanes&=t,e.mutableReadLanes&=t,e.entangledLanes&=t,t=e.entanglements;var r=e.eventTimes;for(e=e.expirationTimes;0=Vr),os=" ",as=!1;function hf(e,t){switch(e){case"keyup":return Oh.indexOf(t.keyCode)!==-1;case"keydown":return t.keyCode!==229;case"keypress":case"mousedown":case"focusout":return!0;default:return!1}}function mf(e){return e=e.detail,typeof e=="object"&&"data"in e?e.data:null}var Qn=!1;function Ih(e,t){switch(e){case"compositionend":return mf(t);case"keypress":return t.which!==32?null:(as=!0,os);case"textInput":return e=t.data,e===os&&as?null:e;default:return null}}function Uh(e,t){if(Qn)return e==="compositionend"||!Ya&&hf(e,t)?(e=df(),Gl=$a=Jt=null,Qn=!1,e):null;switch(e){case"paste":return null;case"keypress":if(!(t.ctrlKey||t.altKey||t.metaKey)||t.ctrlKey&&t.altKey){if(t.char&&1=t)return{node:n,offset:t-e};e=r}e:{for(;n;){if(n.nextSibling){n=n.nextSibling;break e}n=n.parentNode}n=void 0}n=fs(n)}}function wf(e,t){return e&&t?e===t?!0:e&&e.nodeType===3?!1:t&&t.nodeType===3?wf(e,t.parentNode):"contains"in e?e.contains(t):e.compareDocumentPosition?!!(e.compareDocumentPosition(t)&16):!1:!1}function Sf(){for(var e=window,t=ii();t instanceof e.HTMLIFrameElement;){try{var n=typeof t.contentWindow.location.href=="string"}catch{n=!1}if(n)e=t.contentWindow;else break;t=ii(e.document)}return t}function Xa(e){var t=e&&e.nodeName&&e.nodeName.toLowerCase();return t&&(t==="input"&&(e.type==="text"||e.type==="search"||e.type==="tel"||e.type==="url"||e.type==="password")||t==="textarea"||e.contentEditable==="true")}function Yh(e){var t=Sf(),n=e.focusedElem,r=e.selectionRange;if(t!==n&&n&&n.ownerDocument&&wf(n.ownerDocument.documentElement,n)){if(r!==null&&Xa(n)){if(t=r.start,e=r.end,e===void 0&&(e=t),"selectionStart"in n)n.selectionStart=t,n.selectionEnd=Math.min(e,n.value.length);else if(e=(t=n.ownerDocument||document)&&t.defaultView||window,e.getSelection){e=e.getSelection();var l=n.textContent.length,i=Math.min(r.start,l);r=r.end===void 0?i:Math.min(r.end,l),!e.extend&&i>r&&(l=r,r=i,i=l),l=ds(n,i);var o=ds(n,r);l&&o&&(e.rangeCount!==1||e.anchorNode!==l.node||e.anchorOffset!==l.offset||e.focusNode!==o.node||e.focusOffset!==o.offset)&&(t=t.createRange(),t.setStart(l.node,l.offset),e.removeAllRanges(),i>r?(e.addRange(t),e.extend(o.node,o.offset)):(t.setEnd(o.node,o.offset),e.addRange(t)))}}for(t=[],e=n;e=e.parentNode;)e.nodeType===1&&t.push({element:e,left:e.scrollLeft,top:e.scrollTop});for(typeof n.focus=="function"&&n.focus(),n=0;n=document.documentMode,Kn=null,qo=null,$r=null,bo=!1;function ps(e,t,n){var r=n.window===n?n.document:n.nodeType===9?n:n.ownerDocument;bo||Kn==null||Kn!==ii(r)||(r=Kn,"selectionStart"in r&&Xa(r)?r={start:r.selectionStart,end:r.selectionEnd}:(r=(r.ownerDocument&&r.ownerDocument.defaultView||window).getSelection(),r={anchorNode:r.anchorNode,anchorOffset:r.anchorOffset,focusNode:r.focusNode,focusOffset:r.focusOffset}),$r&&nl($r,r)||($r=r,r=di(qo,"onSelect"),0Gn||(e.current=ia[Gn],ia[Gn]=null,Gn--)}function ie(e,t){Gn++,ia[Gn]=e.current,e.current=t}var sn={},ze=fn(sn),$e=fn(!1),Rn=sn;function ur(e,t){var n=e.type.contextTypes;if(!n)return sn;var r=e.stateNode;if(r&&r.__reactInternalMemoizedUnmaskedChildContext===t)return r.__reactInternalMemoizedMaskedChildContext;var l={},i;for(i in n)l[i]=t[i];return r&&(e=e.stateNode,e.__reactInternalMemoizedUnmaskedChildContext=t,e.__reactInternalMemoizedMaskedChildContext=l),l}function Qe(e){return e=e.childContextTypes,e!=null}function hi(){ae($e),ae(ze)}function Ss(e,t,n){if(ze.current!==sn)throw Error(R(168));ie(ze,t),ie($e,n)}function Nf(e,t,n){var r=e.stateNode;if(t=t.childContextTypes,typeof r.getChildContext!="function")return n;r=r.getChildContext();for(var l in r)if(!(l in t))throw Error(R(108,Vp(e)||"Unknown",l));return de({},n,r)}function mi(e){return e=(e=e.stateNode)&&e.__reactInternalMemoizedMergedChildContext||sn,Rn=ze.current,ie(ze,e),ie($e,$e.current),!0}function Es(e,t,n){var r=e.stateNode;if(!r)throw Error(R(169));n?(e=Nf(e,t,Rn),r.__reactInternalMemoizedMergedChildContext=e,ae($e),ae(ze),ie(ze,e)):ae($e),ie($e,n)}var Rt=null,Ai=!1,So=!1;function Tf(e){Rt===null?Rt=[e]:Rt.push(e)}function im(e){Ai=!0,Tf(e)}function dn(){if(!So&&Rt!==null){So=!0;var e=0,t=ee;try{var n=Rt;for(ee=1;e>=o,l-=o,Lt=1<<32-mt(t)+l|n<C?(H=y,y=null):H=y.sibling;var D=p(m,y,v[C],k);if(D===null){y===null&&(y=H);break}e&&y&&D.alternate===null&&t(m,y),c=i(D,c,C),z===null?L=D:z.sibling=D,z=D,y=H}if(C===v.length)return n(m,y),se&&yn(m,C),L;if(y===null){for(;CC?(H=y,y=null):H=y.sibling;var q=p(m,y,D.value,k);if(q===null){y===null&&(y=H);break}e&&y&&q.alternate===null&&t(m,y),c=i(q,c,C),z===null?L=q:z.sibling=q,z=q,y=H}if(D.done)return n(m,y),se&&yn(m,C),L;if(y===null){for(;!D.done;C++,D=v.next())D=f(m,D.value,k),D!==null&&(c=i(D,c,C),z===null?L=D:z.sibling=D,z=D);return se&&yn(m,C),L}for(y=r(m,y);!D.done;C++,D=v.next())D=w(y,m,C,D.value,k),D!==null&&(e&&D.alternate!==null&&y.delete(D.key===null?C:D.key),c=i(D,c,C),z===null?L=D:z.sibling=D,z=D);return e&&y.forEach(function(re){return t(m,re)}),se&&yn(m,C),L}function T(m,c,v,k){if(typeof v=="object"&&v!==null&&v.type===$n&&v.key===null&&(v=v.props.children),typeof v=="object"&&v!==null){switch(v.$$typeof){case Ll:e:{for(var L=v.key,z=c;z!==null;){if(z.key===L){if(L=v.type,L===$n){if(z.tag===7){n(m,z.sibling),c=l(z,v.props.children),c.return=m,m=c;break e}}else if(z.elementType===L||typeof L=="object"&&L!==null&&L.$$typeof===Kt&&Cs(L)===z.type){n(m,z.sibling),c=l(z,v.props),c.ref=Nr(m,z,v),c.return=m,m=c;break e}n(m,z);break}else t(m,z);z=z.sibling}v.type===$n?(c=_n(v.props.children,m.mode,k,v.key),c.return=m,m=c):(k=ri(v.type,v.key,v.props,null,m.mode,k),k.ref=Nr(m,c,v),k.return=m,m=k)}return o(m);case Wn:e:{for(z=v.key;c!==null;){if(c.key===z)if(c.tag===4&&c.stateNode.containerInfo===v.containerInfo&&c.stateNode.implementation===v.implementation){n(m,c.sibling),c=l(c,v.children||[]),c.return=m,m=c;break e}else{n(m,c);break}else t(m,c);c=c.sibling}c=Lo(v,m.mode,k),c.return=m,m=c}return o(m);case Kt:return z=v._init,T(m,c,z(v._payload),k)}if(Fr(v))return x(m,c,v,k);if(Cr(v))return E(m,c,v,k);Al(m,v)}return typeof v=="string"&&v!==""||typeof v=="number"?(v=""+v,c!==null&&c.tag===6?(n(m,c.sibling),c=l(c,v),c.return=m,m=c):(n(m,c),c=Ro(v,m.mode,k),c.return=m,m=c),o(m)):n(m,c)}return T}var cr=jf(!0),Of=jf(!1),gi=fn(null),wi=null,qn=null,qa=null;function ba(){qa=qn=wi=null}function eu(e){var t=gi.current;ae(gi),e._currentValue=t}function ua(e,t,n){for(;e!==null;){var r=e.alternate;if((e.childLanes&t)!==t?(e.childLanes|=t,r!==null&&(r.childLanes|=t)):r!==null&&(r.childLanes&t)!==t&&(r.childLanes|=t),e===n)break;e=e.return}}function ir(e,t){wi=e,qa=qn=null,e=e.dependencies,e!==null&&e.firstContext!==null&&(e.lanes&t&&(We=!0),e.firstContext=null)}function ot(e){var t=e._currentValue;if(qa!==e)if(e={context:e,memoizedValue:t,next:null},qn===null){if(wi===null)throw Error(R(308));qn=e,wi.dependencies={lanes:0,firstContext:e}}else qn=qn.next=e;return t}var xn=null;function tu(e){xn===null?xn=[e]:xn.push(e)}function Ff(e,t,n,r){var l=t.interleaved;return l===null?(n.next=n,tu(t)):(n.next=l.next,l.next=n),t.interleaved=n,jt(e,r)}function jt(e,t){e.lanes|=t;var n=e.alternate;for(n!==null&&(n.lanes|=t),n=e,e=e.return;e!==null;)e.childLanes|=t,n=e.alternate,n!==null&&(n.childLanes|=t),n=e,e=e.return;return n.tag===3?n.stateNode:null}var Yt=!1;function nu(e){e.updateQueue={baseState:e.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,interleaved:null,lanes:0},effects:null}}function If(e,t){e=e.updateQueue,t.updateQueue===e&&(t.updateQueue={baseState:e.baseState,firstBaseUpdate:e.firstBaseUpdate,lastBaseUpdate:e.lastBaseUpdate,shared:e.shared,effects:e.effects})}function Tt(e,t){return{eventTime:e,lane:t,tag:0,payload:null,callback:null,next:null}}function rn(e,t,n){var r=e.updateQueue;if(r===null)return null;if(r=r.shared,G&2){var l=r.pending;return l===null?t.next=t:(t.next=l.next,l.next=t),r.pending=t,jt(e,n)}return l=r.interleaved,l===null?(t.next=t,tu(r)):(t.next=l.next,l.next=t),r.interleaved=t,jt(e,n)}function Zl(e,t,n){if(t=t.updateQueue,t!==null&&(t=t.shared,(n&4194240)!==0)){var r=t.lanes;r&=e.pendingLanes,n|=r,t.lanes=n,Ha(e,n)}}function Ps(e,t){var n=e.updateQueue,r=e.alternate;if(r!==null&&(r=r.updateQueue,n===r)){var l=null,i=null;if(n=n.firstBaseUpdate,n!==null){do{var o={eventTime:n.eventTime,lane:n.lane,tag:n.tag,payload:n.payload,callback:n.callback,next:null};i===null?l=i=o:i=i.next=o,n=n.next}while(n!==null);i===null?l=i=t:i=i.next=t}else l=i=t;n={baseState:r.baseState,firstBaseUpdate:l,lastBaseUpdate:i,shared:r.shared,effects:r.effects},e.updateQueue=n;return}e=n.lastBaseUpdate,e===null?n.firstBaseUpdate=t:e.next=t,n.lastBaseUpdate=t}function Si(e,t,n,r){var l=e.updateQueue;Yt=!1;var i=l.firstBaseUpdate,o=l.lastBaseUpdate,a=l.shared.pending;if(a!==null){l.shared.pending=null;var u=a,s=u.next;u.next=null,o===null?i=s:o.next=s,o=u;var d=e.alternate;d!==null&&(d=d.updateQueue,a=d.lastBaseUpdate,a!==o&&(a===null?d.firstBaseUpdate=s:a.next=s,d.lastBaseUpdate=u))}if(i!==null){var f=l.baseState;o=0,d=s=u=null,a=i;do{var p=a.lane,w=a.eventTime;if((r&p)===p){d!==null&&(d=d.next={eventTime:w,lane:0,tag:a.tag,payload:a.payload,callback:a.callback,next:null});e:{var x=e,E=a;switch(p=t,w=n,E.tag){case 1:if(x=E.payload,typeof x=="function"){f=x.call(w,f,p);break e}f=x;break e;case 3:x.flags=x.flags&-65537|128;case 0:if(x=E.payload,p=typeof x=="function"?x.call(w,f,p):x,p==null)break e;f=de({},f,p);break e;case 2:Yt=!0}}a.callback!==null&&a.lane!==0&&(e.flags|=64,p=l.effects,p===null?l.effects=[a]:p.push(a))}else w={eventTime:w,lane:p,tag:a.tag,payload:a.payload,callback:a.callback,next:null},d===null?(s=d=w,u=f):d=d.next=w,o|=p;if(a=a.next,a===null){if(a=l.shared.pending,a===null)break;p=a,a=p.next,p.next=null,l.lastBaseUpdate=p,l.shared.pending=null}}while(!0);if(d===null&&(u=f),l.baseState=u,l.firstBaseUpdate=s,l.lastBaseUpdate=d,t=l.shared.interleaved,t!==null){l=t;do o|=l.lane,l=l.next;while(l!==t)}else i===null&&(l.shared.lanes=0);Tn|=o,e.lanes=o,e.memoizedState=f}}function _s(e,t,n){if(e=t.effects,t.effects=null,e!==null)for(t=0;tn?n:4,e(!0);var r=xo.transition;xo.transition={};try{e(!1),t()}finally{ee=n,xo.transition=r}}function ed(){return at().memoizedState}function sm(e,t,n){var r=on(e);if(n={lane:r,action:n,hasEagerState:!1,eagerState:null,next:null},td(e))nd(t,n);else if(n=Ff(e,t,n,r),n!==null){var l=Ue();vt(n,e,r,l),rd(n,t,r)}}function cm(e,t,n){var r=on(e),l={lane:r,action:n,hasEagerState:!1,eagerState:null,next:null};if(td(e))nd(t,l);else{var i=e.alternate;if(e.lanes===0&&(i===null||i.lanes===0)&&(i=t.lastRenderedReducer,i!==null))try{var o=t.lastRenderedState,a=i(o,n);if(l.hasEagerState=!0,l.eagerState=a,yt(a,o)){var u=t.interleaved;u===null?(l.next=l,tu(t)):(l.next=u.next,u.next=l),t.interleaved=l;return}}catch{}finally{}n=Ff(e,t,l,r),n!==null&&(l=Ue(),vt(n,e,r,l),rd(n,t,r))}}function td(e){var t=e.alternate;return e===fe||t!==null&&t===fe}function nd(e,t){Qr=xi=!0;var n=e.pending;n===null?t.next=t:(t.next=n.next,n.next=t),e.pending=t}function rd(e,t,n){if(n&4194240){var r=t.lanes;r&=e.pendingLanes,n|=r,t.lanes=n,Ha(e,n)}}var ki={readContext:ot,useCallback:Te,useContext:Te,useEffect:Te,useImperativeHandle:Te,useInsertionEffect:Te,useLayoutEffect:Te,useMemo:Te,useReducer:Te,useRef:Te,useState:Te,useDebugValue:Te,useDeferredValue:Te,useTransition:Te,useMutableSource:Te,useSyncExternalStore:Te,useId:Te,unstable_isNewReconciler:!1},fm={readContext:ot,useCallback:function(e,t){return St().memoizedState=[e,t===void 0?null:t],e},useContext:ot,useEffect:Ls,useImperativeHandle:function(e,t,n){return n=n!=null?n.concat([e]):null,bl(4194308,4,Gf.bind(null,t,e),n)},useLayoutEffect:function(e,t){return bl(4194308,4,e,t)},useInsertionEffect:function(e,t){return bl(4,2,e,t)},useMemo:function(e,t){var n=St();return t=t===void 0?null:t,e=e(),n.memoizedState=[e,t],e},useReducer:function(e,t,n){var r=St();return t=n!==void 0?n(t):t,r.memoizedState=r.baseState=t,e={pending:null,interleaved:null,lanes:0,dispatch:null,lastRenderedReducer:e,lastRenderedState:t},r.queue=e,e=e.dispatch=sm.bind(null,fe,e),[r.memoizedState,e]},useRef:function(e){var t=St();return e={current:e},t.memoizedState=e},useState:Rs,useDebugValue:cu,useDeferredValue:function(e){return St().memoizedState=e},useTransition:function(){var e=Rs(!1),t=e[0];return e=um.bind(null,e[1]),St().memoizedState=e,[t,e]},useMutableSource:function(){},useSyncExternalStore:function(e,t,n){var r=fe,l=St();if(se){if(n===void 0)throw Error(R(407));n=n()}else{if(n=t(),Ce===null)throw Error(R(349));Nn&30||Hf(r,t,n)}l.memoizedState=n;var i={value:n,getSnapshot:t};return l.queue=i,Ls(Wf.bind(null,r,i,e),[e]),r.flags|=2048,cl(9,Vf.bind(null,r,i,n,t),void 0,null),n},useId:function(){var e=St(),t=Ce.identifierPrefix;if(se){var n=Nt,r=Lt;n=(r&~(1<<32-mt(r)-1)).toString(32)+n,t=":"+t+"R"+n,n=ul++,0<\/script>",e=e.removeChild(e.firstChild)):typeof r.is=="string"?e=o.createElement(n,{is:r.is}):(e=o.createElement(n),n==="select"&&(o=e,r.multiple?o.multiple=!0:r.size&&(o.size=r.size))):e=o.createElementNS(e,n),e[Et]=t,e[il]=r,pd(e,t,!1,!1),t.stateNode=e;e:{switch(o=$o(n,r),n){case"dialog":oe("cancel",e),oe("close",e),l=r;break;case"iframe":case"object":case"embed":oe("load",e),l=r;break;case"video":case"audio":for(l=0;lpr&&(t.flags|=128,r=!0,Tr(i,!1),t.lanes=4194304)}else{if(!r)if(e=Ei(o),e!==null){if(t.flags|=128,r=!0,n=e.updateQueue,n!==null&&(t.updateQueue=n,t.flags|=4),Tr(i,!0),i.tail===null&&i.tailMode==="hidden"&&!o.alternate&&!se)return De(t),null}else 2*ve()-i.renderingStartTime>pr&&n!==1073741824&&(t.flags|=128,r=!0,Tr(i,!1),t.lanes=4194304);i.isBackwards?(o.sibling=t.child,t.child=o):(n=i.last,n!==null?n.sibling=o:t.child=o,i.last=o)}return i.tail!==null?(t=i.tail,i.rendering=t,i.tail=t.sibling,i.renderingStartTime=ve(),t.sibling=null,n=ce.current,ie(ce,r?n&1|2:n&1),t):(De(t),null);case 22:case 23:return vu(),r=t.memoizedState!==null,e!==null&&e.memoizedState!==null!==r&&(t.flags|=8192),r&&t.mode&1?Xe&1073741824&&(De(t),t.subtreeFlags&6&&(t.flags|=8192)):De(t),null;case 24:return null;case 25:return null}throw Error(R(156,t.tag))}function wm(e,t){switch(Ja(t),t.tag){case 1:return Qe(t.type)&&hi(),e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 3:return fr(),ae($e),ae(ze),iu(),e=t.flags,e&65536&&!(e&128)?(t.flags=e&-65537|128,t):null;case 5:return lu(t),null;case 13:if(ae(ce),e=t.memoizedState,e!==null&&e.dehydrated!==null){if(t.alternate===null)throw Error(R(340));sr()}return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 19:return ae(ce),null;case 4:return fr(),null;case 10:return eu(t.type._context),null;case 22:case 23:return vu(),null;case 24:return null;default:return null}}var Hl=!1,Me=!1,Sm=typeof WeakSet=="function"?WeakSet:Set,j=null;function bn(e,t){var n=e.ref;if(n!==null)if(typeof n=="function")try{n(null)}catch(r){me(e,t,r)}else n.current=null}function ya(e,t,n){try{n()}catch(r){me(e,t,r)}}var As=!1;function Em(e,t){if(ea=ci,e=Sf(),Xa(e)){if("selectionStart"in e)var n={start:e.selectionStart,end:e.selectionEnd};else e:{n=(n=e.ownerDocument)&&n.defaultView||window;var r=n.getSelection&&n.getSelection();if(r&&r.rangeCount!==0){n=r.anchorNode;var l=r.anchorOffset,i=r.focusNode;r=r.focusOffset;try{n.nodeType,i.nodeType}catch{n=null;break e}var o=0,a=-1,u=-1,s=0,d=0,f=e,p=null;t:for(;;){for(var w;f!==n||l!==0&&f.nodeType!==3||(a=o+l),f!==i||r!==0&&f.nodeType!==3||(u=o+r),f.nodeType===3&&(o+=f.nodeValue.length),(w=f.firstChild)!==null;)p=f,f=w;for(;;){if(f===e)break t;if(p===n&&++s===l&&(a=o),p===i&&++d===r&&(u=o),(w=f.nextSibling)!==null)break;f=p,p=f.parentNode}f=w}n=a===-1||u===-1?null:{start:a,end:u}}else n=null}n=n||{start:0,end:0}}else n=null;for(ta={focusedElem:e,selectionRange:n},ci=!1,j=t;j!==null;)if(t=j,e=t.child,(t.subtreeFlags&1028)!==0&&e!==null)e.return=t,j=e;else for(;j!==null;){t=j;try{var x=t.alternate;if(t.flags&1024)switch(t.tag){case 0:case 11:case 15:break;case 1:if(x!==null){var E=x.memoizedProps,T=x.memoizedState,m=t.stateNode,c=m.getSnapshotBeforeUpdate(t.elementType===t.type?E:ft(t.type,E),T);m.__reactInternalSnapshotBeforeUpdate=c}break;case 3:var v=t.stateNode.containerInfo;v.nodeType===1?v.textContent="":v.nodeType===9&&v.documentElement&&v.removeChild(v.documentElement);break;case 5:case 6:case 4:case 17:break;default:throw Error(R(163))}}catch(k){me(t,t.return,k)}if(e=t.sibling,e!==null){e.return=t.return,j=e;break}j=t.return}return x=As,As=!1,x}function Kr(e,t,n){var r=t.updateQueue;if(r=r!==null?r.lastEffect:null,r!==null){var l=r=r.next;do{if((l.tag&e)===e){var i=l.destroy;l.destroy=void 0,i!==void 0&&ya(t,n,i)}l=l.next}while(l!==r)}}function Vi(e,t){if(t=t.updateQueue,t=t!==null?t.lastEffect:null,t!==null){var n=t=t.next;do{if((n.tag&e)===e){var r=n.create;n.destroy=r()}n=n.next}while(n!==t)}}function ga(e){var t=e.ref;if(t!==null){var n=e.stateNode;switch(e.tag){case 5:e=n;break;default:e=n}typeof t=="function"?t(e):t.current=e}}function vd(e){var t=e.alternate;t!==null&&(e.alternate=null,vd(t)),e.child=null,e.deletions=null,e.sibling=null,e.tag===5&&(t=e.stateNode,t!==null&&(delete t[Et],delete t[il],delete t[la],delete t[rm],delete t[lm])),e.stateNode=null,e.return=null,e.dependencies=null,e.memoizedProps=null,e.memoizedState=null,e.pendingProps=null,e.stateNode=null,e.updateQueue=null}function yd(e){return e.tag===5||e.tag===3||e.tag===4}function Bs(e){e:for(;;){for(;e.sibling===null;){if(e.return===null||yd(e.return))return null;e=e.return}for(e.sibling.return=e.return,e=e.sibling;e.tag!==5&&e.tag!==6&&e.tag!==18;){if(e.flags&2||e.child===null||e.tag===4)continue e;e.child.return=e,e=e.child}if(!(e.flags&2))return e.stateNode}}function wa(e,t,n){var r=e.tag;if(r===5||r===6)e=e.stateNode,t?n.nodeType===8?n.parentNode.insertBefore(e,t):n.insertBefore(e,t):(n.nodeType===8?(t=n.parentNode,t.insertBefore(e,n)):(t=n,t.appendChild(e)),n=n._reactRootContainer,n!=null||t.onclick!==null||(t.onclick=pi));else if(r!==4&&(e=e.child,e!==null))for(wa(e,t,n),e=e.sibling;e!==null;)wa(e,t,n),e=e.sibling}function Sa(e,t,n){var r=e.tag;if(r===5||r===6)e=e.stateNode,t?n.insertBefore(e,t):n.appendChild(e);else if(r!==4&&(e=e.child,e!==null))for(Sa(e,t,n),e=e.sibling;e!==null;)Sa(e,t,n),e=e.sibling}var Re=null,dt=!1;function $t(e,t,n){for(n=n.child;n!==null;)gd(e,t,n),n=n.sibling}function gd(e,t,n){if(xt&&typeof xt.onCommitFiberUnmount=="function")try{xt.onCommitFiberUnmount(ji,n)}catch{}switch(n.tag){case 5:Me||bn(n,t);case 6:var r=Re,l=dt;Re=null,$t(e,t,n),Re=r,dt=l,Re!==null&&(dt?(e=Re,n=n.stateNode,e.nodeType===8?e.parentNode.removeChild(n):e.removeChild(n)):Re.removeChild(n.stateNode));break;case 18:Re!==null&&(dt?(e=Re,n=n.stateNode,e.nodeType===8?wo(e.parentNode,n):e.nodeType===1&&wo(e,n),el(e)):wo(Re,n.stateNode));break;case 4:r=Re,l=dt,Re=n.stateNode.containerInfo,dt=!0,$t(e,t,n),Re=r,dt=l;break;case 0:case 11:case 14:case 15:if(!Me&&(r=n.updateQueue,r!==null&&(r=r.lastEffect,r!==null))){l=r=r.next;do{var i=l,o=i.destroy;i=i.tag,o!==void 0&&(i&2||i&4)&&ya(n,t,o),l=l.next}while(l!==r)}$t(e,t,n);break;case 1:if(!Me&&(bn(n,t),r=n.stateNode,typeof r.componentWillUnmount=="function"))try{r.props=n.memoizedProps,r.state=n.memoizedState,r.componentWillUnmount()}catch(a){me(n,t,a)}$t(e,t,n);break;case 21:$t(e,t,n);break;case 22:n.mode&1?(Me=(r=Me)||n.memoizedState!==null,$t(e,t,n),Me=r):$t(e,t,n);break;default:$t(e,t,n)}}function Hs(e){var t=e.updateQueue;if(t!==null){e.updateQueue=null;var n=e.stateNode;n===null&&(n=e.stateNode=new Sm),t.forEach(function(r){var l=Tm.bind(null,e,r);n.has(r)||(n.add(r),r.then(l,l))})}}function ct(e,t){var n=t.deletions;if(n!==null)for(var r=0;rl&&(l=o),r&=~i}if(r=l,r=ve()-r,r=(120>r?120:480>r?480:1080>r?1080:1920>r?1920:3e3>r?3e3:4320>r?4320:1960*km(r/1960))-r,10e?16:e,Zt===null)var r=!1;else{if(e=Zt,Zt=null,_i=0,G&6)throw Error(R(331));var l=G;for(G|=4,j=e.current;j!==null;){var i=j,o=i.child;if(j.flags&16){var a=i.deletions;if(a!==null){for(var u=0;uve()-hu?Pn(e,0):pu|=n),Ke(e,t)}function _d(e,t){t===0&&(e.mode&1?(t=Ml,Ml<<=1,!(Ml&130023424)&&(Ml=4194304)):t=1);var n=Ue();e=jt(e,t),e!==null&&(ml(e,t,n),Ke(e,n))}function Nm(e){var t=e.memoizedState,n=0;t!==null&&(n=t.retryLane),_d(e,n)}function Tm(e,t){var n=0;switch(e.tag){case 13:var r=e.stateNode,l=e.memoizedState;l!==null&&(n=l.retryLane);break;case 19:r=e.stateNode;break;default:throw Error(R(314))}r!==null&&r.delete(t),_d(e,n)}var Rd;Rd=function(e,t,n){if(e!==null)if(e.memoizedProps!==t.pendingProps||$e.current)We=!0;else{if(!(e.lanes&n)&&!(t.flags&128))return We=!1,ym(e,t,n);We=!!(e.flags&131072)}else We=!1,se&&t.flags&1048576&&Df(t,yi,t.index);switch(t.lanes=0,t.tag){case 2:var r=t.type;ei(e,t),e=t.pendingProps;var l=ur(t,ze.current);ir(t,n),l=au(null,t,r,e,l,n);var i=uu();return t.flags|=1,typeof l=="object"&&l!==null&&typeof l.render=="function"&&l.$$typeof===void 0?(t.tag=1,t.memoizedState=null,t.updateQueue=null,Qe(r)?(i=!0,mi(t)):i=!1,t.memoizedState=l.state!==null&&l.state!==void 0?l.state:null,nu(t),l.updater=Hi,t.stateNode=l,l._reactInternals=t,ca(t,r,e,n),t=pa(null,t,r,!0,i,n)):(t.tag=0,se&&i&&Ga(t),Ie(null,t,l,n),t=t.child),t;case 16:r=t.elementType;e:{switch(ei(e,t),e=t.pendingProps,l=r._init,r=l(r._payload),t.type=r,l=t.tag=Mm(r),e=ft(r,e),l){case 0:t=da(null,t,r,e,n);break e;case 1:t=Fs(null,t,r,e,n);break e;case 11:t=js(null,t,r,e,n);break e;case 14:t=Os(null,t,r,ft(r.type,e),n);break e}throw Error(R(306,r,""))}return t;case 0:return r=t.type,l=t.pendingProps,l=t.elementType===r?l:ft(r,l),da(e,t,r,l,n);case 1:return r=t.type,l=t.pendingProps,l=t.elementType===r?l:ft(r,l),Fs(e,t,r,l,n);case 3:e:{if(cd(t),e===null)throw Error(R(387));r=t.pendingProps,i=t.memoizedState,l=i.element,If(e,t),Si(t,r,null,n);var o=t.memoizedState;if(r=o.element,i.isDehydrated)if(i={element:r,isDehydrated:!1,cache:o.cache,pendingSuspenseBoundaries:o.pendingSuspenseBoundaries,transitions:o.transitions},t.updateQueue.baseState=i,t.memoizedState=i,t.flags&256){l=dr(Error(R(423)),t),t=Is(e,t,r,n,l);break e}else if(r!==l){l=dr(Error(R(424)),t),t=Is(e,t,r,n,l);break e}else for(Je=nn(t.stateNode.containerInfo.firstChild),Ze=t,se=!0,ht=null,n=Of(t,null,r,n),t.child=n;n;)n.flags=n.flags&-3|4096,n=n.sibling;else{if(sr(),r===l){t=Ot(e,t,n);break e}Ie(e,t,r,n)}t=t.child}return t;case 5:return Uf(t),e===null&&aa(t),r=t.type,l=t.pendingProps,i=e!==null?e.memoizedProps:null,o=l.children,na(r,l)?o=null:i!==null&&na(r,i)&&(t.flags|=32),sd(e,t),Ie(e,t,o,n),t.child;case 6:return e===null&&aa(t),null;case 13:return fd(e,t,n);case 4:return ru(t,t.stateNode.containerInfo),r=t.pendingProps,e===null?t.child=cr(t,null,r,n):Ie(e,t,r,n),t.child;case 11:return r=t.type,l=t.pendingProps,l=t.elementType===r?l:ft(r,l),js(e,t,r,l,n);case 7:return Ie(e,t,t.pendingProps,n),t.child;case 8:return Ie(e,t,t.pendingProps.children,n),t.child;case 12:return Ie(e,t,t.pendingProps.children,n),t.child;case 10:e:{if(r=t.type._context,l=t.pendingProps,i=t.memoizedProps,o=l.value,ie(gi,r._currentValue),r._currentValue=o,i!==null)if(yt(i.value,o)){if(i.children===l.children&&!$e.current){t=Ot(e,t,n);break e}}else for(i=t.child,i!==null&&(i.return=t);i!==null;){var a=i.dependencies;if(a!==null){o=i.child;for(var u=a.firstContext;u!==null;){if(u.context===r){if(i.tag===1){u=Tt(-1,n&-n),u.tag=2;var s=i.updateQueue;if(s!==null){s=s.shared;var d=s.pending;d===null?u.next=u:(u.next=d.next,d.next=u),s.pending=u}}i.lanes|=n,u=i.alternate,u!==null&&(u.lanes|=n),ua(i.return,n,t),a.lanes|=n;break}u=u.next}}else if(i.tag===10)o=i.type===t.type?null:i.child;else if(i.tag===18){if(o=i.return,o===null)throw Error(R(341));o.lanes|=n,a=o.alternate,a!==null&&(a.lanes|=n),ua(o,n,t),o=i.sibling}else o=i.child;if(o!==null)o.return=i;else for(o=i;o!==null;){if(o===t){o=null;break}if(i=o.sibling,i!==null){i.return=o.return,o=i;break}o=o.return}i=o}Ie(e,t,l.children,n),t=t.child}return t;case 9:return l=t.type,r=t.pendingProps.children,ir(t,n),l=ot(l),r=r(l),t.flags|=1,Ie(e,t,r,n),t.child;case 14:return r=t.type,l=ft(r,t.pendingProps),l=ft(r.type,l),Os(e,t,r,l,n);case 15:return ad(e,t,t.type,t.pendingProps,n);case 17:return r=t.type,l=t.pendingProps,l=t.elementType===r?l:ft(r,l),ei(e,t),t.tag=1,Qe(r)?(e=!0,mi(t)):e=!1,ir(t,n),ld(t,r,l),ca(t,r,l,n),pa(null,t,r,!0,e,n);case 19:return dd(e,t,n);case 22:return ud(e,t,n)}throw Error(R(156,t.tag))};function Ld(e,t){return ef(e,t)}function Dm(e,t,n,r){this.tag=e,this.key=n,this.sibling=this.child=this.return=this.stateNode=this.type=this.elementType=null,this.index=0,this.ref=null,this.pendingProps=t,this.dependencies=this.memoizedState=this.updateQueue=this.memoizedProps=null,this.mode=r,this.subtreeFlags=this.flags=0,this.deletions=null,this.childLanes=this.lanes=0,this.alternate=null}function lt(e,t,n,r){return new Dm(e,t,n,r)}function gu(e){return e=e.prototype,!(!e||!e.isReactComponent)}function Mm(e){if(typeof e=="function")return gu(e)?1:0;if(e!=null){if(e=e.$$typeof,e===Ia)return 11;if(e===Ua)return 14}return 2}function an(e,t){var n=e.alternate;return n===null?(n=lt(e.tag,t,e.key,e.mode),n.elementType=e.elementType,n.type=e.type,n.stateNode=e.stateNode,n.alternate=e,e.alternate=n):(n.pendingProps=t,n.type=e.type,n.flags=0,n.subtreeFlags=0,n.deletions=null),n.flags=e.flags&14680064,n.childLanes=e.childLanes,n.lanes=e.lanes,n.child=e.child,n.memoizedProps=e.memoizedProps,n.memoizedState=e.memoizedState,n.updateQueue=e.updateQueue,t=e.dependencies,n.dependencies=t===null?null:{lanes:t.lanes,firstContext:t.firstContext},n.sibling=e.sibling,n.index=e.index,n.ref=e.ref,n}function ri(e,t,n,r,l,i){var o=2;if(r=e,typeof e=="function")gu(e)&&(o=1);else if(typeof e=="string")o=5;else e:switch(e){case $n:return _n(n.children,l,i,t);case Fa:o=8,l|=8;break;case jo:return e=lt(12,n,t,l|2),e.elementType=jo,e.lanes=i,e;case Oo:return e=lt(13,n,t,l),e.elementType=Oo,e.lanes=i,e;case Fo:return e=lt(19,n,t,l),e.elementType=Fo,e.lanes=i,e;case Ic:return $i(n,l,i,t);default:if(typeof e=="object"&&e!==null)switch(e.$$typeof){case Oc:o=10;break e;case Fc:o=9;break e;case Ia:o=11;break e;case Ua:o=14;break e;case Kt:o=16,r=null;break e}throw Error(R(130,e==null?e:typeof e,""))}return t=lt(o,n,t,l),t.elementType=e,t.type=r,t.lanes=i,t}function _n(e,t,n,r){return e=lt(7,e,r,t),e.lanes=n,e}function $i(e,t,n,r){return e=lt(22,e,r,t),e.elementType=Ic,e.lanes=n,e.stateNode={isHidden:!1},e}function Ro(e,t,n){return e=lt(6,e,null,t),e.lanes=n,e}function Lo(e,t,n){return t=lt(4,e.children!==null?e.children:[],e.key,t),t.lanes=n,t.stateNode={containerInfo:e.containerInfo,pendingChildren:null,implementation:e.implementation},t}function zm(e,t,n,r,l){this.tag=t,this.containerInfo=e,this.finishedWork=this.pingCache=this.current=this.pendingChildren=null,this.timeoutHandle=-1,this.callbackNode=this.pendingContext=this.context=null,this.callbackPriority=0,this.eventTimes=ao(0),this.expirationTimes=ao(-1),this.entangledLanes=this.finishedLanes=this.mutableReadLanes=this.expiredLanes=this.pingedLanes=this.suspendedLanes=this.pendingLanes=0,this.entanglements=ao(0),this.identifierPrefix=r,this.onRecoverableError=l,this.mutableSourceEagerHydrationData=null}function wu(e,t,n,r,l,i,o,a,u){return e=new zm(e,t,n,a,u),t===1?(t=1,i===!0&&(t|=8)):t=0,i=lt(3,null,null,t),e.current=i,i.stateNode=e,i.memoizedState={element:r,isDehydrated:n,cache:null,transitions:null,pendingSuspenseBoundaries:null},nu(i),e}function jm(e,t,n){var r=3"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(Md)}catch(e){console.error(e)}}Md(),Dc.exports=be;var ku=Dc.exports;const Am=gc(ku),Bm=yc({__proto__:null,default:Am},[ku]);var Gs=ku;Mo.createRoot=Gs.createRoot,Mo.hydrateRoot=Gs.hydrateRoot;/** + * @remix-run/router v1.23.2 + * + * Copyright (c) Remix Software Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE.md file in the root directory of this source tree. + * + * @license MIT + */function ue(){return ue=Object.assign?Object.assign.bind():function(e){for(var t=1;t"u")throw new Error(t)}function Mn(e,t){if(!e){typeof console<"u"&&console.warn(t);try{throw new Error(t)}catch{}}}function Vm(){return Math.random().toString(36).substr(2,8)}function Zs(e,t){return{usr:e.state,key:e.key,idx:t}}function dl(e,t,n,r){return n===void 0&&(n=null),ue({pathname:typeof e=="string"?e:e.pathname,search:"",hash:""},typeof t=="string"?pn(t):t,{state:n,key:t&&t.key||r||Vm()})}function zn(e){let{pathname:t="/",search:n="",hash:r=""}=e;return n&&n!=="?"&&(t+=n.charAt(0)==="?"?n:"?"+n),r&&r!=="#"&&(t+=r.charAt(0)==="#"?r:"#"+r),t}function pn(e){let t={};if(e){let n=e.indexOf("#");n>=0&&(t.hash=e.substr(n),e=e.substr(0,n));let r=e.indexOf("?");r>=0&&(t.search=e.substr(r),e=e.substr(0,r)),e&&(t.pathname=e)}return t}function Wm(e,t,n,r){r===void 0&&(r={});let{window:l=document.defaultView,v5Compat:i=!1}=r,o=l.history,a=ge.Pop,u=null,s=d();s==null&&(s=0,o.replaceState(ue({},o.state,{idx:s}),""));function d(){return(o.state||{idx:null}).idx}function f(){a=ge.Pop;let T=d(),m=T==null?null:T-s;s=T,u&&u({action:a,location:E.location,delta:m})}function p(T,m){a=ge.Push;let c=dl(E.location,T,m);s=d()+1;let v=Zs(c,s),k=E.createHref(c);try{o.pushState(v,"",k)}catch(L){if(L instanceof DOMException&&L.name==="DataCloneError")throw L;l.location.assign(k)}i&&u&&u({action:a,location:E.location,delta:1})}function w(T,m){a=ge.Replace;let c=dl(E.location,T,m);s=d();let v=Zs(c,s),k=E.createHref(c);o.replaceState(v,"",k),i&&u&&u({action:a,location:E.location,delta:0})}function x(T){let m=l.location.origin!=="null"?l.location.origin:l.location.href,c=typeof T=="string"?T:zn(T);return c=c.replace(/ $/,"%20"),K(m,"No window.location.(origin|href) available to create URL for href: "+c),new URL(c,m)}let E={get action(){return a},get location(){return e(l,o)},listen(T){if(u)throw new Error("A history only accepts one active listener");return l.addEventListener(Js,f),u=T,()=>{l.removeEventListener(Js,f),u=null}},createHref(T){return t(l,T)},createURL:x,encodeLocation(T){let m=x(T);return{pathname:m.pathname,search:m.search,hash:m.hash}},push:p,replace:w,go(T){return o.go(T)}};return E}var b;(function(e){e.data="data",e.deferred="deferred",e.redirect="redirect",e.error="error"})(b||(b={}));const $m=new Set(["lazy","caseSensitive","path","id","index","children"]);function Qm(e){return e.index===!0}function Ni(e,t,n,r){return n===void 0&&(n=[]),r===void 0&&(r={}),e.map((l,i)=>{let o=[...n,String(i)],a=typeof l.id=="string"?l.id:o.join("-");if(K(l.index!==!0||!l.children,"Cannot specify children on an index route"),K(!r[a],'Found a route id collision on id "'+a+`". Route id's must be globally unique within Data Router usages`),Qm(l)){let u=ue({},l,t(l),{id:a});return r[a]=u,u}else{let u=ue({},l,t(l),{id:a,children:void 0});return r[a]=u,l.children&&(u.children=Ni(l.children,t,o,r)),u}})}function wn(e,t,n){return n===void 0&&(n="/"),li(e,t,n,!1)}function li(e,t,n,r){let l=typeof t=="string"?pn(t):t,i=Ft(l.pathname||"/",n);if(i==null)return null;let o=zd(e);Ym(o);let a=null;for(let u=0;a==null&&u{let u={relativePath:a===void 0?i.path||"":a,caseSensitive:i.caseSensitive===!0,childrenIndex:o,route:i};u.relativePath.startsWith("/")&&(K(u.relativePath.startsWith(r),'Absolute route path "'+u.relativePath+'" nested under path '+('"'+r+'" is not valid. An absolute child route path ')+"must start with the combined path of all its parent routes."),u.relativePath=u.relativePath.slice(r.length));let s=Dt([r,u.relativePath]),d=n.concat(u);i.children&&i.children.length>0&&(K(i.index!==!0,"Index routes must not have child routes. Please remove "+('all child routes from route path "'+s+'".')),zd(i.children,t,d,s)),!(i.path==null&&!i.index)&&t.push({path:s,score:ev(s,i.index),routesMeta:d})};return e.forEach((i,o)=>{var a;if(i.path===""||!((a=i.path)!=null&&a.includes("?")))l(i,o);else for(let u of jd(i.path))l(i,o,u)}),t}function jd(e){let t=e.split("/");if(t.length===0)return[];let[n,...r]=t,l=n.endsWith("?"),i=n.replace(/\?$/,"");if(r.length===0)return l?[i,""]:[i];let o=jd(r.join("/")),a=[];return a.push(...o.map(u=>u===""?i:[i,u].join("/"))),l&&a.push(...o),a.map(u=>e.startsWith("/")&&u===""?"/":u)}function Ym(e){e.sort((t,n)=>t.score!==n.score?n.score-t.score:tv(t.routesMeta.map(r=>r.childrenIndex),n.routesMeta.map(r=>r.childrenIndex)))}const Xm=/^:[\w-]+$/,Gm=3,Jm=2,Zm=1,qm=10,bm=-2,qs=e=>e==="*";function ev(e,t){let n=e.split("/"),r=n.length;return n.some(qs)&&(r+=bm),t&&(r+=Jm),n.filter(l=>!qs(l)).reduce((l,i)=>l+(Xm.test(i)?Gm:i===""?Zm:qm),r)}function tv(e,t){return e.length===t.length&&e.slice(0,-1).every((r,l)=>r===t[l])?e[e.length-1]-t[t.length-1]:0}function nv(e,t,n){n===void 0&&(n=!1);let{routesMeta:r}=e,l={},i="/",o=[];for(let a=0;a{let{paramName:p,isOptional:w}=d;if(p==="*"){let E=a[f]||"";o=i.slice(0,i.length-E.length).replace(/(.)\/+$/,"$1")}const x=a[f];return w&&!x?s[p]=void 0:s[p]=(x||"").replace(/%2F/g,"/"),s},{}),pathname:i,pathnameBase:o,pattern:e}}function rv(e,t,n){t===void 0&&(t=!1),n===void 0&&(n=!0),Mn(e==="*"||!e.endsWith("*")||e.endsWith("/*"),'Route path "'+e+'" will be treated as if it were '+('"'+e.replace(/\*$/,"/*")+'" because the `*` character must ')+"always follow a `/` in the pattern. To get rid of this warning, "+('please change the route path to "'+e.replace(/\*$/,"/*")+'".'));let r=[],l="^"+e.replace(/\/*\*?$/,"").replace(/^\/*/,"/").replace(/[\\.*+^${}|()[\]]/g,"\\$&").replace(/\/:([\w-]+)(\?)?/g,(o,a,u)=>(r.push({paramName:a,isOptional:u!=null}),u?"/?([^\\/]+)?":"/([^\\/]+)"));return e.endsWith("*")?(r.push({paramName:"*"}),l+=e==="*"||e==="/*"?"(.*)$":"(?:\\/(.+)|\\/*)$"):n?l+="\\/*$":e!==""&&e!=="/"&&(l+="(?:(?=\\/|$))"),[new RegExp(l,t?void 0:"i"),r]}function lv(e){try{return e.split("/").map(t=>decodeURIComponent(t).replace(/\//g,"%2F")).join("/")}catch(t){return Mn(!1,'The URL path "'+e+'" could not be decoded because it is is a malformed URL segment. This is probably due to a bad percent '+("encoding ("+t+").")),e}}function Ft(e,t){if(t==="/")return e;if(!e.toLowerCase().startsWith(t.toLowerCase()))return null;let n=t.endsWith("/")?t.length-1:t.length,r=e.charAt(n);return r&&r!=="/"?null:e.slice(n)||"/"}const iv=/^(?:[a-z][a-z0-9+.-]*:|\/\/)/i,ov=e=>iv.test(e);function av(e,t){t===void 0&&(t="/");let{pathname:n,search:r="",hash:l=""}=typeof e=="string"?pn(e):e,i;if(n)if(ov(n))i=n;else{if(n.includes("//")){let o=n;n=n.replace(/\/\/+/g,"/"),Mn(!1,"Pathnames cannot have embedded double slashes - normalizing "+(o+" -> "+n))}n.startsWith("/")?i=bs(n.substring(1),"/"):i=bs(n,t)}else i=t;return{pathname:i,search:sv(r),hash:cv(l)}}function bs(e,t){let n=t.replace(/\/+$/,"").split("/");return e.split("/").forEach(l=>{l===".."?n.length>1&&n.pop():l!=="."&&n.push(l)}),n.length>1?n.join("/"):"/"}function No(e,t,n,r){return"Cannot include a '"+e+"' character in a manually specified "+("`to."+t+"` field ["+JSON.stringify(r)+"]. Please separate it out to the ")+("`to."+n+"` field. Alternatively you may provide the full path as ")+'a string in and the router will parse it for you.'}function Od(e){return e.filter((t,n)=>n===0||t.route.path&&t.route.path.length>0)}function Gi(e,t){let n=Od(e);return t?n.map((r,l)=>l===n.length-1?r.pathname:r.pathnameBase):n.map(r=>r.pathnameBase)}function Ji(e,t,n,r){r===void 0&&(r=!1);let l;typeof e=="string"?l=pn(e):(l=ue({},e),K(!l.pathname||!l.pathname.includes("?"),No("?","pathname","search",l)),K(!l.pathname||!l.pathname.includes("#"),No("#","pathname","hash",l)),K(!l.search||!l.search.includes("#"),No("#","search","hash",l)));let i=e===""||l.pathname==="",o=i?"/":l.pathname,a;if(o==null)a=n;else{let f=t.length-1;if(!r&&o.startsWith("..")){let p=o.split("/");for(;p[0]==="..";)p.shift(),f-=1;l.pathname=p.join("/")}a=f>=0?t[f]:"/"}let u=av(l,a),s=o&&o!=="/"&&o.endsWith("/"),d=(i||o===".")&&n.endsWith("/");return!u.pathname.endsWith("/")&&(s||d)&&(u.pathname+="/"),u}const Dt=e=>e.join("/").replace(/\/\/+/g,"/"),uv=e=>e.replace(/\/+$/,"").replace(/^\/*/,"/"),sv=e=>!e||e==="?"?"":e.startsWith("?")?e:"?"+e,cv=e=>!e||e==="#"?"":e.startsWith("#")?e:"#"+e;class Di{constructor(t,n,r,l){l===void 0&&(l=!1),this.status=t,this.statusText=n||"",this.internal=l,r instanceof Error?(this.data=r.toString(),this.error=r):this.data=r}}function pl(e){return e!=null&&typeof e.status=="number"&&typeof e.statusText=="string"&&typeof e.internal=="boolean"&&"data"in e}const Fd=["post","put","patch","delete"],fv=new Set(Fd),dv=["get",...Fd],pv=new Set(dv),hv=new Set([301,302,303,307,308]),mv=new Set([307,308]),To={state:"idle",location:void 0,formMethod:void 0,formAction:void 0,formEncType:void 0,formData:void 0,json:void 0,text:void 0},vv={state:"idle",data:void 0,formMethod:void 0,formAction:void 0,formEncType:void 0,formData:void 0,json:void 0,text:void 0},Mr={state:"unblocked",proceed:void 0,reset:void 0,location:void 0},Cu=/^(?:[a-z][a-z0-9+.-]*:|\/\/)/i,yv=e=>({hasErrorBoundary:!!e.hasErrorBoundary}),Id="remix-router-transitions";function gv(e){const t=e.window?e.window:typeof window<"u"?window:void 0,n=typeof t<"u"&&typeof t.document<"u"&&typeof t.document.createElement<"u",r=!n;K(e.routes.length>0,"You must provide a non-empty routes array to createRouter");let l;if(e.mapRouteProperties)l=e.mapRouteProperties;else if(e.detectErrorBoundary){let h=e.detectErrorBoundary;l=g=>({hasErrorBoundary:h(g)})}else l=yv;let i={},o=Ni(e.routes,l,void 0,i),a,u=e.basename||"/",s=e.dataStrategy||xv,d=e.patchRoutesOnNavigation,f=ue({v7_fetcherPersist:!1,v7_normalizeFormMethod:!1,v7_partialHydration:!1,v7_prependBasename:!1,v7_relativeSplatPath:!1,v7_skipActionErrorRevalidation:!1},e.future),p=null,w=new Set,x=null,E=null,T=null,m=e.hydrationData!=null,c=wn(o,e.history.location,u),v=!1,k=null;if(c==null&&!d){let h=He(404,{pathname:e.history.location.pathname}),{matches:g,route:S}=cc(o);c=g,k={[S.id]:h}}c&&!e.hydrationData&&kl(c,o,e.history.location.pathname).active&&(c=null);let L;if(c)if(c.some(h=>h.route.lazy))L=!1;else if(!c.some(h=>h.route.loader))L=!0;else if(f.v7_partialHydration){let h=e.hydrationData?e.hydrationData.loaderData:null,g=e.hydrationData?e.hydrationData.errors:null;if(g){let S=c.findIndex(P=>g[P.route.id]!==void 0);L=c.slice(0,S+1).every(P=>!_a(P.route,h,g))}else L=c.every(S=>!_a(S.route,h,g))}else L=e.hydrationData!=null;else if(L=!1,c=[],f.v7_partialHydration){let h=kl(null,o,e.history.location.pathname);h.active&&h.matches&&(v=!0,c=h.matches)}let z,y={historyAction:e.history.action,location:e.history.location,matches:c,initialized:L,navigation:To,restoreScrollPosition:e.hydrationData!=null?!1:null,preventScrollReset:!1,revalidation:"idle",loaderData:e.hydrationData&&e.hydrationData.loaderData||{},actionData:e.hydrationData&&e.hydrationData.actionData||null,errors:e.hydrationData&&e.hydrationData.errors||k,fetchers:new Map,blockers:new Map},C=ge.Pop,H=!1,D,q=!1,re=new Map,Se=null,Pe=!1,ut=!1,Bt=[],Ht=new Set,N=new Map,V=0,$=-1,te=new Map,ne=new Set,st=new Map,Ye=new Map,je=new Set,Oe=new Map,tt=new Map,Sl;function Jd(){if(p=e.history.listen(h=>{let{action:g,location:S,delta:P}=h;if(Sl){Sl(),Sl=void 0;return}Mn(tt.size===0||P!=null,"You are trying to use a blocker on a POP navigation to a location that was not created by @remix-run/router. This will fail silently in production. This can happen if you are navigating outside the router via `window.history.pushState`/`window.location.hash` instead of using router navigation APIs. This can also happen if you are using createHashRouter and the user manually changes the URL.");let M=Iu({currentLocation:y.location,nextLocation:S,historyAction:g});if(M&&P!=null){let B=new Promise(W=>{Sl=W});e.history.go(P*-1),xl(M,{state:"blocked",location:S,proceed(){xl(M,{state:"proceeding",proceed:void 0,reset:void 0,location:S}),B.then(()=>e.history.go(P))},reset(){let W=new Map(y.blockers);W.set(M,Mr),Fe({blockers:W})}});return}return hn(g,S)}),n){Fv(t,re);let h=()=>Iv(t,re);t.addEventListener("pagehide",h),Se=()=>t.removeEventListener("pagehide",h)}return y.initialized||hn(ge.Pop,y.location,{initialHydration:!0}),z}function Zd(){p&&p(),Se&&Se(),w.clear(),D&&D.abort(),y.fetchers.forEach((h,g)=>El(g)),y.blockers.forEach((h,g)=>Fu(g))}function qd(h){return w.add(h),()=>w.delete(h)}function Fe(h,g){g===void 0&&(g={}),y=ue({},y,h);let S=[],P=[];f.v7_fetcherPersist&&y.fetchers.forEach((M,B)=>{M.state==="idle"&&(je.has(B)?P.push(B):S.push(B))}),je.forEach(M=>{!y.fetchers.has(M)&&!N.has(M)&&P.push(M)}),[...w].forEach(M=>M(y,{deletedFetchers:P,viewTransitionOpts:g.viewTransitionOpts,flushSync:g.flushSync===!0})),f.v7_fetcherPersist?(S.forEach(M=>y.fetchers.delete(M)),P.forEach(M=>El(M))):P.forEach(M=>je.delete(M))}function In(h,g,S){var P,M;let{flushSync:B}=S===void 0?{}:S,W=y.actionData!=null&&y.navigation.formMethod!=null&&pt(y.navigation.formMethod)&&y.navigation.state==="loading"&&((P=h.state)==null?void 0:P._isRedirect)!==!0,F;g.actionData?Object.keys(g.actionData).length>0?F=g.actionData:F=null:W?F=y.actionData:F=null;let I=g.loaderData?uc(y.loaderData,g.loaderData,g.matches||[],g.errors):y.loaderData,O=y.blockers;O.size>0&&(O=new Map(O),O.forEach((X,_e)=>O.set(_e,Mr)));let A=H===!0||y.navigation.formMethod!=null&&pt(y.navigation.formMethod)&&((M=h.state)==null?void 0:M._isRedirect)!==!0;a&&(o=a,a=void 0),Pe||C===ge.Pop||(C===ge.Push?e.history.push(h,h.state):C===ge.Replace&&e.history.replace(h,h.state));let Q;if(C===ge.Pop){let X=re.get(y.location.pathname);X&&X.has(h.pathname)?Q={currentLocation:y.location,nextLocation:h}:re.has(h.pathname)&&(Q={currentLocation:h,nextLocation:y.location})}else if(q){let X=re.get(y.location.pathname);X?X.add(h.pathname):(X=new Set([h.pathname]),re.set(y.location.pathname,X)),Q={currentLocation:y.location,nextLocation:h}}Fe(ue({},g,{actionData:F,loaderData:I,historyAction:C,location:h,initialized:!0,navigation:To,revalidation:"idle",restoreScrollPosition:Au(h,g.matches||y.matches),preventScrollReset:A,blockers:O}),{viewTransitionOpts:Q,flushSync:B===!0}),C=ge.Pop,H=!1,q=!1,Pe=!1,ut=!1,Bt=[]}async function Nu(h,g){if(typeof h=="number"){e.history.go(h);return}let S=Pa(y.location,y.matches,u,f.v7_prependBasename,h,f.v7_relativeSplatPath,g==null?void 0:g.fromRouteId,g==null?void 0:g.relative),{path:P,submission:M,error:B}=ec(f.v7_normalizeFormMethod,!1,S,g),W=y.location,F=dl(y.location,P,g&&g.state);F=ue({},F,e.history.encodeLocation(F));let I=g&&g.replace!=null?g.replace:void 0,O=ge.Push;I===!0?O=ge.Replace:I===!1||M!=null&&pt(M.formMethod)&&M.formAction===y.location.pathname+y.location.search&&(O=ge.Replace);let A=g&&"preventScrollReset"in g?g.preventScrollReset===!0:void 0,Q=(g&&g.flushSync)===!0,X=Iu({currentLocation:W,nextLocation:F,historyAction:O});if(X){xl(X,{state:"blocked",location:F,proceed(){xl(X,{state:"proceeding",proceed:void 0,reset:void 0,location:F}),Nu(h,g)},reset(){let _e=new Map(y.blockers);_e.set(X,Mr),Fe({blockers:_e})}});return}return await hn(O,F,{submission:M,pendingError:B,preventScrollReset:A,replace:g&&g.replace,enableViewTransition:g&&g.viewTransition,flushSync:Q})}function bd(){if(qi(),Fe({revalidation:"loading"}),y.navigation.state!=="submitting"){if(y.navigation.state==="idle"){hn(y.historyAction,y.location,{startUninterruptedRevalidation:!0});return}hn(C||y.historyAction,y.navigation.location,{overrideNavigation:y.navigation,enableViewTransition:q===!0})}}async function hn(h,g,S){D&&D.abort(),D=null,C=h,Pe=(S&&S.startUninterruptedRevalidation)===!0,sp(y.location,y.matches),H=(S&&S.preventScrollReset)===!0,q=(S&&S.enableViewTransition)===!0;let P=a||o,M=S&&S.overrideNavigation,B=S!=null&&S.initialHydration&&y.matches&&y.matches.length>0&&!v?y.matches:wn(P,g,u),W=(S&&S.flushSync)===!0;if(B&&y.initialized&&!ut&&Lv(y.location,g)&&!(S&&S.submission&&pt(S.submission.formMethod))){In(g,{matches:B},{flushSync:W});return}let F=kl(B,P,g.pathname);if(F.active&&F.matches&&(B=F.matches),!B){let{error:le,notFoundMatches:Z,route:pe}=bi(g.pathname);In(g,{matches:Z,loaderData:{},errors:{[pe.id]:le}},{flushSync:W});return}D=new AbortController;let I=Vn(e.history,g,D.signal,S&&S.submission),O;if(S&&S.pendingError)O=[Sn(B).route.id,{type:b.error,error:S.pendingError}];else if(S&&S.submission&&pt(S.submission.formMethod)){let le=await ep(I,g,S.submission,B,F.active,{replace:S.replace,flushSync:W});if(le.shortCircuited)return;if(le.pendingActionResult){let[Z,pe]=le.pendingActionResult;if(Ge(pe)&&pl(pe.error)&&pe.error.status===404){D=null,In(g,{matches:le.matches,loaderData:{},errors:{[Z]:pe.error}});return}}B=le.matches||B,O=le.pendingActionResult,M=Do(g,S.submission),W=!1,F.active=!1,I=Vn(e.history,I.url,I.signal)}let{shortCircuited:A,matches:Q,loaderData:X,errors:_e}=await tp(I,g,B,F.active,M,S&&S.submission,S&&S.fetcherSubmission,S&&S.replace,S&&S.initialHydration===!0,W,O);A||(D=null,In(g,ue({matches:Q||B},sc(O),{loaderData:X,errors:_e})))}async function ep(h,g,S,P,M,B){B===void 0&&(B={}),qi();let W=jv(g,S);if(Fe({navigation:W},{flushSync:B.flushSync===!0}),M){let O=await Cl(P,g.pathname,h.signal);if(O.type==="aborted")return{shortCircuited:!0};if(O.type==="error"){let A=Sn(O.partialMatches).route.id;return{matches:O.partialMatches,pendingActionResult:[A,{type:b.error,error:O.error}]}}else if(O.matches)P=O.matches;else{let{notFoundMatches:A,error:Q,route:X}=bi(g.pathname);return{matches:A,pendingActionResult:[X.id,{type:b.error,error:Q}]}}}let F,I=Ar(P,g);if(!I.route.action&&!I.route.lazy)F={type:b.error,error:He(405,{method:h.method,pathname:g.pathname,routeId:I.route.id})};else if(F=(await Sr("action",y,h,[I],P,null))[I.route.id],h.signal.aborted)return{shortCircuited:!0};if(Cn(F)){let O;return B&&B.replace!=null?O=B.replace:O=ic(F.response.headers.get("Location"),new URL(h.url),u,e.history)===y.location.pathname+y.location.search,await mn(h,F,!0,{submission:S,replace:O}),{shortCircuited:!0}}if(qt(F))throw He(400,{type:"defer-action"});if(Ge(F)){let O=Sn(P,I.route.id);return(B&&B.replace)!==!0&&(C=ge.Push),{matches:P,pendingActionResult:[O.route.id,F]}}return{matches:P,pendingActionResult:[I.route.id,F]}}async function tp(h,g,S,P,M,B,W,F,I,O,A){let Q=M||Do(g,B),X=B||W||dc(Q),_e=!Pe&&(!f.v7_partialHydration||!I);if(P){if(_e){let he=Tu(A);Fe(ue({navigation:Q},he!==void 0?{actionData:he}:{}),{flushSync:O})}let J=await Cl(S,g.pathname,h.signal);if(J.type==="aborted")return{shortCircuited:!0};if(J.type==="error"){let he=Sn(J.partialMatches).route.id;return{matches:J.partialMatches,loaderData:{},errors:{[he]:J.error}}}else if(J.matches)S=J.matches;else{let{error:he,notFoundMatches:An,route:kr}=bi(g.pathname);return{matches:An,loaderData:{},errors:{[kr.id]:he}}}}let le=a||o,[Z,pe]=nc(e.history,y,S,X,g,f.v7_partialHydration&&I===!0,f.v7_skipActionErrorRevalidation,ut,Bt,Ht,je,st,ne,le,u,A);if(eo(J=>!(S&&S.some(he=>he.route.id===J))||Z&&Z.some(he=>he.route.id===J)),$=++V,Z.length===0&&pe.length===0){let J=ju();return In(g,ue({matches:S,loaderData:{},errors:A&&Ge(A[1])?{[A[0]]:A[1].error}:null},sc(A),J?{fetchers:new Map(y.fetchers)}:{}),{flushSync:O}),{shortCircuited:!0}}if(_e){let J={};if(!P){J.navigation=Q;let he=Tu(A);he!==void 0&&(J.actionData=he)}pe.length>0&&(J.fetchers=np(pe)),Fe(J,{flushSync:O})}pe.forEach(J=>{Wt(J.key),J.controller&&N.set(J.key,J.controller)});let Un=()=>pe.forEach(J=>Wt(J.key));D&&D.signal.addEventListener("abort",Un);let{loaderResults:Er,fetcherResults:Pt}=await Du(y,S,Z,pe,h);if(h.signal.aborted)return{shortCircuited:!0};D&&D.signal.removeEventListener("abort",Un),pe.forEach(J=>N.delete(J.key));let gt=$l(Er);if(gt)return await mn(h,gt.result,!0,{replace:F}),{shortCircuited:!0};if(gt=$l(Pt),gt)return ne.add(gt.key),await mn(h,gt.result,!0,{replace:F}),{shortCircuited:!0};let{loaderData:to,errors:xr}=ac(y,S,Er,A,pe,Pt,Oe);Oe.forEach((J,he)=>{J.subscribe(An=>{(An||J.done)&&Oe.delete(he)})}),f.v7_partialHydration&&I&&y.errors&&(xr=ue({},y.errors,xr));let vn=ju(),Pl=Ou($),_l=vn||Pl||pe.length>0;return ue({matches:S,loaderData:to,errors:xr},_l?{fetchers:new Map(y.fetchers)}:{})}function Tu(h){if(h&&!Ge(h[1]))return{[h[0]]:h[1].data};if(y.actionData)return Object.keys(y.actionData).length===0?null:y.actionData}function np(h){return h.forEach(g=>{let S=y.fetchers.get(g.key),P=zr(void 0,S?S.data:void 0);y.fetchers.set(g.key,P)}),new Map(y.fetchers)}function rp(h,g,S,P){if(r)throw new Error("router.fetch() was called during the server render, but it shouldn't be. You are likely calling a useFetcher() method in the body of your component. Try moving it to a useEffect or a callback.");Wt(h);let M=(P&&P.flushSync)===!0,B=a||o,W=Pa(y.location,y.matches,u,f.v7_prependBasename,S,f.v7_relativeSplatPath,g,P==null?void 0:P.relative),F=wn(B,W,u),I=kl(F,B,W);if(I.active&&I.matches&&(F=I.matches),!F){Ct(h,g,He(404,{pathname:W}),{flushSync:M});return}let{path:O,submission:A,error:Q}=ec(f.v7_normalizeFormMethod,!0,W,P);if(Q){Ct(h,g,Q,{flushSync:M});return}let X=Ar(F,O),_e=(P&&P.preventScrollReset)===!0;if(A&&pt(A.formMethod)){lp(h,g,O,X,F,I.active,M,_e,A);return}st.set(h,{routeId:g,path:O}),ip(h,g,O,X,F,I.active,M,_e,A)}async function lp(h,g,S,P,M,B,W,F,I){qi(),st.delete(h);function O(ye){if(!ye.route.action&&!ye.route.lazy){let Bn=He(405,{method:I.formMethod,pathname:S,routeId:g});return Ct(h,g,Bn,{flushSync:W}),!0}return!1}if(!B&&O(P))return;let A=y.fetchers.get(h);Vt(h,Ov(I,A),{flushSync:W});let Q=new AbortController,X=Vn(e.history,S,Q.signal,I);if(B){let ye=await Cl(M,new URL(X.url).pathname,X.signal,h);if(ye.type==="aborted")return;if(ye.type==="error"){Ct(h,g,ye.error,{flushSync:W});return}else if(ye.matches){if(M=ye.matches,P=Ar(M,S),O(P))return}else{Ct(h,g,He(404,{pathname:S}),{flushSync:W});return}}N.set(h,Q);let _e=V,Z=(await Sr("action",y,X,[P],M,h))[P.route.id];if(X.signal.aborted){N.get(h)===Q&&N.delete(h);return}if(f.v7_fetcherPersist&&je.has(h)){if(Cn(Z)||Ge(Z)){Vt(h,Qt(void 0));return}}else{if(Cn(Z))if(N.delete(h),$>_e){Vt(h,Qt(void 0));return}else return ne.add(h),Vt(h,zr(I)),mn(X,Z,!1,{fetcherSubmission:I,preventScrollReset:F});if(Ge(Z)){Ct(h,g,Z.error);return}}if(qt(Z))throw He(400,{type:"defer-action"});let pe=y.navigation.location||y.location,Un=Vn(e.history,pe,Q.signal),Er=a||o,Pt=y.navigation.state!=="idle"?wn(Er,y.navigation.location,u):y.matches;K(Pt,"Didn't find any matches after fetcher action");let gt=++V;te.set(h,gt);let to=zr(I,Z.data);y.fetchers.set(h,to);let[xr,vn]=nc(e.history,y,Pt,I,pe,!1,f.v7_skipActionErrorRevalidation,ut,Bt,Ht,je,st,ne,Er,u,[P.route.id,Z]);vn.filter(ye=>ye.key!==h).forEach(ye=>{let Bn=ye.key,Bu=y.fetchers.get(Bn),dp=zr(void 0,Bu?Bu.data:void 0);y.fetchers.set(Bn,dp),Wt(Bn),ye.controller&&N.set(Bn,ye.controller)}),Fe({fetchers:new Map(y.fetchers)});let Pl=()=>vn.forEach(ye=>Wt(ye.key));Q.signal.addEventListener("abort",Pl);let{loaderResults:_l,fetcherResults:J}=await Du(y,Pt,xr,vn,Un);if(Q.signal.aborted)return;Q.signal.removeEventListener("abort",Pl),te.delete(h),N.delete(h),vn.forEach(ye=>N.delete(ye.key));let he=$l(_l);if(he)return mn(Un,he.result,!1,{preventScrollReset:F});if(he=$l(J),he)return ne.add(he.key),mn(Un,he.result,!1,{preventScrollReset:F});let{loaderData:An,errors:kr}=ac(y,Pt,_l,void 0,vn,J,Oe);if(y.fetchers.has(h)){let ye=Qt(Z.data);y.fetchers.set(h,ye)}Ou(gt),y.navigation.state==="loading"&>>$?(K(C,"Expected pending action"),D&&D.abort(),In(y.navigation.location,{matches:Pt,loaderData:An,errors:kr,fetchers:new Map(y.fetchers)})):(Fe({errors:kr,loaderData:uc(y.loaderData,An,Pt,kr),fetchers:new Map(y.fetchers)}),ut=!1)}async function ip(h,g,S,P,M,B,W,F,I){let O=y.fetchers.get(h);Vt(h,zr(I,O?O.data:void 0),{flushSync:W});let A=new AbortController,Q=Vn(e.history,S,A.signal);if(B){let Z=await Cl(M,new URL(Q.url).pathname,Q.signal,h);if(Z.type==="aborted")return;if(Z.type==="error"){Ct(h,g,Z.error,{flushSync:W});return}else if(Z.matches)M=Z.matches,P=Ar(M,S);else{Ct(h,g,He(404,{pathname:S}),{flushSync:W});return}}N.set(h,A);let X=V,le=(await Sr("loader",y,Q,[P],M,h))[P.route.id];if(qt(le)&&(le=await Pu(le,Q.signal,!0)||le),N.get(h)===A&&N.delete(h),!Q.signal.aborted){if(je.has(h)){Vt(h,Qt(void 0));return}if(Cn(le))if($>X){Vt(h,Qt(void 0));return}else{ne.add(h),await mn(Q,le,!1,{preventScrollReset:F});return}if(Ge(le)){Ct(h,g,le.error);return}K(!qt(le),"Unhandled fetcher deferred data"),Vt(h,Qt(le.data))}}async function mn(h,g,S,P){let{submission:M,fetcherSubmission:B,preventScrollReset:W,replace:F}=P===void 0?{}:P;g.response.headers.has("X-Remix-Revalidate")&&(ut=!0);let I=g.response.headers.get("Location");K(I,"Expected a Location header on the redirect Response"),I=ic(I,new URL(h.url),u,e.history);let O=dl(y.location,I,{_isRedirect:!0});if(n){let Z=!1;if(g.response.headers.has("X-Remix-Reload-Document"))Z=!0;else if(Cu.test(I)){const pe=e.history.createURL(I);Z=pe.origin!==t.location.origin||Ft(pe.pathname,u)==null}if(Z){F?t.location.replace(I):t.location.assign(I);return}}D=null;let A=F===!0||g.response.headers.has("X-Remix-Replace")?ge.Replace:ge.Push,{formMethod:Q,formAction:X,formEncType:_e}=y.navigation;!M&&!B&&Q&&X&&_e&&(M=dc(y.navigation));let le=M||B;if(mv.has(g.response.status)&&le&&pt(le.formMethod))await hn(A,O,{submission:ue({},le,{formAction:I}),preventScrollReset:W||H,enableViewTransition:S?q:void 0});else{let Z=Do(O,M);await hn(A,O,{overrideNavigation:Z,fetcherSubmission:B,preventScrollReset:W||H,enableViewTransition:S?q:void 0})}}async function Sr(h,g,S,P,M,B){let W,F={};try{W=await kv(s,h,g,S,P,M,B,i,l)}catch(I){return P.forEach(O=>{F[O.route.id]={type:b.error,error:I}}),F}for(let[I,O]of Object.entries(W))if(Nv(O)){let A=O.result;F[I]={type:b.redirect,response:_v(A,S,I,M,u,f.v7_relativeSplatPath)}}else F[I]=await Pv(O);return F}async function Du(h,g,S,P,M){let B=h.matches,W=Sr("loader",h,M,S,g,null),F=Promise.all(P.map(async A=>{if(A.matches&&A.match&&A.controller){let X=(await Sr("loader",h,Vn(e.history,A.path,A.controller.signal),[A.match],A.matches,A.key))[A.match.route.id];return{[A.key]:X}}else return Promise.resolve({[A.key]:{type:b.error,error:He(404,{pathname:A.path})}})})),I=await W,O=(await F).reduce((A,Q)=>Object.assign(A,Q),{});return await Promise.all([Mv(g,I,M.signal,B,h.loaderData),zv(g,O,P)]),{loaderResults:I,fetcherResults:O}}function qi(){ut=!0,Bt.push(...eo()),st.forEach((h,g)=>{N.has(g)&&Ht.add(g),Wt(g)})}function Vt(h,g,S){S===void 0&&(S={}),y.fetchers.set(h,g),Fe({fetchers:new Map(y.fetchers)},{flushSync:(S&&S.flushSync)===!0})}function Ct(h,g,S,P){P===void 0&&(P={});let M=Sn(y.matches,g);El(h),Fe({errors:{[M.route.id]:S},fetchers:new Map(y.fetchers)},{flushSync:(P&&P.flushSync)===!0})}function Mu(h){return Ye.set(h,(Ye.get(h)||0)+1),je.has(h)&&je.delete(h),y.fetchers.get(h)||vv}function El(h){let g=y.fetchers.get(h);N.has(h)&&!(g&&g.state==="loading"&&te.has(h))&&Wt(h),st.delete(h),te.delete(h),ne.delete(h),f.v7_fetcherPersist&&je.delete(h),Ht.delete(h),y.fetchers.delete(h)}function op(h){let g=(Ye.get(h)||0)-1;g<=0?(Ye.delete(h),je.add(h),f.v7_fetcherPersist||El(h)):Ye.set(h,g),Fe({fetchers:new Map(y.fetchers)})}function Wt(h){let g=N.get(h);g&&(g.abort(),N.delete(h))}function zu(h){for(let g of h){let S=Mu(g),P=Qt(S.data);y.fetchers.set(g,P)}}function ju(){let h=[],g=!1;for(let S of ne){let P=y.fetchers.get(S);K(P,"Expected fetcher: "+S),P.state==="loading"&&(ne.delete(S),h.push(S),g=!0)}return zu(h),g}function Ou(h){let g=[];for(let[S,P]of te)if(P0}function ap(h,g){let S=y.blockers.get(h)||Mr;return tt.get(h)!==g&&tt.set(h,g),S}function Fu(h){y.blockers.delete(h),tt.delete(h)}function xl(h,g){let S=y.blockers.get(h)||Mr;K(S.state==="unblocked"&&g.state==="blocked"||S.state==="blocked"&&g.state==="blocked"||S.state==="blocked"&&g.state==="proceeding"||S.state==="blocked"&&g.state==="unblocked"||S.state==="proceeding"&&g.state==="unblocked","Invalid blocker state transition: "+S.state+" -> "+g.state);let P=new Map(y.blockers);P.set(h,g),Fe({blockers:P})}function Iu(h){let{currentLocation:g,nextLocation:S,historyAction:P}=h;if(tt.size===0)return;tt.size>1&&Mn(!1,"A router only supports one blocker at a time");let M=Array.from(tt.entries()),[B,W]=M[M.length-1],F=y.blockers.get(B);if(!(F&&F.state==="proceeding")&&W({currentLocation:g,nextLocation:S,historyAction:P}))return B}function bi(h){let g=He(404,{pathname:h}),S=a||o,{matches:P,route:M}=cc(S);return eo(),{notFoundMatches:P,route:M,error:g}}function eo(h){let g=[];return Oe.forEach((S,P)=>{(!h||h(P))&&(S.cancel(),g.push(P),Oe.delete(P))}),g}function up(h,g,S){if(x=h,T=g,E=S||null,!m&&y.navigation===To){m=!0;let P=Au(y.location,y.matches);P!=null&&Fe({restoreScrollPosition:P})}return()=>{x=null,T=null,E=null}}function Uu(h,g){return E&&E(h,g.map(P=>Km(P,y.loaderData)))||h.key}function sp(h,g){if(x&&T){let S=Uu(h,g);x[S]=T()}}function Au(h,g){if(x){let S=Uu(h,g),P=x[S];if(typeof P=="number")return P}return null}function kl(h,g,S){if(d)if(h){if(Object.keys(h[0].params).length>0)return{active:!0,matches:li(g,S,u,!0)}}else return{active:!0,matches:li(g,S,u,!0)||[]};return{active:!1,matches:null}}async function Cl(h,g,S,P){if(!d)return{type:"success",matches:h};let M=h;for(;;){let B=a==null,W=a||o,F=i;try{await d({signal:S,path:g,matches:M,fetcherKey:P,patch:(A,Q)=>{S.aborted||lc(A,Q,W,F,l)}})}catch(A){return{type:"error",error:A,partialMatches:M}}finally{B&&!S.aborted&&(o=[...o])}if(S.aborted)return{type:"aborted"};let I=wn(W,g,u);if(I)return{type:"success",matches:I};let O=li(W,g,u,!0);if(!O||M.length===O.length&&M.every((A,Q)=>A.route.id===O[Q].route.id))return{type:"success",matches:null};M=O}}function cp(h){i={},a=Ni(h,l,void 0,i)}function fp(h,g){let S=a==null;lc(h,g,a||o,i,l),S&&(o=[...o],Fe({}))}return z={get basename(){return u},get future(){return f},get state(){return y},get routes(){return o},get window(){return t},initialize:Jd,subscribe:qd,enableScrollRestoration:up,navigate:Nu,fetch:rp,revalidate:bd,createHref:h=>e.history.createHref(h),encodeLocation:h=>e.history.encodeLocation(h),getFetcher:Mu,deleteFetcher:op,dispose:Zd,getBlocker:ap,deleteBlocker:Fu,patchRoutes:fp,_internalFetchControllers:N,_internalActiveDeferreds:Oe,_internalSetRoutes:cp},z}function wv(e){return e!=null&&("formData"in e&&e.formData!=null||"body"in e&&e.body!==void 0)}function Pa(e,t,n,r,l,i,o,a){let u,s;if(o){u=[];for(let f of t)if(u.push(f),f.route.id===o){s=f;break}}else u=t,s=t[t.length-1];let d=Ji(l||".",Gi(u,i),Ft(e.pathname,n)||e.pathname,a==="path");if(l==null&&(d.search=e.search,d.hash=e.hash),(l==null||l===""||l===".")&&s){let f=_u(d.search);if(s.route.index&&!f)d.search=d.search?d.search.replace(/^\?/,"?index&"):"?index";else if(!s.route.index&&f){let p=new URLSearchParams(d.search),w=p.getAll("index");p.delete("index"),w.filter(E=>E).forEach(E=>p.append("index",E));let x=p.toString();d.search=x?"?"+x:""}}return r&&n!=="/"&&(d.pathname=d.pathname==="/"?n:Dt([n,d.pathname])),zn(d)}function ec(e,t,n,r){if(!r||!wv(r))return{path:n};if(r.formMethod&&!Dv(r.formMethod))return{path:n,error:He(405,{method:r.formMethod})};let l=()=>({path:n,error:He(400,{type:"invalid-body"})}),i=r.formMethod||"get",o=e?i.toUpperCase():i.toLowerCase(),a=Bd(n);if(r.body!==void 0){if(r.formEncType==="text/plain"){if(!pt(o))return l();let p=typeof r.body=="string"?r.body:r.body instanceof FormData||r.body instanceof URLSearchParams?Array.from(r.body.entries()).reduce((w,x)=>{let[E,T]=x;return""+w+E+"="+T+` +`},""):String(r.body);return{path:n,submission:{formMethod:o,formAction:a,formEncType:r.formEncType,formData:void 0,json:void 0,text:p}}}else if(r.formEncType==="application/json"){if(!pt(o))return l();try{let p=typeof r.body=="string"?JSON.parse(r.body):r.body;return{path:n,submission:{formMethod:o,formAction:a,formEncType:r.formEncType,formData:void 0,json:p,text:void 0}}}catch{return l()}}}K(typeof FormData=="function","FormData is not available in this environment");let u,s;if(r.formData)u=Ra(r.formData),s=r.formData;else if(r.body instanceof FormData)u=Ra(r.body),s=r.body;else if(r.body instanceof URLSearchParams)u=r.body,s=oc(u);else if(r.body==null)u=new URLSearchParams,s=new FormData;else try{u=new URLSearchParams(r.body),s=oc(u)}catch{return l()}let d={formMethod:o,formAction:a,formEncType:r&&r.formEncType||"application/x-www-form-urlencoded",formData:s,json:void 0,text:void 0};if(pt(d.formMethod))return{path:n,submission:d};let f=pn(n);return t&&f.search&&_u(f.search)&&u.append("index",""),f.search="?"+u,{path:zn(f),submission:d}}function tc(e,t,n){n===void 0&&(n=!1);let r=e.findIndex(l=>l.route.id===t);return r>=0?e.slice(0,n?r+1:r):e}function nc(e,t,n,r,l,i,o,a,u,s,d,f,p,w,x,E){let T=E?Ge(E[1])?E[1].error:E[1].data:void 0,m=e.createURL(t.location),c=e.createURL(l),v=n;i&&t.errors?v=tc(n,Object.keys(t.errors)[0],!0):E&&Ge(E[1])&&(v=tc(n,E[0]));let k=E?E[1].statusCode:void 0,L=o&&k&&k>=400,z=v.filter((C,H)=>{let{route:D}=C;if(D.lazy)return!0;if(D.loader==null)return!1;if(i)return _a(D,t.loaderData,t.errors);if(Sv(t.loaderData,t.matches[H],C)||u.some(Se=>Se===C.route.id))return!0;let q=t.matches[H],re=C;return rc(C,ue({currentUrl:m,currentParams:q.params,nextUrl:c,nextParams:re.params},r,{actionResult:T,actionStatus:k,defaultShouldRevalidate:L?!1:a||m.pathname+m.search===c.pathname+c.search||m.search!==c.search||Ud(q,re)}))}),y=[];return f.forEach((C,H)=>{if(i||!n.some(Pe=>Pe.route.id===C.routeId)||d.has(H))return;let D=wn(w,C.path,x);if(!D){y.push({key:H,routeId:C.routeId,path:C.path,matches:null,match:null,controller:null});return}let q=t.fetchers.get(H),re=Ar(D,C.path),Se=!1;p.has(H)?Se=!1:s.has(H)?(s.delete(H),Se=!0):q&&q.state!=="idle"&&q.data===void 0?Se=a:Se=rc(re,ue({currentUrl:m,currentParams:t.matches[t.matches.length-1].params,nextUrl:c,nextParams:n[n.length-1].params},r,{actionResult:T,actionStatus:k,defaultShouldRevalidate:L?!1:a})),Se&&y.push({key:H,routeId:C.routeId,path:C.path,matches:D,match:re,controller:new AbortController})}),[z,y]}function _a(e,t,n){if(e.lazy)return!0;if(!e.loader)return!1;let r=t!=null&&t[e.id]!==void 0,l=n!=null&&n[e.id]!==void 0;return!r&&l?!1:typeof e.loader=="function"&&e.loader.hydrate===!0?!0:!r&&!l}function Sv(e,t,n){let r=!t||n.route.id!==t.route.id,l=e[n.route.id]===void 0;return r||l}function Ud(e,t){let n=e.route.path;return e.pathname!==t.pathname||n!=null&&n.endsWith("*")&&e.params["*"]!==t.params["*"]}function rc(e,t){if(e.route.shouldRevalidate){let n=e.route.shouldRevalidate(t);if(typeof n=="boolean")return n}return t.defaultShouldRevalidate}function lc(e,t,n,r,l){var i;let o;if(e){let s=r[e];K(s,"No route found to patch children into: routeId = "+e),s.children||(s.children=[]),o=s.children}else o=n;let a=t.filter(s=>!o.some(d=>Ad(s,d))),u=Ni(a,l,[e||"_","patch",String(((i=o)==null?void 0:i.length)||"0")],r);o.push(...u)}function Ad(e,t){return"id"in e&&"id"in t&&e.id===t.id?!0:e.index===t.index&&e.path===t.path&&e.caseSensitive===t.caseSensitive?(!e.children||e.children.length===0)&&(!t.children||t.children.length===0)?!0:e.children.every((n,r)=>{var l;return(l=t.children)==null?void 0:l.some(i=>Ad(n,i))}):!1}async function Ev(e,t,n){if(!e.lazy)return;let r=await e.lazy();if(!e.lazy)return;let l=n[e.id];K(l,"No route found in manifest");let i={};for(let o in r){let u=l[o]!==void 0&&o!=="hasErrorBoundary";Mn(!u,'Route "'+l.id+'" has a static property "'+o+'" defined but its lazy function is also returning a value for this property. '+('The lazy route property "'+o+'" will be ignored.')),!u&&!$m.has(o)&&(i[o]=r[o])}Object.assign(l,i),Object.assign(l,ue({},t(l),{lazy:void 0}))}async function xv(e){let{matches:t}=e,n=t.filter(l=>l.shouldLoad);return(await Promise.all(n.map(l=>l.resolve()))).reduce((l,i,o)=>Object.assign(l,{[n[o].route.id]:i}),{})}async function kv(e,t,n,r,l,i,o,a,u,s){let d=i.map(w=>w.route.lazy?Ev(w.route,u,a):void 0),f=i.map((w,x)=>{let E=d[x],T=l.some(c=>c.route.id===w.route.id);return ue({},w,{shouldLoad:T,resolve:async c=>(c&&r.method==="GET"&&(w.route.lazy||w.route.loader)&&(T=!0),T?Cv(t,r,w,E,c,s):Promise.resolve({type:b.data,result:void 0}))})}),p=await e({matches:f,request:r,params:i[0].params,fetcherKey:o,context:s});try{await Promise.all(d)}catch{}return p}async function Cv(e,t,n,r,l,i){let o,a,u=s=>{let d,f=new Promise((x,E)=>d=E);a=()=>d(),t.signal.addEventListener("abort",a);let p=x=>typeof s!="function"?Promise.reject(new Error("You cannot call the handler for a route which defines a boolean "+('"'+e+'" [routeId: '+n.route.id+"]"))):s({request:t,params:n.params,context:i},...x!==void 0?[x]:[]),w=(async()=>{try{return{type:"data",result:await(l?l(E=>p(E)):p())}}catch(x){return{type:"error",result:x}}})();return Promise.race([w,f])};try{let s=n.route[e];if(r)if(s){let d,[f]=await Promise.all([u(s).catch(p=>{d=p}),r]);if(d!==void 0)throw d;o=f}else if(await r,s=n.route[e],s)o=await u(s);else if(e==="action"){let d=new URL(t.url),f=d.pathname+d.search;throw He(405,{method:t.method,pathname:f,routeId:n.route.id})}else return{type:b.data,result:void 0};else if(s)o=await u(s);else{let d=new URL(t.url),f=d.pathname+d.search;throw He(404,{pathname:f})}K(o.result!==void 0,"You defined "+(e==="action"?"an action":"a loader")+" for route "+('"'+n.route.id+"\" but didn't return anything from your `"+e+"` ")+"function. Please return a value or `null`.")}catch(s){return{type:b.error,result:s}}finally{a&&t.signal.removeEventListener("abort",a)}return o}async function Pv(e){let{result:t,type:n}=e;if(Hd(t)){let f;try{let p=t.headers.get("Content-Type");p&&/\bapplication\/json\b/.test(p)?t.body==null?f=null:f=await t.json():f=await t.text()}catch(p){return{type:b.error,error:p}}return n===b.error?{type:b.error,error:new Di(t.status,t.statusText,f),statusCode:t.status,headers:t.headers}:{type:b.data,data:f,statusCode:t.status,headers:t.headers}}if(n===b.error){if(fc(t)){var r,l;if(t.data instanceof Error){var i,o;return{type:b.error,error:t.data,statusCode:(i=t.init)==null?void 0:i.status,headers:(o=t.init)!=null&&o.headers?new Headers(t.init.headers):void 0}}return{type:b.error,error:new Di(((r=t.init)==null?void 0:r.status)||500,void 0,t.data),statusCode:pl(t)?t.status:void 0,headers:(l=t.init)!=null&&l.headers?new Headers(t.init.headers):void 0}}return{type:b.error,error:t,statusCode:pl(t)?t.status:void 0}}if(Tv(t)){var a,u;return{type:b.deferred,deferredData:t,statusCode:(a=t.init)==null?void 0:a.status,headers:((u=t.init)==null?void 0:u.headers)&&new Headers(t.init.headers)}}if(fc(t)){var s,d;return{type:b.data,data:t.data,statusCode:(s=t.init)==null?void 0:s.status,headers:(d=t.init)!=null&&d.headers?new Headers(t.init.headers):void 0}}return{type:b.data,data:t}}function _v(e,t,n,r,l,i){let o=e.headers.get("Location");if(K(o,"Redirects returned/thrown from loaders/actions must have a Location header"),!Cu.test(o)){let a=r.slice(0,r.findIndex(u=>u.route.id===n)+1);o=Pa(new URL(t.url),a,l,!0,o,i),e.headers.set("Location",o)}return e}function ic(e,t,n,r){let l=["about:","blob:","chrome:","chrome-untrusted:","content:","data:","devtools:","file:","filesystem:","javascript:"];if(Cu.test(e)){let i=e,o=i.startsWith("//")?new URL(t.protocol+i):new URL(i);if(l.includes(o.protocol))throw new Error("Invalid redirect location");let a=Ft(o.pathname,n)!=null;if(o.origin===t.origin&&a)return o.pathname+o.search+o.hash}try{let i=r.createURL(e);if(l.includes(i.protocol))throw new Error("Invalid redirect location")}catch{}return e}function Vn(e,t,n,r){let l=e.createURL(Bd(t)).toString(),i={signal:n};if(r&&pt(r.formMethod)){let{formMethod:o,formEncType:a}=r;i.method=o.toUpperCase(),a==="application/json"?(i.headers=new Headers({"Content-Type":a}),i.body=JSON.stringify(r.json)):a==="text/plain"?i.body=r.text:a==="application/x-www-form-urlencoded"&&r.formData?i.body=Ra(r.formData):i.body=r.formData}return new Request(l,i)}function Ra(e){let t=new URLSearchParams;for(let[n,r]of e.entries())t.append(n,typeof r=="string"?r:r.name);return t}function oc(e){let t=new FormData;for(let[n,r]of e.entries())t.append(n,r);return t}function Rv(e,t,n,r,l){let i={},o=null,a,u=!1,s={},d=n&&Ge(n[1])?n[1].error:void 0;return e.forEach(f=>{if(!(f.route.id in t))return;let p=f.route.id,w=t[p];if(K(!Cn(w),"Cannot handle redirect results in processLoaderData"),Ge(w)){let x=w.error;d!==void 0&&(x=d,d=void 0),o=o||{};{let E=Sn(e,p);o[E.route.id]==null&&(o[E.route.id]=x)}i[p]=void 0,u||(u=!0,a=pl(w.error)?w.error.status:500),w.headers&&(s[p]=w.headers)}else qt(w)?(r.set(p,w.deferredData),i[p]=w.deferredData.data,w.statusCode!=null&&w.statusCode!==200&&!u&&(a=w.statusCode),w.headers&&(s[p]=w.headers)):(i[p]=w.data,w.statusCode&&w.statusCode!==200&&!u&&(a=w.statusCode),w.headers&&(s[p]=w.headers))}),d!==void 0&&n&&(o={[n[0]]:d},i[n[0]]=void 0),{loaderData:i,errors:o,statusCode:a||200,loaderHeaders:s}}function ac(e,t,n,r,l,i,o){let{loaderData:a,errors:u}=Rv(t,n,r,o);return l.forEach(s=>{let{key:d,match:f,controller:p}=s,w=i[d];if(K(w,"Did not find corresponding fetcher result"),!(p&&p.signal.aborted))if(Ge(w)){let x=Sn(e.matches,f==null?void 0:f.route.id);u&&u[x.route.id]||(u=ue({},u,{[x.route.id]:w.error})),e.fetchers.delete(d)}else if(Cn(w))K(!1,"Unhandled fetcher revalidation redirect");else if(qt(w))K(!1,"Unhandled fetcher deferred data");else{let x=Qt(w.data);e.fetchers.set(d,x)}}),{loaderData:a,errors:u}}function uc(e,t,n,r){let l=ue({},t);for(let i of n){let o=i.route.id;if(t.hasOwnProperty(o)?t[o]!==void 0&&(l[o]=t[o]):e[o]!==void 0&&i.route.loader&&(l[o]=e[o]),r&&r.hasOwnProperty(o))break}return l}function sc(e){return e?Ge(e[1])?{actionData:{}}:{actionData:{[e[0]]:e[1].data}}:{}}function Sn(e,t){return(t?e.slice(0,e.findIndex(r=>r.route.id===t)+1):[...e]).reverse().find(r=>r.route.hasErrorBoundary===!0)||e[0]}function cc(e){let t=e.length===1?e[0]:e.find(n=>n.index||!n.path||n.path==="/")||{id:"__shim-error-route__"};return{matches:[{params:{},pathname:"",pathnameBase:"",route:t}],route:t}}function He(e,t){let{pathname:n,routeId:r,method:l,type:i,message:o}=t===void 0?{}:t,a="Unknown Server Error",u="Unknown @remix-run/router error";return e===400?(a="Bad Request",l&&n&&r?u="You made a "+l+' request to "'+n+'" but '+('did not provide a `loader` for route "'+r+'", ')+"so there is no way to handle the request.":i==="defer-action"?u="defer() is not supported in actions":i==="invalid-body"&&(u="Unable to encode submission body")):e===403?(a="Forbidden",u='Route "'+r+'" does not match URL "'+n+'"'):e===404?(a="Not Found",u='No route matches URL "'+n+'"'):e===405&&(a="Method Not Allowed",l&&n&&r?u="You made a "+l.toUpperCase()+' request to "'+n+'" but '+('did not provide an `action` for route "'+r+'", ')+"so there is no way to handle the request.":l&&(u='Invalid request method "'+l.toUpperCase()+'"')),new Di(e||500,a,new Error(u),!0)}function $l(e){let t=Object.entries(e);for(let n=t.length-1;n>=0;n--){let[r,l]=t[n];if(Cn(l))return{key:r,result:l}}}function Bd(e){let t=typeof e=="string"?pn(e):e;return zn(ue({},t,{hash:""}))}function Lv(e,t){return e.pathname!==t.pathname||e.search!==t.search?!1:e.hash===""?t.hash!=="":e.hash===t.hash?!0:t.hash!==""}function Nv(e){return Hd(e.result)&&hv.has(e.result.status)}function qt(e){return e.type===b.deferred}function Ge(e){return e.type===b.error}function Cn(e){return(e&&e.type)===b.redirect}function fc(e){return typeof e=="object"&&e!=null&&"type"in e&&"data"in e&&"init"in e&&e.type==="DataWithResponseInit"}function Tv(e){let t=e;return t&&typeof t=="object"&&typeof t.data=="object"&&typeof t.subscribe=="function"&&typeof t.cancel=="function"&&typeof t.resolveData=="function"}function Hd(e){return e!=null&&typeof e.status=="number"&&typeof e.statusText=="string"&&typeof e.headers=="object"&&typeof e.body<"u"}function Dv(e){return pv.has(e.toLowerCase())}function pt(e){return fv.has(e.toLowerCase())}async function Mv(e,t,n,r,l){let i=Object.entries(t);for(let o=0;o(p==null?void 0:p.route.id)===a);if(!s)continue;let d=r.find(p=>p.route.id===s.route.id),f=d!=null&&!Ud(d,s)&&(l&&l[s.route.id])!==void 0;qt(u)&&f&&await Pu(u,n,!1).then(p=>{p&&(t[a]=p)})}}async function zv(e,t,n){for(let r=0;r(s==null?void 0:s.route.id)===i)&&qt(a)&&(K(o,"Expected an AbortController for revalidating fetcher deferred result"),await Pu(a,o.signal,!0).then(s=>{s&&(t[l]=s)}))}}async function Pu(e,t,n){if(n===void 0&&(n=!1),!await e.deferredData.resolveData(t)){if(n)try{return{type:b.data,data:e.deferredData.unwrappedData}}catch(l){return{type:b.error,error:l}}return{type:b.data,data:e.deferredData.data}}}function _u(e){return new URLSearchParams(e).getAll("index").some(t=>t==="")}function Ar(e,t){let n=typeof t=="string"?pn(t).search:t.search;if(e[e.length-1].route.index&&_u(n||""))return e[e.length-1];let r=Od(e);return r[r.length-1]}function dc(e){let{formMethod:t,formAction:n,formEncType:r,text:l,formData:i,json:o}=e;if(!(!t||!n||!r)){if(l!=null)return{formMethod:t,formAction:n,formEncType:r,formData:void 0,json:void 0,text:l};if(i!=null)return{formMethod:t,formAction:n,formEncType:r,formData:i,json:void 0,text:void 0};if(o!==void 0)return{formMethod:t,formAction:n,formEncType:r,formData:void 0,json:o,text:void 0}}}function Do(e,t){return t?{state:"loading",location:e,formMethod:t.formMethod,formAction:t.formAction,formEncType:t.formEncType,formData:t.formData,json:t.json,text:t.text}:{state:"loading",location:e,formMethod:void 0,formAction:void 0,formEncType:void 0,formData:void 0,json:void 0,text:void 0}}function jv(e,t){return{state:"submitting",location:e,formMethod:t.formMethod,formAction:t.formAction,formEncType:t.formEncType,formData:t.formData,json:t.json,text:t.text}}function zr(e,t){return e?{state:"loading",formMethod:e.formMethod,formAction:e.formAction,formEncType:e.formEncType,formData:e.formData,json:e.json,text:e.text,data:t}:{state:"loading",formMethod:void 0,formAction:void 0,formEncType:void 0,formData:void 0,json:void 0,text:void 0,data:t}}function Ov(e,t){return{state:"submitting",formMethod:e.formMethod,formAction:e.formAction,formEncType:e.formEncType,formData:e.formData,json:e.json,text:e.text,data:t?t.data:void 0}}function Qt(e){return{state:"idle",formMethod:void 0,formAction:void 0,formEncType:void 0,formData:void 0,json:void 0,text:void 0,data:e}}function Fv(e,t){try{let n=e.sessionStorage.getItem(Id);if(n){let r=JSON.parse(n);for(let[l,i]of Object.entries(r||{}))i&&Array.isArray(i)&&t.set(l,new Set(i||[]))}}catch{}}function Iv(e,t){if(t.size>0){let n={};for(let[r,l]of t)n[r]=[...l];try{e.sessionStorage.setItem(Id,JSON.stringify(n))}catch(r){Mn(!1,"Failed to save applied view transitions in sessionStorage ("+r+").")}}}/** + * React Router v6.30.3 + * + * Copyright (c) Remix Software Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE.md file in the root directory of this source tree. + * + * @license MIT + */function Mi(){return Mi=Object.assign?Object.assign.bind():function(e){for(var t=1;t{a.current=!0}),_.useCallback(function(s,d){if(d===void 0&&(d={}),!a.current)return;if(typeof s=="number"){r.go(s);return}let f=Ji(s,JSON.parse(o),i,d.relative==="path");e==null&&t!=="/"&&(f.pathname=f.pathname==="/"?t:Dt([t,f.pathname])),(d.replace?r.replace:r.push)(f,d.state,d)},[t,r,o,i,e])}const Bv=_.createContext(null);function Hv(e){let t=_.useContext(At).outlet;return t&&_.createElement(Bv.Provider,{value:e},t)}function Zi(e,t){let{relative:n}=t===void 0?{}:t,{future:r}=_.useContext(Ut),{matches:l}=_.useContext(At),{pathname:i}=wr(),o=JSON.stringify(Gi(l,r.v7_relativeSplatPath));return _.useMemo(()=>Ji(e,JSON.parse(o),i,n==="path"),[e,o,i,n])}function Vv(e,t,n,r){gr()||K(!1);let{navigator:l}=_.useContext(Ut),{matches:i}=_.useContext(At),o=i[i.length-1],a=o?o.params:{};o&&o.pathname;let u=o?o.pathnameBase:"/";o&&o.route;let s=wr(),d;d=s;let f=d.pathname||"/",p=f;if(u!=="/"){let E=u.replace(/^\//,"").split("/");p="/"+f.replace(/^\//,"").split("/").slice(E.length).join("/")}let w=wn(e,{pathname:p});return Yv(w&&w.map(E=>Object.assign({},E,{params:Object.assign({},a,E.params),pathname:Dt([u,l.encodeLocation?l.encodeLocation(E.pathname).pathname:E.pathname]),pathnameBase:E.pathnameBase==="/"?u:Dt([u,l.encodeLocation?l.encodeLocation(E.pathnameBase).pathname:E.pathnameBase])})),i,n,r)}function Wv(){let e=Zv(),t=pl(e)?e.status+" "+e.statusText:e instanceof Error?e.message:JSON.stringify(e),n=e instanceof Error?e.stack:null,l={padding:"0.5rem",backgroundColor:"rgba(200,200,200, 0.5)"};return _.createElement(_.Fragment,null,_.createElement("h2",null,"Unexpected Application Error!"),_.createElement("h3",{style:{fontStyle:"italic"}},t),n?_.createElement("pre",{style:l},n):null,null)}const $v=_.createElement(Wv,null);class Qv extends _.Component{constructor(t){super(t),this.state={location:t.location,revalidation:t.revalidation,error:t.error}}static getDerivedStateFromError(t){return{error:t}}static getDerivedStateFromProps(t,n){return n.location!==t.location||n.revalidation!=="idle"&&t.revalidation==="idle"?{error:t.error,location:t.location,revalidation:t.revalidation}:{error:t.error!==void 0?t.error:n.error,location:n.location,revalidation:t.revalidation||n.revalidation}}componentDidCatch(t,n){console.error("React Router caught the following error during render",t,n)}render(){return this.state.error!==void 0?_.createElement(At.Provider,{value:this.props.routeContext},_.createElement(Vd.Provider,{value:this.state.error,children:this.props.component})):this.props.children}}function Kv(e){let{routeContext:t,match:n,children:r}=e,l=_.useContext(wl);return l&&l.static&&l.staticContext&&(n.route.errorElement||n.route.ErrorBoundary)&&(l.staticContext._deepestRenderedBoundaryId=n.route.id),_.createElement(At.Provider,{value:t},r)}function Yv(e,t,n,r){var l;if(t===void 0&&(t=[]),n===void 0&&(n=null),r===void 0&&(r=null),e==null){var i;if(!n)return null;if(n.errors)e=n.matches;else if((i=r)!=null&&i.v7_partialHydration&&t.length===0&&!n.initialized&&n.matches.length>0)e=n.matches;else return null}let o=e,a=(l=n)==null?void 0:l.errors;if(a!=null){let d=o.findIndex(f=>f.route.id&&(a==null?void 0:a[f.route.id])!==void 0);d>=0||K(!1),o=o.slice(0,Math.min(o.length,d+1))}let u=!1,s=-1;if(n&&r&&r.v7_partialHydration)for(let d=0;d=0?o=o.slice(0,s+1):o=[o[0]];break}}}return o.reduceRight((d,f,p)=>{let w,x=!1,E=null,T=null;n&&(w=a&&f.route.id?a[f.route.id]:void 0,E=f.route.errorElement||$v,u&&(s<0&&p===0?(bv("route-fallback"),x=!0,T=null):s===p&&(x=!0,T=f.route.hydrateFallbackElement||null)));let m=t.concat(o.slice(0,p+1)),c=()=>{let v;return w?v=E:x?v=T:f.route.Component?v=_.createElement(f.route.Component,null):f.route.element?v=f.route.element:v=d,_.createElement(Kv,{match:f,routeContext:{outlet:d,matches:m,isDataRoute:n!=null},children:v})};return n&&(f.route.ErrorBoundary||f.route.errorElement||p===0)?_.createElement(Qv,{location:n.location,revalidation:n.revalidation,component:E,error:w,children:c(),routeContext:{outlet:null,matches:m,isDataRoute:!0}}):c()},null)}var Qd=function(e){return e.UseBlocker="useBlocker",e.UseRevalidator="useRevalidator",e.UseNavigateStable="useNavigate",e}(Qd||{}),Kd=function(e){return e.UseBlocker="useBlocker",e.UseLoaderData="useLoaderData",e.UseActionData="useActionData",e.UseRouteError="useRouteError",e.UseNavigation="useNavigation",e.UseRouteLoaderData="useRouteLoaderData",e.UseMatches="useMatches",e.UseRevalidator="useRevalidator",e.UseNavigateStable="useNavigate",e.UseRouteId="useRouteId",e}(Kd||{});function Xv(e){let t=_.useContext(wl);return t||K(!1),t}function Gv(e){let t=_.useContext(Ru);return t||K(!1),t}function Jv(e){let t=_.useContext(At);return t||K(!1),t}function Yd(e){let t=Jv(),n=t.matches[t.matches.length-1];return n.route.id||K(!1),n.route.id}function Zv(){var e;let t=_.useContext(Vd),n=Gv(Kd.UseRouteError),r=Yd();return t!==void 0?t:(e=n.errors)==null?void 0:e[r]}function qv(){let{router:e}=Xv(Qd.UseNavigateStable),t=Yd(),n=_.useRef(!1);return Wd(()=>{n.current=!0}),_.useCallback(function(l,i){i===void 0&&(i={}),n.current&&(typeof l=="number"?e.navigate(l):e.navigate(l,Mi({fromRouteId:t},i)))},[e,t])}const pc={};function bv(e,t,n){pc[e]||(pc[e]=!0)}function ey(e,t){e==null||e.v7_startTransition,(e==null?void 0:e.v7_relativeSplatPath)===void 0&&(!t||t.v7_relativeSplatPath),t&&(t.v7_fetcherPersist,t.v7_normalizeFormMethod,t.v7_partialHydration,t.v7_skipActionErrorRevalidation)}function ty(e){let{to:t,replace:n,state:r,relative:l}=e;gr()||K(!1);let{future:i,static:o}=_.useContext(Ut),{matches:a}=_.useContext(At),{pathname:u}=wr(),s=$d(),d=Ji(t,Gi(a,i.v7_relativeSplatPath),u,l==="path"),f=JSON.stringify(d);return _.useEffect(()=>s(JSON.parse(f),{replace:n,state:r,relative:l}),[s,f,l,n,r]),null}function ny(e){return Hv(e.context)}function ry(e){let{basename:t="/",children:n=null,location:r,navigationType:l=ge.Pop,navigator:i,static:o=!1,future:a}=e;gr()&&K(!1);let u=t.replace(/^\/*/,"/"),s=_.useMemo(()=>({basename:u,navigator:i,static:o,future:Mi({v7_relativeSplatPath:!1},a)}),[u,a,i,o]);typeof r=="string"&&(r=pn(r));let{pathname:d="/",search:f="",hash:p="",state:w=null,key:x="default"}=r,E=_.useMemo(()=>{let T=Ft(d,u);return T==null?null:{location:{pathname:T,search:f,hash:p,state:w,key:x},navigationType:l}},[u,d,f,p,w,x,l]);return E==null?null:_.createElement(Ut.Provider,{value:s},_.createElement(Lu.Provider,{children:n,value:E}))}new Promise(()=>{});function ly(e){let t={hasErrorBoundary:e.ErrorBoundary!=null||e.errorElement!=null};return e.Component&&Object.assign(t,{element:_.createElement(e.Component),Component:void 0}),e.HydrateFallback&&Object.assign(t,{hydrateFallbackElement:_.createElement(e.HydrateFallback),HydrateFallback:void 0}),e.ErrorBoundary&&Object.assign(t,{errorElement:_.createElement(e.ErrorBoundary),ErrorBoundary:void 0}),t}/** + * React Router DOM v6.30.3 + * + * Copyright (c) Remix Software Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE.md file in the root directory of this source tree. + * + * @license MIT + */function hr(){return hr=Object.assign?Object.assign.bind():function(e){for(var t=1;t=0)&&(n[l]=e[l]);return n}function iy(e){return!!(e.metaKey||e.altKey||e.ctrlKey||e.shiftKey)}function oy(e,t){return e.button===0&&(!t||t==="_self")&&!iy(e)}const ay=["onClick","relative","reloadDocument","replace","state","target","to","preventScrollReset","viewTransition"],uy=["aria-current","caseSensitive","className","end","style","to","viewTransition","children"],sy="6";try{window.__reactRouterVersion=sy}catch{}function cy(e,t){return gv({basename:void 0,future:hr({},void 0,{v7_prependBasename:!0}),history:Hm({window:void 0}),hydrationData:fy(),routes:e,mapRouteProperties:ly,dataStrategy:void 0,patchRoutesOnNavigation:void 0,window:void 0}).initialize()}function fy(){var e;let t=(e=window)==null?void 0:e.__staticRouterHydrationData;return t&&t.errors&&(t=hr({},t,{errors:dy(t.errors)})),t}function dy(e){if(!e)return null;let t=Object.entries(e),n={};for(let[r,l]of t)if(l&&l.__type==="RouteErrorResponse")n[r]=new Di(l.status,l.statusText,l.data,l.internal===!0);else if(l&&l.__type==="Error"){if(l.__subType){let i=window[l.__subType];if(typeof i=="function")try{let o=new i(l.message);o.stack="",n[r]=o}catch{}}if(n[r]==null){let i=new Error(l.message);i.stack="",n[r]=i}}else n[r]=l;return n}const Gd=_.createContext({isTransitioning:!1}),py=_.createContext(new Map),hy="startTransition",hc=Lp[hy],my="flushSync",mc=Bm[my];function vy(e){hc?hc(e):e()}function jr(e){mc?mc(e):e()}class yy{constructor(){this.status="pending",this.promise=new Promise((t,n)=>{this.resolve=r=>{this.status==="pending"&&(this.status="resolved",t(r))},this.reject=r=>{this.status==="pending"&&(this.status="rejected",n(r))}})}}function gy(e){let{fallbackElement:t,router:n,future:r}=e,[l,i]=_.useState(n.state),[o,a]=_.useState(),[u,s]=_.useState({isTransitioning:!1}),[d,f]=_.useState(),[p,w]=_.useState(),[x,E]=_.useState(),T=_.useRef(new Map),{v7_startTransition:m}=r||{},c=_.useCallback(C=>{m?vy(C):C()},[m]),v=_.useCallback((C,H)=>{let{deletedFetchers:D,flushSync:q,viewTransitionOpts:re}=H;C.fetchers.forEach((Pe,ut)=>{Pe.data!==void 0&&T.current.set(ut,Pe.data)}),D.forEach(Pe=>T.current.delete(Pe));let Se=n.window==null||n.window.document==null||typeof n.window.document.startViewTransition!="function";if(!re||Se){q?jr(()=>i(C)):c(()=>i(C));return}if(q){jr(()=>{p&&(d&&d.resolve(),p.skipTransition()),s({isTransitioning:!0,flushSync:!0,currentLocation:re.currentLocation,nextLocation:re.nextLocation})});let Pe=n.window.document.startViewTransition(()=>{jr(()=>i(C))});Pe.finished.finally(()=>{jr(()=>{f(void 0),w(void 0),a(void 0),s({isTransitioning:!1})})}),jr(()=>w(Pe));return}p?(d&&d.resolve(),p.skipTransition(),E({state:C,currentLocation:re.currentLocation,nextLocation:re.nextLocation})):(a(C),s({isTransitioning:!0,flushSync:!1,currentLocation:re.currentLocation,nextLocation:re.nextLocation}))},[n.window,p,d,T,c]);_.useLayoutEffect(()=>n.subscribe(v),[n,v]),_.useEffect(()=>{u.isTransitioning&&!u.flushSync&&f(new yy)},[u]),_.useEffect(()=>{if(d&&o&&n.window){let C=o,H=d.promise,D=n.window.document.startViewTransition(async()=>{c(()=>i(C)),await H});D.finished.finally(()=>{f(void 0),w(void 0),a(void 0),s({isTransitioning:!1})}),w(D)}},[c,o,d,n.window]),_.useEffect(()=>{d&&o&&l.location.key===o.location.key&&d.resolve()},[d,p,l.location,o]),_.useEffect(()=>{!u.isTransitioning&&x&&(a(x.state),s({isTransitioning:!0,flushSync:!1,currentLocation:x.currentLocation,nextLocation:x.nextLocation}),E(void 0))},[u.isTransitioning,x]),_.useEffect(()=>{},[]);let k=_.useMemo(()=>({createHref:n.createHref,encodeLocation:n.encodeLocation,go:C=>n.navigate(C),push:(C,H,D)=>n.navigate(C,{state:H,preventScrollReset:D==null?void 0:D.preventScrollReset}),replace:(C,H,D)=>n.navigate(C,{replace:!0,state:H,preventScrollReset:D==null?void 0:D.preventScrollReset})}),[n]),L=n.basename||"/",z=_.useMemo(()=>({router:n,navigator:k,static:!1,basename:L}),[n,k,L]),y=_.useMemo(()=>({v7_relativeSplatPath:n.future.v7_relativeSplatPath}),[n.future.v7_relativeSplatPath]);return _.useEffect(()=>ey(r,n.future),[r,n.future]),_.createElement(_.Fragment,null,_.createElement(wl.Provider,{value:z},_.createElement(Ru.Provider,{value:l},_.createElement(py.Provider,{value:T.current},_.createElement(Gd.Provider,{value:u},_.createElement(ry,{basename:L,location:l.location,navigationType:l.historyAction,navigator:k,future:y},l.initialized||n.future.v7_partialHydration?_.createElement(wy,{routes:n.routes,future:n.future,state:l}):t))))),null)}const wy=_.memo(Sy);function Sy(e){let{routes:t,future:n,state:r}=e;return Vv(t,void 0,r,n)}const Ey=typeof window<"u"&&typeof window.document<"u"&&typeof window.document.createElement<"u",xy=/^(?:[a-z][a-z0-9+.-]*:|\/\/)/i,ky=_.forwardRef(function(t,n){let{onClick:r,relative:l,reloadDocument:i,replace:o,state:a,target:u,to:s,preventScrollReset:d,viewTransition:f}=t,p=Xd(t,ay),{basename:w}=_.useContext(Ut),x,E=!1;if(typeof s=="string"&&xy.test(s)&&(x=s,Ey))try{let v=new URL(window.location.href),k=s.startsWith("//")?new URL(v.protocol+s):new URL(s),L=Ft(k.pathname,w);k.origin===v.origin&&L!=null?s=L+k.search+k.hash:E=!0}catch{}let T=Uv(s,{relative:l}),m=_y(s,{replace:o,state:a,target:u,preventScrollReset:d,relative:l,viewTransition:f});function c(v){r&&r(v),v.defaultPrevented||m(v)}return _.createElement("a",hr({},p,{href:x||T,onClick:E||i?r:c,ref:n,target:u}))}),Cy=_.forwardRef(function(t,n){let{"aria-current":r="page",caseSensitive:l=!1,className:i="",end:o=!1,style:a,to:u,viewTransition:s,children:d}=t,f=Xd(t,uy),p=Zi(u,{relative:f.relative}),w=wr(),x=_.useContext(Ru),{navigator:E,basename:T}=_.useContext(Ut),m=x!=null&&Ry(p)&&s===!0,c=E.encodeLocation?E.encodeLocation(p).pathname:p.pathname,v=w.pathname,k=x&&x.navigation&&x.navigation.location?x.navigation.location.pathname:null;l||(v=v.toLowerCase(),k=k?k.toLowerCase():null,c=c.toLowerCase()),k&&T&&(k=Ft(k,T)||k);const L=c!=="/"&&c.endsWith("/")?c.length-1:c.length;let z=v===c||!o&&v.startsWith(c)&&v.charAt(L)==="/",y=k!=null&&(k===c||!o&&k.startsWith(c)&&k.charAt(c.length)==="/"),C={isActive:z,isPending:y,isTransitioning:m},H=z?r:void 0,D;typeof i=="function"?D=i(C):D=[i,z?"active":null,y?"pending":null,m?"transitioning":null].filter(Boolean).join(" ");let q=typeof a=="function"?a(C):a;return _.createElement(ky,hr({},f,{"aria-current":H,className:D,ref:n,style:q,to:u,viewTransition:s}),typeof d=="function"?d(C):d)});var La;(function(e){e.UseScrollRestoration="useScrollRestoration",e.UseSubmit="useSubmit",e.UseSubmitFetcher="useSubmitFetcher",e.UseFetcher="useFetcher",e.useViewTransitionState="useViewTransitionState"})(La||(La={}));var vc;(function(e){e.UseFetcher="useFetcher",e.UseFetchers="useFetchers",e.UseScrollRestoration="useScrollRestoration"})(vc||(vc={}));function Py(e){let t=_.useContext(wl);return t||K(!1),t}function _y(e,t){let{target:n,replace:r,state:l,preventScrollReset:i,relative:o,viewTransition:a}=t===void 0?{}:t,u=$d(),s=wr(),d=Zi(e,{relative:o});return _.useCallback(f=>{if(oy(f,n)){f.preventDefault();let p=r!==void 0?r:zn(s)===zn(d);u(e,{replace:p,state:l,preventScrollReset:i,relative:o,viewTransition:a})}},[s,u,d,r,l,n,e,i,o,a])}function Ry(e,t){t===void 0&&(t={});let n=_.useContext(Gd);n==null&&K(!1);let{basename:r}=Py(La.useViewTransitionState),l=Zi(e,{relative:t.relative});if(!n.isTransitioning)return!1;let i=Ft(n.currentLocation.pathname,r)||n.currentLocation.pathname,o=Ft(n.nextLocation.pathname,r)||n.nextLocation.pathname;return Ti(l.pathname,o)!=null||Ti(l.pathname,i)!=null}function Fn(e){return U.jsxs("section",{className:"page",children:[U.jsx("h1",{children:e.title}),U.jsx("p",{children:e.subtitle}),e.children]})}function Ql(e){return U.jsxs("div",{className:"card",children:[U.jsx("h3",{children:e.title}),e.children]})}function Ly(){return U.jsx(Fn,{title:"Dashboard",subtitle:"High-level operational overview for Osham.",children:U.jsxs("div",{className:"card-grid",children:[U.jsx(Ql,{title:"Service Health",children:U.jsxs("p",{children:["Wire to ",U.jsx("code",{children:"/__osham/admin/health"}),"."]})}),U.jsx(Ql,{title:"Startup Summary",children:U.jsxs("p",{children:["Wire to ",U.jsx("code",{children:"/__osham/admin/startup-summary"}),"."]})}),U.jsx(Ql,{title:"Metrics Summary",children:U.jsxs("p",{children:["Wire to ",U.jsx("code",{children:"/__osham/admin/metrics/summary"}),"."]})}),U.jsx(Ql,{title:"Revision",children:U.jsx("p",{children:"Show current config revision and apply state."})})]})})}function Ny(){return U.jsx(Fn,{title:"Config",subtitle:"Landing page for global settings and namespace editors.",children:U.jsxs("div",{className:"code-block",children:[U.jsx("strong",{children:"Planned next:"}),U.jsxs("ul",{children:[U.jsxs("li",{children:["load current config from ",U.jsx("code",{children:"/__osham/admin/config"})]}),U.jsx("li",{children:"draft editing for global config + namespaces"}),U.jsx("li",{children:"validate, save, and reload actions"})]})]})})}function Ty(){return U.jsx(Fn,{title:"Metrics",subtitle:"Namespace and aggregate cache metrics.",children:U.jsxs("div",{className:"code-block",children:["Hook into ",U.jsx("code",{children:"/__osham/admin/metrics/summary"})," and ",U.jsx("code",{children:"/__osham/admin/metrics/namespaces"}),"."]})})}function Dy(){return U.jsx(Fn,{title:"Health",subtitle:"Operational health and startup visibility.",children:U.jsx("div",{className:"code-block",children:"Surface cache backend, uptime, revision, and feature flags here."})})}function My(){return U.jsx(Fn,{title:"Purge",subtitle:"Safe cache invalidation tools.",children:U.jsxs("div",{className:"code-block",children:["Next step: connect form to ",U.jsx("code",{children:"POST /__osham/admin/purge"})," with warning display and dry-run support."]})})}function zy(){return U.jsx(Fn,{title:"Audit",subtitle:"Recent admin actions.",children:U.jsxs("div",{className:"code-block",children:["Next step: table view over ",U.jsx("code",{children:"/__osham/admin/audit"}),"."]})})}function jy(){return U.jsx(Fn,{title:"Not Found",subtitle:"That route does not exist."})}const Oy=[["/admin/dashboard","Dashboard"],["/admin/config","Config"],["/admin/metrics","Metrics"],["/admin/health","Health"],["/admin/purge","Purge"],["/admin/audit","Audit"]];function Fy(){return U.jsxs("aside",{className:"sidebar",children:[U.jsx("div",{className:"brand",children:"Osham Admin"}),U.jsx("nav",{className:"nav-list",children:Oy.map(([e,t])=>U.jsx(Cy,{to:e,className:({isActive:n})=>`nav-link${n?" active":""}`,children:t},e))})]})}function Iy(){return U.jsxs("div",{className:"app-shell",children:[U.jsx(Fy,{}),U.jsx("main",{className:"content-shell",children:U.jsx(ny,{})})]})}const Uy=cy([{path:"/",element:U.jsx(Iy,{}),children:[{index:!0,element:U.jsx(ty,{to:"/admin/dashboard",replace:!0})},{path:"/admin/dashboard",element:U.jsx(Ly,{})},{path:"/admin/config",element:U.jsx(Ny,{})},{path:"/admin/metrics",element:U.jsx(Ty,{})},{path:"/admin/health",element:U.jsx(Dy,{})},{path:"/admin/purge",element:U.jsx(My,{})},{path:"/admin/audit",element:U.jsx(zy,{})},{path:"*",element:U.jsx(jy,{})}]}]);Mo.createRoot(document.getElementById("root")).render(U.jsx(Nc.StrictMode,{children:U.jsx(gy,{router:Uy})})); diff --git a/admin-ui/dist/index.html b/admin-ui/dist/index.html new file mode 100644 index 0000000..13c7016 --- /dev/null +++ b/admin-ui/dist/index.html @@ -0,0 +1,13 @@ + + + + + + Osham Admin + + + + +
+ + diff --git a/admin-ui/index.html b/admin-ui/index.html new file mode 100644 index 0000000..b356922 --- /dev/null +++ b/admin-ui/index.html @@ -0,0 +1,12 @@ + + + + + + Osham Admin + + +
+ + + diff --git a/admin-ui/package-lock.json b/admin-ui/package-lock.json new file mode 100644 index 0000000..d285571 --- /dev/null +++ b/admin-ui/package-lock.json @@ -0,0 +1,1771 @@ +{ + "name": "osham-admin-ui", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "osham-admin-ui", + "version": "0.1.0", + "dependencies": { + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-router-dom": "^6.30.1" + }, + "devDependencies": { + "@types/react": "^18.3.12", + "@types/react-dom": "^18.3.1", + "@vitejs/plugin-react": "^4.3.4", + "typescript": "^5.6.3", + "vite": "^5.4.10" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", + "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", + "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helpers": "^7.28.6", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@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.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz", + "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz", + "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "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/template": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@remix-run/router": { + "version": "1.23.2", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.2.tgz", + "integrity": "sha512-Ic6m2U/rMjTkhERIa/0ZtXJP17QUi2CbWE7cqx4J58M8aA3QTfW+2UlQ4psvTX9IO1RfNVhK3pcpdjej7L+t2w==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.27", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", + "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.0.tgz", + "integrity": "sha512-WOhNW9K8bR3kf4zLxbfg6Pxu2ybOUbB2AjMDHSQx86LIF4rH4Ft7vmMwNt0loO0eonglSNy4cpD3MKXXKQu0/A==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.0.tgz", + "integrity": "sha512-u6JHLll5QKRvjciE78bQXDmqRqNs5M/3GVqZeMwvmjaNODJih/WIrJlFVEihvV0MiYFmd+ZyPr9wxOVbPAG2Iw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.0.tgz", + "integrity": "sha512-qEF7CsKKzSRc20Ciu2Zw1wRrBz4g56F7r/vRwY430UPp/nt1x21Q/fpJ9N5l47WWvJlkNCPJz3QRVw008fi7yA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.0.tgz", + "integrity": "sha512-WADYozJ4QCnXCH4wPB+3FuGmDPoFseVCUrANmA5LWwGmC6FL14BWC7pcq+FstOZv3baGX65tZ378uT6WG8ynTw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.0.tgz", + "integrity": "sha512-6b8wGHJlDrGeSE3aH5mGNHBjA0TTkxdoNHik5EkvPHCt351XnigA4pS7Wsj/Eo9Y8RBU6f35cjN9SYmCFBtzxw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.0.tgz", + "integrity": "sha512-h25Ga0t4jaylMB8M/JKAyrvvfxGRjnPQIR8lnCayyzEjEOx2EJIlIiMbhpWxDRKGKF8jbNH01NnN663dH638mA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.0.tgz", + "integrity": "sha512-RzeBwv0B3qtVBWtcuABtSuCzToo2IEAIQrcyB/b2zMvBWVbjo8bZDjACUpnaafaxhTw2W+imQbP2BD1usasK4g==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.0.tgz", + "integrity": "sha512-Sf7zusNI2CIU1HLzuu9Tc5YGAHEZs5Lu7N1ssJG4Tkw6e0MEsN7NdjUDDfGNHy2IU+ENyWT+L2obgWiguWibWQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.0.tgz", + "integrity": "sha512-DX2x7CMcrJzsE91q7/O02IJQ5/aLkVtYFryqCjduJhUfGKG6yJV8hxaw8pZa93lLEpPTP/ohdN4wFz7yp/ry9A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.0.tgz", + "integrity": "sha512-09EL+yFVbJZlhcQfShpswwRZ0Rg+z/CsSELFCnPt3iK+iqwGsI4zht3secj5vLEs957QvFFXnzAT0FFPIxSrkQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.0.tgz", + "integrity": "sha512-i9IcCMPr3EXm8EQg5jnja0Zyc1iFxJjZWlb4wr7U2Wx/GrddOuEafxRdMPRYVaXjgbhvqalp6np07hN1w9kAKw==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.0.tgz", + "integrity": "sha512-DGzdJK9kyJ+B78MCkWeGnpXJ91tK/iKA6HwHxF4TAlPIY7GXEvMe8hBFRgdrR9Ly4qebR/7gfUs9y2IoaVEyog==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.0.tgz", + "integrity": "sha512-RwpnLsqC8qbS8z1H1AxBA1H6qknR4YpPR9w2XX0vo2Sz10miu57PkNcnHVaZkbqyw/kUWfKMI73jhmfi9BRMUQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.0.tgz", + "integrity": "sha512-Z8pPf54Ly3aqtdWC3G4rFigZgNvd+qJlOE52fmko3KST9SoGfAdSRCwyoyG05q1HrrAblLbk1/PSIV+80/pxLg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.0.tgz", + "integrity": "sha512-3a3qQustp3COCGvnP4SvrMHnPQ9d1vzCakQVRTliaz8cIp/wULGjiGpbcqrkv0WrHTEp8bQD/B3HBjzujVWLOA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.0.tgz", + "integrity": "sha512-pjZDsVH/1VsghMJ2/kAaxt6dL0psT6ZexQVrijczOf+PeP2BUqTHYejk3l6TlPRydggINOeNRhvpLa0AYpCWSQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.0.tgz", + "integrity": "sha512-3ObQs0BhvPgiUVZrN7gqCSvmFuMWvWvsjG5ayJ3Lraqv+2KhOsp+pUbigqbeWqueGIsnn+09HBw27rJ+gYK4VQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.0.tgz", + "integrity": "sha512-EtylprDtQPdS5rXvAayrNDYoJhIz1/vzN2fEubo3yLE7tfAw+948dO0g4M0vkTVFhKojnF+n6C8bDNe+gDRdTg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.0.tgz", + "integrity": "sha512-k09oiRCi/bHU9UVFqD17r3eJR9bn03TyKraCrlz5ULFJGdJGi7VOmm9jl44vOJvRJ6P7WuBi/s2A97LxxHGIdw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.0.tgz", + "integrity": "sha512-1o/0/pIhozoSaDJoDcec+IVLbnRtQmHwPV730+AOD29lHEEo4F5BEUB24H0OBdhbBBDwIOSuf7vgg0Ywxdfiiw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.0.tgz", + "integrity": "sha512-pESDkos/PDzYwtyzB5p/UoNU/8fJo68vcXM9ZW2V0kjYayj1KaaUfi1NmTUTUpMn4UhU4gTuK8gIaFO4UGuMbA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.0.tgz", + "integrity": "sha512-hj1wFStD7B1YBeYmvY+lWXZ7ey73YGPcViMShYikqKT1GtstIKQAtfUI6yrzPjAy/O7pO0VLXGmUVWXQMaYgTQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.0.tgz", + "integrity": "sha512-SyaIPFoxmUPlNDq5EHkTbiKzmSEmq/gOYFI/3HHJ8iS/v1mbugVa7dXUzcJGQfoytp9DJFLhHH4U3/eTy2Bq4w==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.0.tgz", + "integrity": "sha512-RdcryEfzZr+lAr5kRm2ucN9aVlCCa2QNq4hXelZxb8GG0NJSazq44Z3PCCc8wISRuCVnGs0lQJVX5Vp6fKA+IA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.0.tgz", + "integrity": "sha512-PrsWNQ8BuE00O3Xsx3ALh2Df8fAj9+cvvX9AIA6o4KpATR98c9mud4XtDWVvsEuyia5U4tVSTKygawyJkjm60w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/prop-types": { + "version": "15.7.15", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", + "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "18.3.28", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.28.tgz", + "integrity": "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "18.3.7", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", + "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^18.0.0" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", + "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.0", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.27", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.17.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.10", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.10.tgz", + "integrity": "sha512-sUoJ3IMxx4AyRqO4MLeHlnGDkyXRoUG0/AI9fjK+vS72ekpV0yWVY7O0BVjmBcRtkNcsAO2QDZ4tdKKGoI6YaQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/browserslist": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001781", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001781.tgz", + "integrity": "sha512-RdwNCyMsNBftLjW6w01z8bKEvT6e/5tpPVEgtn22TiLGlstHOVecsX2KHFkD5e/vRnIE4EGzpuIODb3mtswtkw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.321", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.321.tgz", + "integrity": "sha512-L2C7Q279W2D/J4PLZLk7sebOILDSWos7bMsMNN06rK482umHUrh/3lM8G7IlHFOYip2oAg5nha1rCMxr/rs6ZQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/fsevents": { + "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, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-releases": { + "version": "2.0.36", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.36.tgz", + "integrity": "sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/postcss": { + "version": "8.5.8", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz", + "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/react-refresh": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", + "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-router": { + "version": "6.30.3", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.3.tgz", + "integrity": "sha512-XRnlbKMTmktBkjCLE8/XcZFlnHvr2Ltdr1eJX4idL55/9BbORzyZEaIkBFDhFGCEWBBItsVrDxwx3gnisMitdw==", + "license": "MIT", + "dependencies": { + "@remix-run/router": "1.23.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8" + } + }, + "node_modules/react-router-dom": { + "version": "6.30.3", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.30.3.tgz", + "integrity": "sha512-pxPcv1AczD4vso7G4Z3TKcvlxK7g7TNt3/FNGMhfqyntocvYKj+GCatfigGDjbLozC4baguJ0ReCigoDJXb0ag==", + "license": "MIT", + "dependencies": { + "@remix-run/router": "1.23.2", + "react-router": "6.30.3" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/rollup": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.0.tgz", + "integrity": "sha512-yqjxruMGBQJ2gG4HtjZtAfXArHomazDHoFwFFmZZl0r7Pdo7qCIXKqKHZc8yeoMgzJJ+pO6pEEHa+V7uzWlrAQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.60.0", + "@rollup/rollup-android-arm64": "4.60.0", + "@rollup/rollup-darwin-arm64": "4.60.0", + "@rollup/rollup-darwin-x64": "4.60.0", + "@rollup/rollup-freebsd-arm64": "4.60.0", + "@rollup/rollup-freebsd-x64": "4.60.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.60.0", + "@rollup/rollup-linux-arm-musleabihf": "4.60.0", + "@rollup/rollup-linux-arm64-gnu": "4.60.0", + "@rollup/rollup-linux-arm64-musl": "4.60.0", + "@rollup/rollup-linux-loong64-gnu": "4.60.0", + "@rollup/rollup-linux-loong64-musl": "4.60.0", + "@rollup/rollup-linux-ppc64-gnu": "4.60.0", + "@rollup/rollup-linux-ppc64-musl": "4.60.0", + "@rollup/rollup-linux-riscv64-gnu": "4.60.0", + "@rollup/rollup-linux-riscv64-musl": "4.60.0", + "@rollup/rollup-linux-s390x-gnu": "4.60.0", + "@rollup/rollup-linux-x64-gnu": "4.60.0", + "@rollup/rollup-linux-x64-musl": "4.60.0", + "@rollup/rollup-openbsd-x64": "4.60.0", + "@rollup/rollup-openharmony-arm64": "4.60.0", + "@rollup/rollup-win32-arm64-msvc": "4.60.0", + "@rollup/rollup-win32-ia32-msvc": "4.60.0", + "@rollup/rollup-win32-x64-gnu": "4.60.0", + "@rollup/rollup-win32-x64-msvc": "4.60.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/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, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/vite": { + "version": "5.4.21", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", + "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + } + } +} diff --git a/admin-ui/package.json b/admin-ui/package.json new file mode 100644 index 0000000..cd6f636 --- /dev/null +++ b/admin-ui/package.json @@ -0,0 +1,23 @@ +{ + "name": "osham-admin-ui", + "private": true, + "version": "0.1.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc -b && vite build", + "preview": "vite preview" + }, + "dependencies": { + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-router-dom": "^6.30.1" + }, + "devDependencies": { + "@types/react": "^18.3.12", + "@types/react-dom": "^18.3.1", + "@vitejs/plugin-react": "^4.3.4", + "typescript": "^5.6.3", + "vite": "^5.4.10" + } +} diff --git a/admin-ui/src/main.tsx b/admin-ui/src/main.tsx new file mode 100644 index 0000000..4d1b418 --- /dev/null +++ b/admin-ui/src/main.tsx @@ -0,0 +1,11 @@ +import React from 'react'; +import ReactDOM from 'react-dom/client'; +import { RouterProvider } from 'react-router-dom'; +import { router } from './router'; +import './styles.css'; + +ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render( + + + , +); diff --git a/admin-ui/src/router.tsx b/admin-ui/src/router.tsx new file mode 100644 index 0000000..0285143 --- /dev/null +++ b/admin-ui/src/router.tsx @@ -0,0 +1,38 @@ +import React from 'react'; +import { createBrowserRouter, Navigate, Outlet } from 'react-router-dom'; +import { DashboardPage } from './screens/DashboardPage'; +import { ConfigPage } from './screens/ConfigPage'; +import { MetricsPage } from './screens/MetricsPage'; +import { HealthPage } from './screens/HealthPage'; +import { PurgePage } from './screens/PurgePage'; +import { AuditPage } from './screens/AuditPage'; +import { NotFoundPage } from './screens/NotFoundPage'; +import { Sidebar } from './ui/Sidebar'; + +function Layout() { + return ( +
+ +
+ +
+
+ ); +} + +export const router = createBrowserRouter([ + { + path: '/', + element: , + children: [ + { index: true, element: }, + { path: '/admin/dashboard', element: }, + { path: '/admin/config', element: }, + { path: '/admin/metrics', element: }, + { path: '/admin/health', element: }, + { path: '/admin/purge', element: }, + { path: '/admin/audit', element: }, + { path: '*', element: }, + ], + }, +]); diff --git a/admin-ui/src/screens/AuditPage.tsx b/admin-ui/src/screens/AuditPage.tsx new file mode 100644 index 0000000..cdab84b --- /dev/null +++ b/admin-ui/src/screens/AuditPage.tsx @@ -0,0 +1,10 @@ +import React from 'react'; +import { Page } from '../ui/Page'; + +export function AuditPage() { + return ( + +
Next step: table view over /__osham/admin/audit.
+
+ ); +} diff --git a/admin-ui/src/screens/ConfigPage.tsx b/admin-ui/src/screens/ConfigPage.tsx new file mode 100644 index 0000000..28986bc --- /dev/null +++ b/admin-ui/src/screens/ConfigPage.tsx @@ -0,0 +1,17 @@ +import React from 'react'; +import { Page } from '../ui/Page'; + +export function ConfigPage() { + return ( + +
+ Planned next: +
    +
  • load current config from /__osham/admin/config
  • +
  • draft editing for global config + namespaces
  • +
  • validate, save, and reload actions
  • +
+
+
+ ); +} diff --git a/admin-ui/src/screens/DashboardPage.tsx b/admin-ui/src/screens/DashboardPage.tsx new file mode 100644 index 0000000..3102498 --- /dev/null +++ b/admin-ui/src/screens/DashboardPage.tsx @@ -0,0 +1,24 @@ +import React from 'react'; +import { Page } from '../ui/Page'; +import { Card } from '../ui/Card'; + +export function DashboardPage() { + return ( + +
+ +

Wire to /__osham/admin/health.

+
+ +

Wire to /__osham/admin/startup-summary.

+
+ +

Wire to /__osham/admin/metrics/summary.

+
+ +

Show current config revision and apply state.

+
+
+
+ ); +} diff --git a/admin-ui/src/screens/HealthPage.tsx b/admin-ui/src/screens/HealthPage.tsx new file mode 100644 index 0000000..ce669fa --- /dev/null +++ b/admin-ui/src/screens/HealthPage.tsx @@ -0,0 +1,10 @@ +import React from 'react'; +import { Page } from '../ui/Page'; + +export function HealthPage() { + return ( + +
Surface cache backend, uptime, revision, and feature flags here.
+
+ ); +} diff --git a/admin-ui/src/screens/MetricsPage.tsx b/admin-ui/src/screens/MetricsPage.tsx new file mode 100644 index 0000000..1e99fb6 --- /dev/null +++ b/admin-ui/src/screens/MetricsPage.tsx @@ -0,0 +1,10 @@ +import React from 'react'; +import { Page } from '../ui/Page'; + +export function MetricsPage() { + return ( + +
Hook into /__osham/admin/metrics/summary and /__osham/admin/metrics/namespaces.
+
+ ); +} diff --git a/admin-ui/src/screens/NotFoundPage.tsx b/admin-ui/src/screens/NotFoundPage.tsx new file mode 100644 index 0000000..aa27271 --- /dev/null +++ b/admin-ui/src/screens/NotFoundPage.tsx @@ -0,0 +1,6 @@ +import React from 'react'; +import { Page } from '../ui/Page'; + +export function NotFoundPage() { + return ; +} diff --git a/admin-ui/src/screens/PurgePage.tsx b/admin-ui/src/screens/PurgePage.tsx new file mode 100644 index 0000000..49c034f --- /dev/null +++ b/admin-ui/src/screens/PurgePage.tsx @@ -0,0 +1,10 @@ +import React from 'react'; +import { Page } from '../ui/Page'; + +export function PurgePage() { + return ( + +
Next step: connect form to POST /__osham/admin/purge with warning display and dry-run support.
+
+ ); +} diff --git a/admin-ui/src/styles.css b/admin-ui/src/styles.css new file mode 100644 index 0000000..362d0ec --- /dev/null +++ b/admin-ui/src/styles.css @@ -0,0 +1,112 @@ +:root { + font-family: Inter, system-ui, sans-serif; + color: #e7ecf3; + background: #0b1220; +} + +* { + box-sizing: border-box; +} + +body { + margin: 0; + min-width: 320px; + min-height: 100vh; + background: linear-gradient(180deg, #0b1220 0%, #111827 100%); +} + +#root { + min-height: 100vh; +} + +.app-shell { + display: grid; + grid-template-columns: 240px 1fr; + min-height: 100vh; +} + +.sidebar { + border-right: 1px solid #22304a; + padding: 24px 18px; + background: rgba(8, 15, 28, 0.92); +} + +.brand { + font-size: 1.2rem; + font-weight: 700; + margin-bottom: 20px; +} + +.nav-list { + display: flex; + flex-direction: column; + gap: 10px; +} + +.nav-link { + color: #c2d1e6; + text-decoration: none; + padding: 10px 12px; + border-radius: 10px; +} + +.nav-link.active, +.nav-link:hover { + background: #1d4ed8; + color: white; +} + +.content-shell { + padding: 28px; +} + +.page { + max-width: 1080px; +} + +.page h1 { + margin-top: 0; + margin-bottom: 8px; +} + +.page p { + color: #a8b4c7; +} + +.card-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); + gap: 16px; + margin-top: 24px; +} + +.card { + background: rgba(17, 24, 39, 0.88); + border: 1px solid #22304a; + border-radius: 16px; + padding: 18px; +} + +.card h3 { + margin-top: 0; +} + +.code-block { + margin-top: 16px; + padding: 16px; + background: #0f172a; + border: 1px solid #22304a; + border-radius: 12px; + overflow: auto; +} + +@media (max-width: 820px) { + .app-shell { + grid-template-columns: 1fr; + } + + .sidebar { + border-right: 0; + border-bottom: 1px solid #22304a; + } +} diff --git a/admin-ui/src/ui/Card.tsx b/admin-ui/src/ui/Card.tsx new file mode 100644 index 0000000..18b5d7f --- /dev/null +++ b/admin-ui/src/ui/Card.tsx @@ -0,0 +1,10 @@ +import React, { PropsWithChildren } from 'react'; + +export function Card(props: PropsWithChildren<{ title: string }>) { + return ( +
+

{props.title}

+ {props.children} +
+ ); +} diff --git a/admin-ui/src/ui/Page.tsx b/admin-ui/src/ui/Page.tsx new file mode 100644 index 0000000..2e3afae --- /dev/null +++ b/admin-ui/src/ui/Page.tsx @@ -0,0 +1,11 @@ +import React, { PropsWithChildren } from 'react'; + +export function Page(props: PropsWithChildren<{ title: string; subtitle: string }>) { + return ( +
+

{props.title}

+

{props.subtitle}

+ {props.children} +
+ ); +} diff --git a/admin-ui/src/ui/Sidebar.tsx b/admin-ui/src/ui/Sidebar.tsx new file mode 100644 index 0000000..3ee9843 --- /dev/null +++ b/admin-ui/src/ui/Sidebar.tsx @@ -0,0 +1,26 @@ +import React from 'react'; +import { NavLink } from 'react-router-dom'; + +const links = [ + ['/admin/dashboard', 'Dashboard'], + ['/admin/config', 'Config'], + ['/admin/metrics', 'Metrics'], + ['/admin/health', 'Health'], + ['/admin/purge', 'Purge'], + ['/admin/audit', 'Audit'], +] as const; + +export function Sidebar() { + return ( + + ); +} diff --git a/admin-ui/tsconfig.json b/admin-ui/tsconfig.json new file mode 100644 index 0000000..fe6cc77 --- /dev/null +++ b/admin-ui/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + "moduleResolution": "Bundler", + "allowImportingTsExtensions": false, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "strict": true + }, + "include": ["src"] +} diff --git a/admin-ui/tsconfig.tsbuildinfo b/admin-ui/tsconfig.tsbuildinfo new file mode 100644 index 0000000..2ca0631 --- /dev/null +++ b/admin-ui/tsconfig.tsbuildinfo @@ -0,0 +1 @@ +{"root":["./src/main.tsx","./src/router.tsx","./src/screens/AuditPage.tsx","./src/screens/ConfigPage.tsx","./src/screens/DashboardPage.tsx","./src/screens/HealthPage.tsx","./src/screens/MetricsPage.tsx","./src/screens/NotFoundPage.tsx","./src/screens/PurgePage.tsx","./src/ui/Card.tsx","./src/ui/Page.tsx","./src/ui/Sidebar.tsx"],"version":"5.9.3"} \ No newline at end of file diff --git a/admin-ui/vite.config.ts b/admin-ui/vite.config.ts new file mode 100644 index 0000000..7a7b363 --- /dev/null +++ b/admin-ui/vite.config.ts @@ -0,0 +1,9 @@ +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; + +export default defineConfig({ + plugins: [react()], + server: { + port: 4173, + }, +}); From 5d6f46fbde3d0b5ce4e68d4c07403247b66df370 Mon Sep 17 00:00:00 2001 From: Ajay Singh Date: Mon, 23 Mar 2026 18:43:20 +0000 Subject: [PATCH 13/50] feat(admin-ui): wire dashboard, health, audit, and purge pages --- admin-ui/dist/assets/index-BNnQfmSP.css | 1 - admin-ui/dist/assets/index-Brm5M7Pt.css | 1 + admin-ui/dist/assets/index-Bxwtaj6p.js | 68 ------------------------- admin-ui/dist/assets/index-CtLrP-Jx.js | 68 +++++++++++++++++++++++++ admin-ui/dist/index.html | 4 +- admin-ui/src/api.ts | 30 +++++++++++ admin-ui/src/router.tsx | 2 + admin-ui/src/screens/AuditPage.tsx | 33 +++++++++++- admin-ui/src/screens/DashboardPage.tsx | 44 +++++++++++++--- admin-ui/src/screens/HealthPage.tsx | 16 +++++- admin-ui/src/screens/PurgePage.tsx | 35 ++++++++++++- admin-ui/src/styles.css | 40 +++++++++++++++ admin-ui/src/types.ts | 31 +++++++++++ admin-ui/src/ui/AdminSecretBar.tsx | 24 +++++++++ admin-ui/tsconfig.tsbuildinfo | 2 +- 15 files changed, 318 insertions(+), 81 deletions(-) delete mode 100644 admin-ui/dist/assets/index-BNnQfmSP.css create mode 100644 admin-ui/dist/assets/index-Brm5M7Pt.css delete mode 100644 admin-ui/dist/assets/index-Bxwtaj6p.js create mode 100644 admin-ui/dist/assets/index-CtLrP-Jx.js create mode 100644 admin-ui/src/api.ts create mode 100644 admin-ui/src/types.ts create mode 100644 admin-ui/src/ui/AdminSecretBar.tsx diff --git a/admin-ui/dist/assets/index-BNnQfmSP.css b/admin-ui/dist/assets/index-BNnQfmSP.css deleted file mode 100644 index 846ecf4..0000000 --- a/admin-ui/dist/assets/index-BNnQfmSP.css +++ /dev/null @@ -1 +0,0 @@ -:root{font-family:Inter,system-ui,sans-serif;color:#e7ecf3;background:#0b1220}*{box-sizing:border-box}body{margin:0;min-width:320px;min-height:100vh;background:linear-gradient(180deg,#0b1220,#111827)}#root{min-height:100vh}.app-shell{display:grid;grid-template-columns:240px 1fr;min-height:100vh}.sidebar{border-right:1px solid #22304a;padding:24px 18px;background:#080f1ceb}.brand{font-size:1.2rem;font-weight:700;margin-bottom:20px}.nav-list{display:flex;flex-direction:column;gap:10px}.nav-link{color:#c2d1e6;text-decoration:none;padding:10px 12px;border-radius:10px}.nav-link.active,.nav-link:hover{background:#1d4ed8;color:#fff}.content-shell{padding:28px}.page{max-width:1080px}.page h1{margin-top:0;margin-bottom:8px}.page p{color:#a8b4c7}.card-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:16px;margin-top:24px}.card{background:#111827e0;border:1px solid #22304a;border-radius:16px;padding:18px}.card h3{margin-top:0}.code-block{margin-top:16px;padding:16px;background:#0f172a;border:1px solid #22304a;border-radius:12px;overflow:auto}@media (max-width: 820px){.app-shell{grid-template-columns:1fr}.sidebar{border-right:0;border-bottom:1px solid #22304a}} diff --git a/admin-ui/dist/assets/index-Brm5M7Pt.css b/admin-ui/dist/assets/index-Brm5M7Pt.css new file mode 100644 index 0000000..2edd367 --- /dev/null +++ b/admin-ui/dist/assets/index-Brm5M7Pt.css @@ -0,0 +1 @@ +:root{font-family:Inter,system-ui,sans-serif;color:#e7ecf3;background:#0b1220}*{box-sizing:border-box}body{margin:0;min-width:320px;min-height:100vh;background:linear-gradient(180deg,#0b1220,#111827)}#root{min-height:100vh}.app-shell{display:grid;grid-template-columns:240px 1fr;min-height:100vh}.sidebar{border-right:1px solid #22304a;padding:24px 18px;background:#080f1ceb}.brand{font-size:1.2rem;font-weight:700;margin-bottom:20px}.nav-list{display:flex;flex-direction:column;gap:10px}.nav-link{color:#c2d1e6;text-decoration:none;padding:10px 12px;border-radius:10px}.nav-link.active,.nav-link:hover{background:#1d4ed8;color:#fff}.content-shell{padding:28px}.toolbar{display:flex;gap:12px;align-items:center;margin-bottom:20px;flex-wrap:wrap}.input,.button,.textarea{border:1px solid #31415e;background:#0f172a;color:#e7ecf3;border-radius:10px;padding:10px 12px}.input{min-width:280px}.button{cursor:pointer}.table{width:100%;border-collapse:collapse;margin-top:16px}.table th,.table td{border-bottom:1px solid #22304a;padding:10px;text-align:left;vertical-align:top}.page{max-width:1080px}.page h1{margin-top:0;margin-bottom:8px}.page p{color:#a8b4c7}.card-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:16px;margin-top:24px}.card{background:#111827e0;border:1px solid #22304a;border-radius:16px;padding:18px}.card h3{margin-top:0}.code-block{margin-top:16px;padding:16px;background:#0f172a;border:1px solid #22304a;border-radius:12px;overflow:auto}@media (max-width: 820px){.app-shell{grid-template-columns:1fr}.sidebar{border-right:0;border-bottom:1px solid #22304a}} diff --git a/admin-ui/dist/assets/index-Bxwtaj6p.js b/admin-ui/dist/assets/index-Bxwtaj6p.js deleted file mode 100644 index 96e0514..0000000 --- a/admin-ui/dist/assets/index-Bxwtaj6p.js +++ /dev/null @@ -1,68 +0,0 @@ -function yc(e,t){for(var n=0;nr[l]})}}}return Object.freeze(Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}))}(function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const l of document.querySelectorAll('link[rel="modulepreload"]'))r(l);new MutationObserver(l=>{for(const i of l)if(i.type==="childList")for(const o of i.addedNodes)o.tagName==="LINK"&&o.rel==="modulepreload"&&r(o)}).observe(document,{childList:!0,subtree:!0});function n(l){const i={};return l.integrity&&(i.integrity=l.integrity),l.referrerPolicy&&(i.referrerPolicy=l.referrerPolicy),l.crossOrigin==="use-credentials"?i.credentials="include":l.crossOrigin==="anonymous"?i.credentials="omit":i.credentials="same-origin",i}function r(l){if(l.ep)return;l.ep=!0;const i=n(l);fetch(l.href,i)}})();function gc(e){return e&&e.__esModule&&Object.prototype.hasOwnProperty.call(e,"default")?e.default:e}var wc={exports:{}},zi={},Sc={exports:{}},Y={};/** - * @license React - * react.production.min.js - * - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */var hl=Symbol.for("react.element"),pp=Symbol.for("react.portal"),hp=Symbol.for("react.fragment"),mp=Symbol.for("react.strict_mode"),vp=Symbol.for("react.profiler"),yp=Symbol.for("react.provider"),gp=Symbol.for("react.context"),wp=Symbol.for("react.forward_ref"),Sp=Symbol.for("react.suspense"),Ep=Symbol.for("react.memo"),xp=Symbol.for("react.lazy"),Hu=Symbol.iterator;function kp(e){return e===null||typeof e!="object"?null:(e=Hu&&e[Hu]||e["@@iterator"],typeof e=="function"?e:null)}var Ec={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},xc=Object.assign,kc={};function mr(e,t,n){this.props=e,this.context=t,this.refs=kc,this.updater=n||Ec}mr.prototype.isReactComponent={};mr.prototype.setState=function(e,t){if(typeof e!="object"&&typeof e!="function"&&e!=null)throw Error("setState(...): takes an object of state variables to update or a function which returns an object of state variables.");this.updater.enqueueSetState(this,e,t,"setState")};mr.prototype.forceUpdate=function(e){this.updater.enqueueForceUpdate(this,e,"forceUpdate")};function Cc(){}Cc.prototype=mr.prototype;function Na(e,t,n){this.props=e,this.context=t,this.refs=kc,this.updater=n||Ec}var Ta=Na.prototype=new Cc;Ta.constructor=Na;xc(Ta,mr.prototype);Ta.isPureReactComponent=!0;var Vu=Array.isArray,Pc=Object.prototype.hasOwnProperty,Da={current:null},_c={key:!0,ref:!0,__self:!0,__source:!0};function Rc(e,t,n){var r,l={},i=null,o=null;if(t!=null)for(r in t.ref!==void 0&&(o=t.ref),t.key!==void 0&&(i=""+t.key),t)Pc.call(t,r)&&!_c.hasOwnProperty(r)&&(l[r]=t[r]);var a=arguments.length-2;if(a===1)l.children=n;else if(1>>1,ne=N[te];if(0>>1;tel(je,$))Oel(tt,je)?(N[te]=tt,N[Oe]=$,te=Oe):(N[te]=je,N[Ye]=$,te=Ye);else if(Oel(tt,$))N[te]=tt,N[Oe]=$,te=Oe;else break e}}return V}function l(N,V){var $=N.sortIndex-V.sortIndex;return $!==0?$:N.id-V.id}if(typeof performance=="object"&&typeof performance.now=="function"){var i=performance;e.unstable_now=function(){return i.now()}}else{var o=Date,a=o.now();e.unstable_now=function(){return o.now()-a}}var u=[],s=[],d=1,f=null,p=3,w=!1,x=!1,E=!1,T=typeof setTimeout=="function"?setTimeout:null,m=typeof clearTimeout=="function"?clearTimeout:null,c=typeof setImmediate<"u"?setImmediate:null;typeof navigator<"u"&&navigator.scheduling!==void 0&&navigator.scheduling.isInputPending!==void 0&&navigator.scheduling.isInputPending.bind(navigator.scheduling);function v(N){for(var V=n(s);V!==null;){if(V.callback===null)r(s);else if(V.startTime<=N)r(s),V.sortIndex=V.expirationTime,t(u,V);else break;V=n(s)}}function k(N){if(E=!1,v(N),!x)if(n(u)!==null)x=!0,Bt(L);else{var V=n(s);V!==null&&Ht(k,V.startTime-N)}}function L(N,V){x=!1,E&&(E=!1,m(C),C=-1),w=!0;var $=p;try{for(v(V),f=n(u);f!==null&&(!(f.expirationTime>V)||N&&!q());){var te=f.callback;if(typeof te=="function"){f.callback=null,p=f.priorityLevel;var ne=te(f.expirationTime<=V);V=e.unstable_now(),typeof ne=="function"?f.callback=ne:f===n(u)&&r(u),v(V)}else r(u);f=n(u)}if(f!==null)var st=!0;else{var Ye=n(s);Ye!==null&&Ht(k,Ye.startTime-V),st=!1}return st}finally{f=null,p=$,w=!1}}var z=!1,y=null,C=-1,H=5,D=-1;function q(){return!(e.unstable_now()-DN||125te?(N.sortIndex=$,t(s,N),n(u)===null&&N===n(s)&&(E?(m(C),C=-1):E=!0,Ht(k,$-te))):(N.sortIndex=ne,t(u,N),x||w||(x=!0,Bt(L))),N},e.unstable_shouldYield=q,e.unstable_wrapCallback=function(N){var V=p;return function(){var $=p;p=V;try{return N.apply(this,arguments)}finally{p=$}}}})(zc);Mc.exports=zc;var Op=Mc.exports;/** - * @license React - * react-dom.production.min.js - * - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */var Fp=_,qe=Op;function R(e){for(var t="https://reactjs.org/docs/error-decoder.html?invariant="+e,n=1;n"u"||typeof window.document>"u"||typeof window.document.createElement>"u"),zo=Object.prototype.hasOwnProperty,Ip=/^[:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD][:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD\-.0-9\u00B7\u0300-\u036F\u203F-\u2040]*$/,$u={},Qu={};function Up(e){return zo.call(Qu,e)?!0:zo.call($u,e)?!1:Ip.test(e)?Qu[e]=!0:($u[e]=!0,!1)}function Ap(e,t,n,r){if(n!==null&&n.type===0)return!1;switch(typeof t){case"function":case"symbol":return!0;case"boolean":return r?!1:n!==null?!n.acceptsBooleans:(e=e.toLowerCase().slice(0,5),e!=="data-"&&e!=="aria-");default:return!1}}function Bp(e,t,n,r){if(t===null||typeof t>"u"||Ap(e,t,n,r))return!0;if(r)return!1;if(n!==null)switch(n.type){case 3:return!t;case 4:return t===!1;case 5:return isNaN(t);case 6:return isNaN(t)||1>t}return!1}function Be(e,t,n,r,l,i,o){this.acceptsBooleans=t===2||t===3||t===4,this.attributeName=r,this.attributeNamespace=l,this.mustUseProperty=n,this.propertyName=e,this.type=t,this.sanitizeURL=i,this.removeEmptyString=o}var Ne={};"children dangerouslySetInnerHTML defaultValue defaultChecked innerHTML suppressContentEditableWarning suppressHydrationWarning style".split(" ").forEach(function(e){Ne[e]=new Be(e,0,!1,e,null,!1,!1)});[["acceptCharset","accept-charset"],["className","class"],["htmlFor","for"],["httpEquiv","http-equiv"]].forEach(function(e){var t=e[0];Ne[t]=new Be(t,1,!1,e[1],null,!1,!1)});["contentEditable","draggable","spellCheck","value"].forEach(function(e){Ne[e]=new Be(e,2,!1,e.toLowerCase(),null,!1,!1)});["autoReverse","externalResourcesRequired","focusable","preserveAlpha"].forEach(function(e){Ne[e]=new Be(e,2,!1,e,null,!1,!1)});"allowFullScreen async autoFocus autoPlay controls default defer disabled disablePictureInPicture disableRemotePlayback formNoValidate hidden loop noModule noValidate open playsInline readOnly required reversed scoped seamless itemScope".split(" ").forEach(function(e){Ne[e]=new Be(e,3,!1,e.toLowerCase(),null,!1,!1)});["checked","multiple","muted","selected"].forEach(function(e){Ne[e]=new Be(e,3,!0,e,null,!1,!1)});["capture","download"].forEach(function(e){Ne[e]=new Be(e,4,!1,e,null,!1,!1)});["cols","rows","size","span"].forEach(function(e){Ne[e]=new Be(e,6,!1,e,null,!1,!1)});["rowSpan","start"].forEach(function(e){Ne[e]=new Be(e,5,!1,e.toLowerCase(),null,!1,!1)});var za=/[\-:]([a-z])/g;function ja(e){return e[1].toUpperCase()}"accent-height alignment-baseline arabic-form baseline-shift cap-height clip-path clip-rule color-interpolation color-interpolation-filters color-profile color-rendering dominant-baseline enable-background fill-opacity fill-rule flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-name glyph-orientation-horizontal glyph-orientation-vertical horiz-adv-x horiz-origin-x image-rendering letter-spacing lighting-color marker-end marker-mid marker-start overline-position overline-thickness paint-order panose-1 pointer-events rendering-intent shape-rendering stop-color stop-opacity strikethrough-position strikethrough-thickness stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width text-anchor text-decoration text-rendering underline-position underline-thickness unicode-bidi unicode-range units-per-em v-alphabetic v-hanging v-ideographic v-mathematical vector-effect vert-adv-y vert-origin-x vert-origin-y word-spacing writing-mode xmlns:xlink x-height".split(" ").forEach(function(e){var t=e.replace(za,ja);Ne[t]=new Be(t,1,!1,e,null,!1,!1)});"xlink:actuate xlink:arcrole xlink:role xlink:show xlink:title xlink:type".split(" ").forEach(function(e){var t=e.replace(za,ja);Ne[t]=new Be(t,1,!1,e,"http://www.w3.org/1999/xlink",!1,!1)});["xml:base","xml:lang","xml:space"].forEach(function(e){var t=e.replace(za,ja);Ne[t]=new Be(t,1,!1,e,"http://www.w3.org/XML/1998/namespace",!1,!1)});["tabIndex","crossOrigin"].forEach(function(e){Ne[e]=new Be(e,1,!1,e.toLowerCase(),null,!1,!1)});Ne.xlinkHref=new Be("xlinkHref",1,!1,"xlink:href","http://www.w3.org/1999/xlink",!0,!1);["src","href","action","formAction"].forEach(function(e){Ne[e]=new Be(e,1,!1,e.toLowerCase(),null,!0,!0)});function Oa(e,t,n,r){var l=Ne.hasOwnProperty(t)?Ne[t]:null;(l!==null?l.type!==0:r||!(2a||l[o]!==i[a]){var u=` -`+l[o].replace(" at new "," at ");return e.displayName&&u.includes("")&&(u=u.replace("",e.displayName)),u}while(1<=o&&0<=a);break}}}finally{lo=!1,Error.prepareStackTrace=n}return(e=e?e.displayName||e.name:"")?Or(e):""}function Hp(e){switch(e.tag){case 5:return Or(e.type);case 16:return Or("Lazy");case 13:return Or("Suspense");case 19:return Or("SuspenseList");case 0:case 2:case 15:return e=io(e.type,!1),e;case 11:return e=io(e.type.render,!1),e;case 1:return e=io(e.type,!0),e;default:return""}}function Io(e){if(e==null)return null;if(typeof e=="function")return e.displayName||e.name||null;if(typeof e=="string")return e;switch(e){case $n:return"Fragment";case Wn:return"Portal";case jo:return"Profiler";case Fa:return"StrictMode";case Oo:return"Suspense";case Fo:return"SuspenseList"}if(typeof e=="object")switch(e.$$typeof){case Fc:return(e.displayName||"Context")+".Consumer";case Oc:return(e._context.displayName||"Context")+".Provider";case Ia:var t=e.render;return e=e.displayName,e||(e=t.displayName||t.name||"",e=e!==""?"ForwardRef("+e+")":"ForwardRef"),e;case Ua:return t=e.displayName||null,t!==null?t:Io(e.type)||"Memo";case Kt:t=e._payload,e=e._init;try{return Io(e(t))}catch{}}return null}function Vp(e){var t=e.type;switch(e.tag){case 24:return"Cache";case 9:return(t.displayName||"Context")+".Consumer";case 10:return(t._context.displayName||"Context")+".Provider";case 18:return"DehydratedFragment";case 11:return e=t.render,e=e.displayName||e.name||"",t.displayName||(e!==""?"ForwardRef("+e+")":"ForwardRef");case 7:return"Fragment";case 5:return t;case 4:return"Portal";case 3:return"Root";case 6:return"Text";case 16:return Io(t);case 8:return t===Fa?"StrictMode":"Mode";case 22:return"Offscreen";case 12:return"Profiler";case 21:return"Scope";case 13:return"Suspense";case 19:return"SuspenseList";case 25:return"TracingMarker";case 1:case 0:case 17:case 2:case 14:case 15:if(typeof t=="function")return t.displayName||t.name||null;if(typeof t=="string")return t}return null}function un(e){switch(typeof e){case"boolean":case"number":case"string":case"undefined":return e;case"object":return e;default:return""}}function Uc(e){var t=e.type;return(e=e.nodeName)&&e.toLowerCase()==="input"&&(t==="checkbox"||t==="radio")}function Wp(e){var t=Uc(e)?"checked":"value",n=Object.getOwnPropertyDescriptor(e.constructor.prototype,t),r=""+e[t];if(!e.hasOwnProperty(t)&&typeof n<"u"&&typeof n.get=="function"&&typeof n.set=="function"){var l=n.get,i=n.set;return Object.defineProperty(e,t,{configurable:!0,get:function(){return l.call(this)},set:function(o){r=""+o,i.call(this,o)}}),Object.defineProperty(e,t,{enumerable:n.enumerable}),{getValue:function(){return r},setValue:function(o){r=""+o},stopTracking:function(){e._valueTracker=null,delete e[t]}}}}function Nl(e){e._valueTracker||(e._valueTracker=Wp(e))}function Ac(e){if(!e)return!1;var t=e._valueTracker;if(!t)return!0;var n=t.getValue(),r="";return e&&(r=Uc(e)?e.checked?"true":"false":e.value),e=r,e!==n?(t.setValue(e),!0):!1}function ii(e){if(e=e||(typeof document<"u"?document:void 0),typeof e>"u")return null;try{return e.activeElement||e.body}catch{return e.body}}function Uo(e,t){var n=t.checked;return de({},t,{defaultChecked:void 0,defaultValue:void 0,value:void 0,checked:n??e._wrapperState.initialChecked})}function Yu(e,t){var n=t.defaultValue==null?"":t.defaultValue,r=t.checked!=null?t.checked:t.defaultChecked;n=un(t.value!=null?t.value:n),e._wrapperState={initialChecked:r,initialValue:n,controlled:t.type==="checkbox"||t.type==="radio"?t.checked!=null:t.value!=null}}function Bc(e,t){t=t.checked,t!=null&&Oa(e,"checked",t,!1)}function Ao(e,t){Bc(e,t);var n=un(t.value),r=t.type;if(n!=null)r==="number"?(n===0&&e.value===""||e.value!=n)&&(e.value=""+n):e.value!==""+n&&(e.value=""+n);else if(r==="submit"||r==="reset"){e.removeAttribute("value");return}t.hasOwnProperty("value")?Bo(e,t.type,n):t.hasOwnProperty("defaultValue")&&Bo(e,t.type,un(t.defaultValue)),t.checked==null&&t.defaultChecked!=null&&(e.defaultChecked=!!t.defaultChecked)}function Xu(e,t,n){if(t.hasOwnProperty("value")||t.hasOwnProperty("defaultValue")){var r=t.type;if(!(r!=="submit"&&r!=="reset"||t.value!==void 0&&t.value!==null))return;t=""+e._wrapperState.initialValue,n||t===e.value||(e.value=t),e.defaultValue=t}n=e.name,n!==""&&(e.name=""),e.defaultChecked=!!e._wrapperState.initialChecked,n!==""&&(e.name=n)}function Bo(e,t,n){(t!=="number"||ii(e.ownerDocument)!==e)&&(n==null?e.defaultValue=""+e._wrapperState.initialValue:e.defaultValue!==""+n&&(e.defaultValue=""+n))}var Fr=Array.isArray;function tr(e,t,n,r){if(e=e.options,t){t={};for(var l=0;l"+t.valueOf().toString()+"",t=Tl.firstChild;e.firstChild;)e.removeChild(e.firstChild);for(;t.firstChild;)e.appendChild(t.firstChild)}});function Jr(e,t){if(t){var n=e.firstChild;if(n&&n===e.lastChild&&n.nodeType===3){n.nodeValue=t;return}}e.textContent=t}var Br={animationIterationCount:!0,aspectRatio:!0,borderImageOutset:!0,borderImageSlice:!0,borderImageWidth:!0,boxFlex:!0,boxFlexGroup:!0,boxOrdinalGroup:!0,columnCount:!0,columns:!0,flex:!0,flexGrow:!0,flexPositive:!0,flexShrink:!0,flexNegative:!0,flexOrder:!0,gridArea:!0,gridRow:!0,gridRowEnd:!0,gridRowSpan:!0,gridRowStart:!0,gridColumn:!0,gridColumnEnd:!0,gridColumnSpan:!0,gridColumnStart:!0,fontWeight:!0,lineClamp:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,tabSize:!0,widows:!0,zIndex:!0,zoom:!0,fillOpacity:!0,floodOpacity:!0,stopOpacity:!0,strokeDasharray:!0,strokeDashoffset:!0,strokeMiterlimit:!0,strokeOpacity:!0,strokeWidth:!0},$p=["Webkit","ms","Moz","O"];Object.keys(Br).forEach(function(e){$p.forEach(function(t){t=t+e.charAt(0).toUpperCase()+e.substring(1),Br[t]=Br[e]})});function $c(e,t,n){return t==null||typeof t=="boolean"||t===""?"":n||typeof t!="number"||t===0||Br.hasOwnProperty(e)&&Br[e]?(""+t).trim():t+"px"}function Qc(e,t){e=e.style;for(var n in t)if(t.hasOwnProperty(n)){var r=n.indexOf("--")===0,l=$c(n,t[n],r);n==="float"&&(n="cssFloat"),r?e.setProperty(n,l):e[n]=l}}var Qp=de({menuitem:!0},{area:!0,base:!0,br:!0,col:!0,embed:!0,hr:!0,img:!0,input:!0,keygen:!0,link:!0,meta:!0,param:!0,source:!0,track:!0,wbr:!0});function Wo(e,t){if(t){if(Qp[e]&&(t.children!=null||t.dangerouslySetInnerHTML!=null))throw Error(R(137,e));if(t.dangerouslySetInnerHTML!=null){if(t.children!=null)throw Error(R(60));if(typeof t.dangerouslySetInnerHTML!="object"||!("__html"in t.dangerouslySetInnerHTML))throw Error(R(61))}if(t.style!=null&&typeof t.style!="object")throw Error(R(62))}}function $o(e,t){if(e.indexOf("-")===-1)return typeof t.is=="string";switch(e){case"annotation-xml":case"color-profile":case"font-face":case"font-face-src":case"font-face-uri":case"font-face-format":case"font-face-name":case"missing-glyph":return!1;default:return!0}}var Qo=null;function Aa(e){return e=e.target||e.srcElement||window,e.correspondingUseElement&&(e=e.correspondingUseElement),e.nodeType===3?e.parentNode:e}var Ko=null,nr=null,rr=null;function Zu(e){if(e=yl(e)){if(typeof Ko!="function")throw Error(R(280));var t=e.stateNode;t&&(t=Ui(t),Ko(e.stateNode,e.type,t))}}function Kc(e){nr?rr?rr.push(e):rr=[e]:nr=e}function Yc(){if(nr){var e=nr,t=rr;if(rr=nr=null,Zu(e),t)for(e=0;e>>=0,e===0?32:31-(nh(e)/rh|0)|0}var Dl=64,Ml=4194304;function Ir(e){switch(e&-e){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return e&4194240;case 4194304:case 8388608:case 16777216:case 33554432:case 67108864:return e&130023424;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 1073741824;default:return e}}function si(e,t){var n=e.pendingLanes;if(n===0)return 0;var r=0,l=e.suspendedLanes,i=e.pingedLanes,o=n&268435455;if(o!==0){var a=o&~l;a!==0?r=Ir(a):(i&=o,i!==0&&(r=Ir(i)))}else o=n&~l,o!==0?r=Ir(o):i!==0&&(r=Ir(i));if(r===0)return 0;if(t!==0&&t!==r&&!(t&l)&&(l=r&-r,i=t&-t,l>=i||l===16&&(i&4194240)!==0))return t;if(r&4&&(r|=n&16),t=e.entangledLanes,t!==0)for(e=e.entanglements,t&=r;0n;n++)t.push(e);return t}function ml(e,t,n){e.pendingLanes|=t,t!==536870912&&(e.suspendedLanes=0,e.pingedLanes=0),e=e.eventTimes,t=31-mt(t),e[t]=n}function ah(e,t){var n=e.pendingLanes&~t;e.pendingLanes=t,e.suspendedLanes=0,e.pingedLanes=0,e.expiredLanes&=t,e.mutableReadLanes&=t,e.entangledLanes&=t,t=e.entanglements;var r=e.eventTimes;for(e=e.expirationTimes;0=Vr),os=" ",as=!1;function hf(e,t){switch(e){case"keyup":return Oh.indexOf(t.keyCode)!==-1;case"keydown":return t.keyCode!==229;case"keypress":case"mousedown":case"focusout":return!0;default:return!1}}function mf(e){return e=e.detail,typeof e=="object"&&"data"in e?e.data:null}var Qn=!1;function Ih(e,t){switch(e){case"compositionend":return mf(t);case"keypress":return t.which!==32?null:(as=!0,os);case"textInput":return e=t.data,e===os&&as?null:e;default:return null}}function Uh(e,t){if(Qn)return e==="compositionend"||!Ya&&hf(e,t)?(e=df(),Gl=$a=Jt=null,Qn=!1,e):null;switch(e){case"paste":return null;case"keypress":if(!(t.ctrlKey||t.altKey||t.metaKey)||t.ctrlKey&&t.altKey){if(t.char&&1=t)return{node:n,offset:t-e};e=r}e:{for(;n;){if(n.nextSibling){n=n.nextSibling;break e}n=n.parentNode}n=void 0}n=fs(n)}}function wf(e,t){return e&&t?e===t?!0:e&&e.nodeType===3?!1:t&&t.nodeType===3?wf(e,t.parentNode):"contains"in e?e.contains(t):e.compareDocumentPosition?!!(e.compareDocumentPosition(t)&16):!1:!1}function Sf(){for(var e=window,t=ii();t instanceof e.HTMLIFrameElement;){try{var n=typeof t.contentWindow.location.href=="string"}catch{n=!1}if(n)e=t.contentWindow;else break;t=ii(e.document)}return t}function Xa(e){var t=e&&e.nodeName&&e.nodeName.toLowerCase();return t&&(t==="input"&&(e.type==="text"||e.type==="search"||e.type==="tel"||e.type==="url"||e.type==="password")||t==="textarea"||e.contentEditable==="true")}function Yh(e){var t=Sf(),n=e.focusedElem,r=e.selectionRange;if(t!==n&&n&&n.ownerDocument&&wf(n.ownerDocument.documentElement,n)){if(r!==null&&Xa(n)){if(t=r.start,e=r.end,e===void 0&&(e=t),"selectionStart"in n)n.selectionStart=t,n.selectionEnd=Math.min(e,n.value.length);else if(e=(t=n.ownerDocument||document)&&t.defaultView||window,e.getSelection){e=e.getSelection();var l=n.textContent.length,i=Math.min(r.start,l);r=r.end===void 0?i:Math.min(r.end,l),!e.extend&&i>r&&(l=r,r=i,i=l),l=ds(n,i);var o=ds(n,r);l&&o&&(e.rangeCount!==1||e.anchorNode!==l.node||e.anchorOffset!==l.offset||e.focusNode!==o.node||e.focusOffset!==o.offset)&&(t=t.createRange(),t.setStart(l.node,l.offset),e.removeAllRanges(),i>r?(e.addRange(t),e.extend(o.node,o.offset)):(t.setEnd(o.node,o.offset),e.addRange(t)))}}for(t=[],e=n;e=e.parentNode;)e.nodeType===1&&t.push({element:e,left:e.scrollLeft,top:e.scrollTop});for(typeof n.focus=="function"&&n.focus(),n=0;n=document.documentMode,Kn=null,qo=null,$r=null,bo=!1;function ps(e,t,n){var r=n.window===n?n.document:n.nodeType===9?n:n.ownerDocument;bo||Kn==null||Kn!==ii(r)||(r=Kn,"selectionStart"in r&&Xa(r)?r={start:r.selectionStart,end:r.selectionEnd}:(r=(r.ownerDocument&&r.ownerDocument.defaultView||window).getSelection(),r={anchorNode:r.anchorNode,anchorOffset:r.anchorOffset,focusNode:r.focusNode,focusOffset:r.focusOffset}),$r&&nl($r,r)||($r=r,r=di(qo,"onSelect"),0Gn||(e.current=ia[Gn],ia[Gn]=null,Gn--)}function ie(e,t){Gn++,ia[Gn]=e.current,e.current=t}var sn={},ze=fn(sn),$e=fn(!1),Rn=sn;function ur(e,t){var n=e.type.contextTypes;if(!n)return sn;var r=e.stateNode;if(r&&r.__reactInternalMemoizedUnmaskedChildContext===t)return r.__reactInternalMemoizedMaskedChildContext;var l={},i;for(i in n)l[i]=t[i];return r&&(e=e.stateNode,e.__reactInternalMemoizedUnmaskedChildContext=t,e.__reactInternalMemoizedMaskedChildContext=l),l}function Qe(e){return e=e.childContextTypes,e!=null}function hi(){ae($e),ae(ze)}function Ss(e,t,n){if(ze.current!==sn)throw Error(R(168));ie(ze,t),ie($e,n)}function Nf(e,t,n){var r=e.stateNode;if(t=t.childContextTypes,typeof r.getChildContext!="function")return n;r=r.getChildContext();for(var l in r)if(!(l in t))throw Error(R(108,Vp(e)||"Unknown",l));return de({},n,r)}function mi(e){return e=(e=e.stateNode)&&e.__reactInternalMemoizedMergedChildContext||sn,Rn=ze.current,ie(ze,e),ie($e,$e.current),!0}function Es(e,t,n){var r=e.stateNode;if(!r)throw Error(R(169));n?(e=Nf(e,t,Rn),r.__reactInternalMemoizedMergedChildContext=e,ae($e),ae(ze),ie(ze,e)):ae($e),ie($e,n)}var Rt=null,Ai=!1,So=!1;function Tf(e){Rt===null?Rt=[e]:Rt.push(e)}function im(e){Ai=!0,Tf(e)}function dn(){if(!So&&Rt!==null){So=!0;var e=0,t=ee;try{var n=Rt;for(ee=1;e>=o,l-=o,Lt=1<<32-mt(t)+l|n<C?(H=y,y=null):H=y.sibling;var D=p(m,y,v[C],k);if(D===null){y===null&&(y=H);break}e&&y&&D.alternate===null&&t(m,y),c=i(D,c,C),z===null?L=D:z.sibling=D,z=D,y=H}if(C===v.length)return n(m,y),se&&yn(m,C),L;if(y===null){for(;CC?(H=y,y=null):H=y.sibling;var q=p(m,y,D.value,k);if(q===null){y===null&&(y=H);break}e&&y&&q.alternate===null&&t(m,y),c=i(q,c,C),z===null?L=q:z.sibling=q,z=q,y=H}if(D.done)return n(m,y),se&&yn(m,C),L;if(y===null){for(;!D.done;C++,D=v.next())D=f(m,D.value,k),D!==null&&(c=i(D,c,C),z===null?L=D:z.sibling=D,z=D);return se&&yn(m,C),L}for(y=r(m,y);!D.done;C++,D=v.next())D=w(y,m,C,D.value,k),D!==null&&(e&&D.alternate!==null&&y.delete(D.key===null?C:D.key),c=i(D,c,C),z===null?L=D:z.sibling=D,z=D);return e&&y.forEach(function(re){return t(m,re)}),se&&yn(m,C),L}function T(m,c,v,k){if(typeof v=="object"&&v!==null&&v.type===$n&&v.key===null&&(v=v.props.children),typeof v=="object"&&v!==null){switch(v.$$typeof){case Ll:e:{for(var L=v.key,z=c;z!==null;){if(z.key===L){if(L=v.type,L===$n){if(z.tag===7){n(m,z.sibling),c=l(z,v.props.children),c.return=m,m=c;break e}}else if(z.elementType===L||typeof L=="object"&&L!==null&&L.$$typeof===Kt&&Cs(L)===z.type){n(m,z.sibling),c=l(z,v.props),c.ref=Nr(m,z,v),c.return=m,m=c;break e}n(m,z);break}else t(m,z);z=z.sibling}v.type===$n?(c=_n(v.props.children,m.mode,k,v.key),c.return=m,m=c):(k=ri(v.type,v.key,v.props,null,m.mode,k),k.ref=Nr(m,c,v),k.return=m,m=k)}return o(m);case Wn:e:{for(z=v.key;c!==null;){if(c.key===z)if(c.tag===4&&c.stateNode.containerInfo===v.containerInfo&&c.stateNode.implementation===v.implementation){n(m,c.sibling),c=l(c,v.children||[]),c.return=m,m=c;break e}else{n(m,c);break}else t(m,c);c=c.sibling}c=Lo(v,m.mode,k),c.return=m,m=c}return o(m);case Kt:return z=v._init,T(m,c,z(v._payload),k)}if(Fr(v))return x(m,c,v,k);if(Cr(v))return E(m,c,v,k);Al(m,v)}return typeof v=="string"&&v!==""||typeof v=="number"?(v=""+v,c!==null&&c.tag===6?(n(m,c.sibling),c=l(c,v),c.return=m,m=c):(n(m,c),c=Ro(v,m.mode,k),c.return=m,m=c),o(m)):n(m,c)}return T}var cr=jf(!0),Of=jf(!1),gi=fn(null),wi=null,qn=null,qa=null;function ba(){qa=qn=wi=null}function eu(e){var t=gi.current;ae(gi),e._currentValue=t}function ua(e,t,n){for(;e!==null;){var r=e.alternate;if((e.childLanes&t)!==t?(e.childLanes|=t,r!==null&&(r.childLanes|=t)):r!==null&&(r.childLanes&t)!==t&&(r.childLanes|=t),e===n)break;e=e.return}}function ir(e,t){wi=e,qa=qn=null,e=e.dependencies,e!==null&&e.firstContext!==null&&(e.lanes&t&&(We=!0),e.firstContext=null)}function ot(e){var t=e._currentValue;if(qa!==e)if(e={context:e,memoizedValue:t,next:null},qn===null){if(wi===null)throw Error(R(308));qn=e,wi.dependencies={lanes:0,firstContext:e}}else qn=qn.next=e;return t}var xn=null;function tu(e){xn===null?xn=[e]:xn.push(e)}function Ff(e,t,n,r){var l=t.interleaved;return l===null?(n.next=n,tu(t)):(n.next=l.next,l.next=n),t.interleaved=n,jt(e,r)}function jt(e,t){e.lanes|=t;var n=e.alternate;for(n!==null&&(n.lanes|=t),n=e,e=e.return;e!==null;)e.childLanes|=t,n=e.alternate,n!==null&&(n.childLanes|=t),n=e,e=e.return;return n.tag===3?n.stateNode:null}var Yt=!1;function nu(e){e.updateQueue={baseState:e.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,interleaved:null,lanes:0},effects:null}}function If(e,t){e=e.updateQueue,t.updateQueue===e&&(t.updateQueue={baseState:e.baseState,firstBaseUpdate:e.firstBaseUpdate,lastBaseUpdate:e.lastBaseUpdate,shared:e.shared,effects:e.effects})}function Tt(e,t){return{eventTime:e,lane:t,tag:0,payload:null,callback:null,next:null}}function rn(e,t,n){var r=e.updateQueue;if(r===null)return null;if(r=r.shared,G&2){var l=r.pending;return l===null?t.next=t:(t.next=l.next,l.next=t),r.pending=t,jt(e,n)}return l=r.interleaved,l===null?(t.next=t,tu(r)):(t.next=l.next,l.next=t),r.interleaved=t,jt(e,n)}function Zl(e,t,n){if(t=t.updateQueue,t!==null&&(t=t.shared,(n&4194240)!==0)){var r=t.lanes;r&=e.pendingLanes,n|=r,t.lanes=n,Ha(e,n)}}function Ps(e,t){var n=e.updateQueue,r=e.alternate;if(r!==null&&(r=r.updateQueue,n===r)){var l=null,i=null;if(n=n.firstBaseUpdate,n!==null){do{var o={eventTime:n.eventTime,lane:n.lane,tag:n.tag,payload:n.payload,callback:n.callback,next:null};i===null?l=i=o:i=i.next=o,n=n.next}while(n!==null);i===null?l=i=t:i=i.next=t}else l=i=t;n={baseState:r.baseState,firstBaseUpdate:l,lastBaseUpdate:i,shared:r.shared,effects:r.effects},e.updateQueue=n;return}e=n.lastBaseUpdate,e===null?n.firstBaseUpdate=t:e.next=t,n.lastBaseUpdate=t}function Si(e,t,n,r){var l=e.updateQueue;Yt=!1;var i=l.firstBaseUpdate,o=l.lastBaseUpdate,a=l.shared.pending;if(a!==null){l.shared.pending=null;var u=a,s=u.next;u.next=null,o===null?i=s:o.next=s,o=u;var d=e.alternate;d!==null&&(d=d.updateQueue,a=d.lastBaseUpdate,a!==o&&(a===null?d.firstBaseUpdate=s:a.next=s,d.lastBaseUpdate=u))}if(i!==null){var f=l.baseState;o=0,d=s=u=null,a=i;do{var p=a.lane,w=a.eventTime;if((r&p)===p){d!==null&&(d=d.next={eventTime:w,lane:0,tag:a.tag,payload:a.payload,callback:a.callback,next:null});e:{var x=e,E=a;switch(p=t,w=n,E.tag){case 1:if(x=E.payload,typeof x=="function"){f=x.call(w,f,p);break e}f=x;break e;case 3:x.flags=x.flags&-65537|128;case 0:if(x=E.payload,p=typeof x=="function"?x.call(w,f,p):x,p==null)break e;f=de({},f,p);break e;case 2:Yt=!0}}a.callback!==null&&a.lane!==0&&(e.flags|=64,p=l.effects,p===null?l.effects=[a]:p.push(a))}else w={eventTime:w,lane:p,tag:a.tag,payload:a.payload,callback:a.callback,next:null},d===null?(s=d=w,u=f):d=d.next=w,o|=p;if(a=a.next,a===null){if(a=l.shared.pending,a===null)break;p=a,a=p.next,p.next=null,l.lastBaseUpdate=p,l.shared.pending=null}}while(!0);if(d===null&&(u=f),l.baseState=u,l.firstBaseUpdate=s,l.lastBaseUpdate=d,t=l.shared.interleaved,t!==null){l=t;do o|=l.lane,l=l.next;while(l!==t)}else i===null&&(l.shared.lanes=0);Tn|=o,e.lanes=o,e.memoizedState=f}}function _s(e,t,n){if(e=t.effects,t.effects=null,e!==null)for(t=0;tn?n:4,e(!0);var r=xo.transition;xo.transition={};try{e(!1),t()}finally{ee=n,xo.transition=r}}function ed(){return at().memoizedState}function sm(e,t,n){var r=on(e);if(n={lane:r,action:n,hasEagerState:!1,eagerState:null,next:null},td(e))nd(t,n);else if(n=Ff(e,t,n,r),n!==null){var l=Ue();vt(n,e,r,l),rd(n,t,r)}}function cm(e,t,n){var r=on(e),l={lane:r,action:n,hasEagerState:!1,eagerState:null,next:null};if(td(e))nd(t,l);else{var i=e.alternate;if(e.lanes===0&&(i===null||i.lanes===0)&&(i=t.lastRenderedReducer,i!==null))try{var o=t.lastRenderedState,a=i(o,n);if(l.hasEagerState=!0,l.eagerState=a,yt(a,o)){var u=t.interleaved;u===null?(l.next=l,tu(t)):(l.next=u.next,u.next=l),t.interleaved=l;return}}catch{}finally{}n=Ff(e,t,l,r),n!==null&&(l=Ue(),vt(n,e,r,l),rd(n,t,r))}}function td(e){var t=e.alternate;return e===fe||t!==null&&t===fe}function nd(e,t){Qr=xi=!0;var n=e.pending;n===null?t.next=t:(t.next=n.next,n.next=t),e.pending=t}function rd(e,t,n){if(n&4194240){var r=t.lanes;r&=e.pendingLanes,n|=r,t.lanes=n,Ha(e,n)}}var ki={readContext:ot,useCallback:Te,useContext:Te,useEffect:Te,useImperativeHandle:Te,useInsertionEffect:Te,useLayoutEffect:Te,useMemo:Te,useReducer:Te,useRef:Te,useState:Te,useDebugValue:Te,useDeferredValue:Te,useTransition:Te,useMutableSource:Te,useSyncExternalStore:Te,useId:Te,unstable_isNewReconciler:!1},fm={readContext:ot,useCallback:function(e,t){return St().memoizedState=[e,t===void 0?null:t],e},useContext:ot,useEffect:Ls,useImperativeHandle:function(e,t,n){return n=n!=null?n.concat([e]):null,bl(4194308,4,Gf.bind(null,t,e),n)},useLayoutEffect:function(e,t){return bl(4194308,4,e,t)},useInsertionEffect:function(e,t){return bl(4,2,e,t)},useMemo:function(e,t){var n=St();return t=t===void 0?null:t,e=e(),n.memoizedState=[e,t],e},useReducer:function(e,t,n){var r=St();return t=n!==void 0?n(t):t,r.memoizedState=r.baseState=t,e={pending:null,interleaved:null,lanes:0,dispatch:null,lastRenderedReducer:e,lastRenderedState:t},r.queue=e,e=e.dispatch=sm.bind(null,fe,e),[r.memoizedState,e]},useRef:function(e){var t=St();return e={current:e},t.memoizedState=e},useState:Rs,useDebugValue:cu,useDeferredValue:function(e){return St().memoizedState=e},useTransition:function(){var e=Rs(!1),t=e[0];return e=um.bind(null,e[1]),St().memoizedState=e,[t,e]},useMutableSource:function(){},useSyncExternalStore:function(e,t,n){var r=fe,l=St();if(se){if(n===void 0)throw Error(R(407));n=n()}else{if(n=t(),Ce===null)throw Error(R(349));Nn&30||Hf(r,t,n)}l.memoizedState=n;var i={value:n,getSnapshot:t};return l.queue=i,Ls(Wf.bind(null,r,i,e),[e]),r.flags|=2048,cl(9,Vf.bind(null,r,i,n,t),void 0,null),n},useId:function(){var e=St(),t=Ce.identifierPrefix;if(se){var n=Nt,r=Lt;n=(r&~(1<<32-mt(r)-1)).toString(32)+n,t=":"+t+"R"+n,n=ul++,0<\/script>",e=e.removeChild(e.firstChild)):typeof r.is=="string"?e=o.createElement(n,{is:r.is}):(e=o.createElement(n),n==="select"&&(o=e,r.multiple?o.multiple=!0:r.size&&(o.size=r.size))):e=o.createElementNS(e,n),e[Et]=t,e[il]=r,pd(e,t,!1,!1),t.stateNode=e;e:{switch(o=$o(n,r),n){case"dialog":oe("cancel",e),oe("close",e),l=r;break;case"iframe":case"object":case"embed":oe("load",e),l=r;break;case"video":case"audio":for(l=0;lpr&&(t.flags|=128,r=!0,Tr(i,!1),t.lanes=4194304)}else{if(!r)if(e=Ei(o),e!==null){if(t.flags|=128,r=!0,n=e.updateQueue,n!==null&&(t.updateQueue=n,t.flags|=4),Tr(i,!0),i.tail===null&&i.tailMode==="hidden"&&!o.alternate&&!se)return De(t),null}else 2*ve()-i.renderingStartTime>pr&&n!==1073741824&&(t.flags|=128,r=!0,Tr(i,!1),t.lanes=4194304);i.isBackwards?(o.sibling=t.child,t.child=o):(n=i.last,n!==null?n.sibling=o:t.child=o,i.last=o)}return i.tail!==null?(t=i.tail,i.rendering=t,i.tail=t.sibling,i.renderingStartTime=ve(),t.sibling=null,n=ce.current,ie(ce,r?n&1|2:n&1),t):(De(t),null);case 22:case 23:return vu(),r=t.memoizedState!==null,e!==null&&e.memoizedState!==null!==r&&(t.flags|=8192),r&&t.mode&1?Xe&1073741824&&(De(t),t.subtreeFlags&6&&(t.flags|=8192)):De(t),null;case 24:return null;case 25:return null}throw Error(R(156,t.tag))}function wm(e,t){switch(Ja(t),t.tag){case 1:return Qe(t.type)&&hi(),e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 3:return fr(),ae($e),ae(ze),iu(),e=t.flags,e&65536&&!(e&128)?(t.flags=e&-65537|128,t):null;case 5:return lu(t),null;case 13:if(ae(ce),e=t.memoizedState,e!==null&&e.dehydrated!==null){if(t.alternate===null)throw Error(R(340));sr()}return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 19:return ae(ce),null;case 4:return fr(),null;case 10:return eu(t.type._context),null;case 22:case 23:return vu(),null;case 24:return null;default:return null}}var Hl=!1,Me=!1,Sm=typeof WeakSet=="function"?WeakSet:Set,j=null;function bn(e,t){var n=e.ref;if(n!==null)if(typeof n=="function")try{n(null)}catch(r){me(e,t,r)}else n.current=null}function ya(e,t,n){try{n()}catch(r){me(e,t,r)}}var As=!1;function Em(e,t){if(ea=ci,e=Sf(),Xa(e)){if("selectionStart"in e)var n={start:e.selectionStart,end:e.selectionEnd};else e:{n=(n=e.ownerDocument)&&n.defaultView||window;var r=n.getSelection&&n.getSelection();if(r&&r.rangeCount!==0){n=r.anchorNode;var l=r.anchorOffset,i=r.focusNode;r=r.focusOffset;try{n.nodeType,i.nodeType}catch{n=null;break e}var o=0,a=-1,u=-1,s=0,d=0,f=e,p=null;t:for(;;){for(var w;f!==n||l!==0&&f.nodeType!==3||(a=o+l),f!==i||r!==0&&f.nodeType!==3||(u=o+r),f.nodeType===3&&(o+=f.nodeValue.length),(w=f.firstChild)!==null;)p=f,f=w;for(;;){if(f===e)break t;if(p===n&&++s===l&&(a=o),p===i&&++d===r&&(u=o),(w=f.nextSibling)!==null)break;f=p,p=f.parentNode}f=w}n=a===-1||u===-1?null:{start:a,end:u}}else n=null}n=n||{start:0,end:0}}else n=null;for(ta={focusedElem:e,selectionRange:n},ci=!1,j=t;j!==null;)if(t=j,e=t.child,(t.subtreeFlags&1028)!==0&&e!==null)e.return=t,j=e;else for(;j!==null;){t=j;try{var x=t.alternate;if(t.flags&1024)switch(t.tag){case 0:case 11:case 15:break;case 1:if(x!==null){var E=x.memoizedProps,T=x.memoizedState,m=t.stateNode,c=m.getSnapshotBeforeUpdate(t.elementType===t.type?E:ft(t.type,E),T);m.__reactInternalSnapshotBeforeUpdate=c}break;case 3:var v=t.stateNode.containerInfo;v.nodeType===1?v.textContent="":v.nodeType===9&&v.documentElement&&v.removeChild(v.documentElement);break;case 5:case 6:case 4:case 17:break;default:throw Error(R(163))}}catch(k){me(t,t.return,k)}if(e=t.sibling,e!==null){e.return=t.return,j=e;break}j=t.return}return x=As,As=!1,x}function Kr(e,t,n){var r=t.updateQueue;if(r=r!==null?r.lastEffect:null,r!==null){var l=r=r.next;do{if((l.tag&e)===e){var i=l.destroy;l.destroy=void 0,i!==void 0&&ya(t,n,i)}l=l.next}while(l!==r)}}function Vi(e,t){if(t=t.updateQueue,t=t!==null?t.lastEffect:null,t!==null){var n=t=t.next;do{if((n.tag&e)===e){var r=n.create;n.destroy=r()}n=n.next}while(n!==t)}}function ga(e){var t=e.ref;if(t!==null){var n=e.stateNode;switch(e.tag){case 5:e=n;break;default:e=n}typeof t=="function"?t(e):t.current=e}}function vd(e){var t=e.alternate;t!==null&&(e.alternate=null,vd(t)),e.child=null,e.deletions=null,e.sibling=null,e.tag===5&&(t=e.stateNode,t!==null&&(delete t[Et],delete t[il],delete t[la],delete t[rm],delete t[lm])),e.stateNode=null,e.return=null,e.dependencies=null,e.memoizedProps=null,e.memoizedState=null,e.pendingProps=null,e.stateNode=null,e.updateQueue=null}function yd(e){return e.tag===5||e.tag===3||e.tag===4}function Bs(e){e:for(;;){for(;e.sibling===null;){if(e.return===null||yd(e.return))return null;e=e.return}for(e.sibling.return=e.return,e=e.sibling;e.tag!==5&&e.tag!==6&&e.tag!==18;){if(e.flags&2||e.child===null||e.tag===4)continue e;e.child.return=e,e=e.child}if(!(e.flags&2))return e.stateNode}}function wa(e,t,n){var r=e.tag;if(r===5||r===6)e=e.stateNode,t?n.nodeType===8?n.parentNode.insertBefore(e,t):n.insertBefore(e,t):(n.nodeType===8?(t=n.parentNode,t.insertBefore(e,n)):(t=n,t.appendChild(e)),n=n._reactRootContainer,n!=null||t.onclick!==null||(t.onclick=pi));else if(r!==4&&(e=e.child,e!==null))for(wa(e,t,n),e=e.sibling;e!==null;)wa(e,t,n),e=e.sibling}function Sa(e,t,n){var r=e.tag;if(r===5||r===6)e=e.stateNode,t?n.insertBefore(e,t):n.appendChild(e);else if(r!==4&&(e=e.child,e!==null))for(Sa(e,t,n),e=e.sibling;e!==null;)Sa(e,t,n),e=e.sibling}var Re=null,dt=!1;function $t(e,t,n){for(n=n.child;n!==null;)gd(e,t,n),n=n.sibling}function gd(e,t,n){if(xt&&typeof xt.onCommitFiberUnmount=="function")try{xt.onCommitFiberUnmount(ji,n)}catch{}switch(n.tag){case 5:Me||bn(n,t);case 6:var r=Re,l=dt;Re=null,$t(e,t,n),Re=r,dt=l,Re!==null&&(dt?(e=Re,n=n.stateNode,e.nodeType===8?e.parentNode.removeChild(n):e.removeChild(n)):Re.removeChild(n.stateNode));break;case 18:Re!==null&&(dt?(e=Re,n=n.stateNode,e.nodeType===8?wo(e.parentNode,n):e.nodeType===1&&wo(e,n),el(e)):wo(Re,n.stateNode));break;case 4:r=Re,l=dt,Re=n.stateNode.containerInfo,dt=!0,$t(e,t,n),Re=r,dt=l;break;case 0:case 11:case 14:case 15:if(!Me&&(r=n.updateQueue,r!==null&&(r=r.lastEffect,r!==null))){l=r=r.next;do{var i=l,o=i.destroy;i=i.tag,o!==void 0&&(i&2||i&4)&&ya(n,t,o),l=l.next}while(l!==r)}$t(e,t,n);break;case 1:if(!Me&&(bn(n,t),r=n.stateNode,typeof r.componentWillUnmount=="function"))try{r.props=n.memoizedProps,r.state=n.memoizedState,r.componentWillUnmount()}catch(a){me(n,t,a)}$t(e,t,n);break;case 21:$t(e,t,n);break;case 22:n.mode&1?(Me=(r=Me)||n.memoizedState!==null,$t(e,t,n),Me=r):$t(e,t,n);break;default:$t(e,t,n)}}function Hs(e){var t=e.updateQueue;if(t!==null){e.updateQueue=null;var n=e.stateNode;n===null&&(n=e.stateNode=new Sm),t.forEach(function(r){var l=Tm.bind(null,e,r);n.has(r)||(n.add(r),r.then(l,l))})}}function ct(e,t){var n=t.deletions;if(n!==null)for(var r=0;rl&&(l=o),r&=~i}if(r=l,r=ve()-r,r=(120>r?120:480>r?480:1080>r?1080:1920>r?1920:3e3>r?3e3:4320>r?4320:1960*km(r/1960))-r,10e?16:e,Zt===null)var r=!1;else{if(e=Zt,Zt=null,_i=0,G&6)throw Error(R(331));var l=G;for(G|=4,j=e.current;j!==null;){var i=j,o=i.child;if(j.flags&16){var a=i.deletions;if(a!==null){for(var u=0;uve()-hu?Pn(e,0):pu|=n),Ke(e,t)}function _d(e,t){t===0&&(e.mode&1?(t=Ml,Ml<<=1,!(Ml&130023424)&&(Ml=4194304)):t=1);var n=Ue();e=jt(e,t),e!==null&&(ml(e,t,n),Ke(e,n))}function Nm(e){var t=e.memoizedState,n=0;t!==null&&(n=t.retryLane),_d(e,n)}function Tm(e,t){var n=0;switch(e.tag){case 13:var r=e.stateNode,l=e.memoizedState;l!==null&&(n=l.retryLane);break;case 19:r=e.stateNode;break;default:throw Error(R(314))}r!==null&&r.delete(t),_d(e,n)}var Rd;Rd=function(e,t,n){if(e!==null)if(e.memoizedProps!==t.pendingProps||$e.current)We=!0;else{if(!(e.lanes&n)&&!(t.flags&128))return We=!1,ym(e,t,n);We=!!(e.flags&131072)}else We=!1,se&&t.flags&1048576&&Df(t,yi,t.index);switch(t.lanes=0,t.tag){case 2:var r=t.type;ei(e,t),e=t.pendingProps;var l=ur(t,ze.current);ir(t,n),l=au(null,t,r,e,l,n);var i=uu();return t.flags|=1,typeof l=="object"&&l!==null&&typeof l.render=="function"&&l.$$typeof===void 0?(t.tag=1,t.memoizedState=null,t.updateQueue=null,Qe(r)?(i=!0,mi(t)):i=!1,t.memoizedState=l.state!==null&&l.state!==void 0?l.state:null,nu(t),l.updater=Hi,t.stateNode=l,l._reactInternals=t,ca(t,r,e,n),t=pa(null,t,r,!0,i,n)):(t.tag=0,se&&i&&Ga(t),Ie(null,t,l,n),t=t.child),t;case 16:r=t.elementType;e:{switch(ei(e,t),e=t.pendingProps,l=r._init,r=l(r._payload),t.type=r,l=t.tag=Mm(r),e=ft(r,e),l){case 0:t=da(null,t,r,e,n);break e;case 1:t=Fs(null,t,r,e,n);break e;case 11:t=js(null,t,r,e,n);break e;case 14:t=Os(null,t,r,ft(r.type,e),n);break e}throw Error(R(306,r,""))}return t;case 0:return r=t.type,l=t.pendingProps,l=t.elementType===r?l:ft(r,l),da(e,t,r,l,n);case 1:return r=t.type,l=t.pendingProps,l=t.elementType===r?l:ft(r,l),Fs(e,t,r,l,n);case 3:e:{if(cd(t),e===null)throw Error(R(387));r=t.pendingProps,i=t.memoizedState,l=i.element,If(e,t),Si(t,r,null,n);var o=t.memoizedState;if(r=o.element,i.isDehydrated)if(i={element:r,isDehydrated:!1,cache:o.cache,pendingSuspenseBoundaries:o.pendingSuspenseBoundaries,transitions:o.transitions},t.updateQueue.baseState=i,t.memoizedState=i,t.flags&256){l=dr(Error(R(423)),t),t=Is(e,t,r,n,l);break e}else if(r!==l){l=dr(Error(R(424)),t),t=Is(e,t,r,n,l);break e}else for(Je=nn(t.stateNode.containerInfo.firstChild),Ze=t,se=!0,ht=null,n=Of(t,null,r,n),t.child=n;n;)n.flags=n.flags&-3|4096,n=n.sibling;else{if(sr(),r===l){t=Ot(e,t,n);break e}Ie(e,t,r,n)}t=t.child}return t;case 5:return Uf(t),e===null&&aa(t),r=t.type,l=t.pendingProps,i=e!==null?e.memoizedProps:null,o=l.children,na(r,l)?o=null:i!==null&&na(r,i)&&(t.flags|=32),sd(e,t),Ie(e,t,o,n),t.child;case 6:return e===null&&aa(t),null;case 13:return fd(e,t,n);case 4:return ru(t,t.stateNode.containerInfo),r=t.pendingProps,e===null?t.child=cr(t,null,r,n):Ie(e,t,r,n),t.child;case 11:return r=t.type,l=t.pendingProps,l=t.elementType===r?l:ft(r,l),js(e,t,r,l,n);case 7:return Ie(e,t,t.pendingProps,n),t.child;case 8:return Ie(e,t,t.pendingProps.children,n),t.child;case 12:return Ie(e,t,t.pendingProps.children,n),t.child;case 10:e:{if(r=t.type._context,l=t.pendingProps,i=t.memoizedProps,o=l.value,ie(gi,r._currentValue),r._currentValue=o,i!==null)if(yt(i.value,o)){if(i.children===l.children&&!$e.current){t=Ot(e,t,n);break e}}else for(i=t.child,i!==null&&(i.return=t);i!==null;){var a=i.dependencies;if(a!==null){o=i.child;for(var u=a.firstContext;u!==null;){if(u.context===r){if(i.tag===1){u=Tt(-1,n&-n),u.tag=2;var s=i.updateQueue;if(s!==null){s=s.shared;var d=s.pending;d===null?u.next=u:(u.next=d.next,d.next=u),s.pending=u}}i.lanes|=n,u=i.alternate,u!==null&&(u.lanes|=n),ua(i.return,n,t),a.lanes|=n;break}u=u.next}}else if(i.tag===10)o=i.type===t.type?null:i.child;else if(i.tag===18){if(o=i.return,o===null)throw Error(R(341));o.lanes|=n,a=o.alternate,a!==null&&(a.lanes|=n),ua(o,n,t),o=i.sibling}else o=i.child;if(o!==null)o.return=i;else for(o=i;o!==null;){if(o===t){o=null;break}if(i=o.sibling,i!==null){i.return=o.return,o=i;break}o=o.return}i=o}Ie(e,t,l.children,n),t=t.child}return t;case 9:return l=t.type,r=t.pendingProps.children,ir(t,n),l=ot(l),r=r(l),t.flags|=1,Ie(e,t,r,n),t.child;case 14:return r=t.type,l=ft(r,t.pendingProps),l=ft(r.type,l),Os(e,t,r,l,n);case 15:return ad(e,t,t.type,t.pendingProps,n);case 17:return r=t.type,l=t.pendingProps,l=t.elementType===r?l:ft(r,l),ei(e,t),t.tag=1,Qe(r)?(e=!0,mi(t)):e=!1,ir(t,n),ld(t,r,l),ca(t,r,l,n),pa(null,t,r,!0,e,n);case 19:return dd(e,t,n);case 22:return ud(e,t,n)}throw Error(R(156,t.tag))};function Ld(e,t){return ef(e,t)}function Dm(e,t,n,r){this.tag=e,this.key=n,this.sibling=this.child=this.return=this.stateNode=this.type=this.elementType=null,this.index=0,this.ref=null,this.pendingProps=t,this.dependencies=this.memoizedState=this.updateQueue=this.memoizedProps=null,this.mode=r,this.subtreeFlags=this.flags=0,this.deletions=null,this.childLanes=this.lanes=0,this.alternate=null}function lt(e,t,n,r){return new Dm(e,t,n,r)}function gu(e){return e=e.prototype,!(!e||!e.isReactComponent)}function Mm(e){if(typeof e=="function")return gu(e)?1:0;if(e!=null){if(e=e.$$typeof,e===Ia)return 11;if(e===Ua)return 14}return 2}function an(e,t){var n=e.alternate;return n===null?(n=lt(e.tag,t,e.key,e.mode),n.elementType=e.elementType,n.type=e.type,n.stateNode=e.stateNode,n.alternate=e,e.alternate=n):(n.pendingProps=t,n.type=e.type,n.flags=0,n.subtreeFlags=0,n.deletions=null),n.flags=e.flags&14680064,n.childLanes=e.childLanes,n.lanes=e.lanes,n.child=e.child,n.memoizedProps=e.memoizedProps,n.memoizedState=e.memoizedState,n.updateQueue=e.updateQueue,t=e.dependencies,n.dependencies=t===null?null:{lanes:t.lanes,firstContext:t.firstContext},n.sibling=e.sibling,n.index=e.index,n.ref=e.ref,n}function ri(e,t,n,r,l,i){var o=2;if(r=e,typeof e=="function")gu(e)&&(o=1);else if(typeof e=="string")o=5;else e:switch(e){case $n:return _n(n.children,l,i,t);case Fa:o=8,l|=8;break;case jo:return e=lt(12,n,t,l|2),e.elementType=jo,e.lanes=i,e;case Oo:return e=lt(13,n,t,l),e.elementType=Oo,e.lanes=i,e;case Fo:return e=lt(19,n,t,l),e.elementType=Fo,e.lanes=i,e;case Ic:return $i(n,l,i,t);default:if(typeof e=="object"&&e!==null)switch(e.$$typeof){case Oc:o=10;break e;case Fc:o=9;break e;case Ia:o=11;break e;case Ua:o=14;break e;case Kt:o=16,r=null;break e}throw Error(R(130,e==null?e:typeof e,""))}return t=lt(o,n,t,l),t.elementType=e,t.type=r,t.lanes=i,t}function _n(e,t,n,r){return e=lt(7,e,r,t),e.lanes=n,e}function $i(e,t,n,r){return e=lt(22,e,r,t),e.elementType=Ic,e.lanes=n,e.stateNode={isHidden:!1},e}function Ro(e,t,n){return e=lt(6,e,null,t),e.lanes=n,e}function Lo(e,t,n){return t=lt(4,e.children!==null?e.children:[],e.key,t),t.lanes=n,t.stateNode={containerInfo:e.containerInfo,pendingChildren:null,implementation:e.implementation},t}function zm(e,t,n,r,l){this.tag=t,this.containerInfo=e,this.finishedWork=this.pingCache=this.current=this.pendingChildren=null,this.timeoutHandle=-1,this.callbackNode=this.pendingContext=this.context=null,this.callbackPriority=0,this.eventTimes=ao(0),this.expirationTimes=ao(-1),this.entangledLanes=this.finishedLanes=this.mutableReadLanes=this.expiredLanes=this.pingedLanes=this.suspendedLanes=this.pendingLanes=0,this.entanglements=ao(0),this.identifierPrefix=r,this.onRecoverableError=l,this.mutableSourceEagerHydrationData=null}function wu(e,t,n,r,l,i,o,a,u){return e=new zm(e,t,n,a,u),t===1?(t=1,i===!0&&(t|=8)):t=0,i=lt(3,null,null,t),e.current=i,i.stateNode=e,i.memoizedState={element:r,isDehydrated:n,cache:null,transitions:null,pendingSuspenseBoundaries:null},nu(i),e}function jm(e,t,n){var r=3"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(Md)}catch(e){console.error(e)}}Md(),Dc.exports=be;var ku=Dc.exports;const Am=gc(ku),Bm=yc({__proto__:null,default:Am},[ku]);var Gs=ku;Mo.createRoot=Gs.createRoot,Mo.hydrateRoot=Gs.hydrateRoot;/** - * @remix-run/router v1.23.2 - * - * Copyright (c) Remix Software Inc. - * - * This source code is licensed under the MIT license found in the - * LICENSE.md file in the root directory of this source tree. - * - * @license MIT - */function ue(){return ue=Object.assign?Object.assign.bind():function(e){for(var t=1;t"u")throw new Error(t)}function Mn(e,t){if(!e){typeof console<"u"&&console.warn(t);try{throw new Error(t)}catch{}}}function Vm(){return Math.random().toString(36).substr(2,8)}function Zs(e,t){return{usr:e.state,key:e.key,idx:t}}function dl(e,t,n,r){return n===void 0&&(n=null),ue({pathname:typeof e=="string"?e:e.pathname,search:"",hash:""},typeof t=="string"?pn(t):t,{state:n,key:t&&t.key||r||Vm()})}function zn(e){let{pathname:t="/",search:n="",hash:r=""}=e;return n&&n!=="?"&&(t+=n.charAt(0)==="?"?n:"?"+n),r&&r!=="#"&&(t+=r.charAt(0)==="#"?r:"#"+r),t}function pn(e){let t={};if(e){let n=e.indexOf("#");n>=0&&(t.hash=e.substr(n),e=e.substr(0,n));let r=e.indexOf("?");r>=0&&(t.search=e.substr(r),e=e.substr(0,r)),e&&(t.pathname=e)}return t}function Wm(e,t,n,r){r===void 0&&(r={});let{window:l=document.defaultView,v5Compat:i=!1}=r,o=l.history,a=ge.Pop,u=null,s=d();s==null&&(s=0,o.replaceState(ue({},o.state,{idx:s}),""));function d(){return(o.state||{idx:null}).idx}function f(){a=ge.Pop;let T=d(),m=T==null?null:T-s;s=T,u&&u({action:a,location:E.location,delta:m})}function p(T,m){a=ge.Push;let c=dl(E.location,T,m);s=d()+1;let v=Zs(c,s),k=E.createHref(c);try{o.pushState(v,"",k)}catch(L){if(L instanceof DOMException&&L.name==="DataCloneError")throw L;l.location.assign(k)}i&&u&&u({action:a,location:E.location,delta:1})}function w(T,m){a=ge.Replace;let c=dl(E.location,T,m);s=d();let v=Zs(c,s),k=E.createHref(c);o.replaceState(v,"",k),i&&u&&u({action:a,location:E.location,delta:0})}function x(T){let m=l.location.origin!=="null"?l.location.origin:l.location.href,c=typeof T=="string"?T:zn(T);return c=c.replace(/ $/,"%20"),K(m,"No window.location.(origin|href) available to create URL for href: "+c),new URL(c,m)}let E={get action(){return a},get location(){return e(l,o)},listen(T){if(u)throw new Error("A history only accepts one active listener");return l.addEventListener(Js,f),u=T,()=>{l.removeEventListener(Js,f),u=null}},createHref(T){return t(l,T)},createURL:x,encodeLocation(T){let m=x(T);return{pathname:m.pathname,search:m.search,hash:m.hash}},push:p,replace:w,go(T){return o.go(T)}};return E}var b;(function(e){e.data="data",e.deferred="deferred",e.redirect="redirect",e.error="error"})(b||(b={}));const $m=new Set(["lazy","caseSensitive","path","id","index","children"]);function Qm(e){return e.index===!0}function Ni(e,t,n,r){return n===void 0&&(n=[]),r===void 0&&(r={}),e.map((l,i)=>{let o=[...n,String(i)],a=typeof l.id=="string"?l.id:o.join("-");if(K(l.index!==!0||!l.children,"Cannot specify children on an index route"),K(!r[a],'Found a route id collision on id "'+a+`". Route id's must be globally unique within Data Router usages`),Qm(l)){let u=ue({},l,t(l),{id:a});return r[a]=u,u}else{let u=ue({},l,t(l),{id:a,children:void 0});return r[a]=u,l.children&&(u.children=Ni(l.children,t,o,r)),u}})}function wn(e,t,n){return n===void 0&&(n="/"),li(e,t,n,!1)}function li(e,t,n,r){let l=typeof t=="string"?pn(t):t,i=Ft(l.pathname||"/",n);if(i==null)return null;let o=zd(e);Ym(o);let a=null;for(let u=0;a==null&&u{let u={relativePath:a===void 0?i.path||"":a,caseSensitive:i.caseSensitive===!0,childrenIndex:o,route:i};u.relativePath.startsWith("/")&&(K(u.relativePath.startsWith(r),'Absolute route path "'+u.relativePath+'" nested under path '+('"'+r+'" is not valid. An absolute child route path ')+"must start with the combined path of all its parent routes."),u.relativePath=u.relativePath.slice(r.length));let s=Dt([r,u.relativePath]),d=n.concat(u);i.children&&i.children.length>0&&(K(i.index!==!0,"Index routes must not have child routes. Please remove "+('all child routes from route path "'+s+'".')),zd(i.children,t,d,s)),!(i.path==null&&!i.index)&&t.push({path:s,score:ev(s,i.index),routesMeta:d})};return e.forEach((i,o)=>{var a;if(i.path===""||!((a=i.path)!=null&&a.includes("?")))l(i,o);else for(let u of jd(i.path))l(i,o,u)}),t}function jd(e){let t=e.split("/");if(t.length===0)return[];let[n,...r]=t,l=n.endsWith("?"),i=n.replace(/\?$/,"");if(r.length===0)return l?[i,""]:[i];let o=jd(r.join("/")),a=[];return a.push(...o.map(u=>u===""?i:[i,u].join("/"))),l&&a.push(...o),a.map(u=>e.startsWith("/")&&u===""?"/":u)}function Ym(e){e.sort((t,n)=>t.score!==n.score?n.score-t.score:tv(t.routesMeta.map(r=>r.childrenIndex),n.routesMeta.map(r=>r.childrenIndex)))}const Xm=/^:[\w-]+$/,Gm=3,Jm=2,Zm=1,qm=10,bm=-2,qs=e=>e==="*";function ev(e,t){let n=e.split("/"),r=n.length;return n.some(qs)&&(r+=bm),t&&(r+=Jm),n.filter(l=>!qs(l)).reduce((l,i)=>l+(Xm.test(i)?Gm:i===""?Zm:qm),r)}function tv(e,t){return e.length===t.length&&e.slice(0,-1).every((r,l)=>r===t[l])?e[e.length-1]-t[t.length-1]:0}function nv(e,t,n){n===void 0&&(n=!1);let{routesMeta:r}=e,l={},i="/",o=[];for(let a=0;a{let{paramName:p,isOptional:w}=d;if(p==="*"){let E=a[f]||"";o=i.slice(0,i.length-E.length).replace(/(.)\/+$/,"$1")}const x=a[f];return w&&!x?s[p]=void 0:s[p]=(x||"").replace(/%2F/g,"/"),s},{}),pathname:i,pathnameBase:o,pattern:e}}function rv(e,t,n){t===void 0&&(t=!1),n===void 0&&(n=!0),Mn(e==="*"||!e.endsWith("*")||e.endsWith("/*"),'Route path "'+e+'" will be treated as if it were '+('"'+e.replace(/\*$/,"/*")+'" because the `*` character must ')+"always follow a `/` in the pattern. To get rid of this warning, "+('please change the route path to "'+e.replace(/\*$/,"/*")+'".'));let r=[],l="^"+e.replace(/\/*\*?$/,"").replace(/^\/*/,"/").replace(/[\\.*+^${}|()[\]]/g,"\\$&").replace(/\/:([\w-]+)(\?)?/g,(o,a,u)=>(r.push({paramName:a,isOptional:u!=null}),u?"/?([^\\/]+)?":"/([^\\/]+)"));return e.endsWith("*")?(r.push({paramName:"*"}),l+=e==="*"||e==="/*"?"(.*)$":"(?:\\/(.+)|\\/*)$"):n?l+="\\/*$":e!==""&&e!=="/"&&(l+="(?:(?=\\/|$))"),[new RegExp(l,t?void 0:"i"),r]}function lv(e){try{return e.split("/").map(t=>decodeURIComponent(t).replace(/\//g,"%2F")).join("/")}catch(t){return Mn(!1,'The URL path "'+e+'" could not be decoded because it is is a malformed URL segment. This is probably due to a bad percent '+("encoding ("+t+").")),e}}function Ft(e,t){if(t==="/")return e;if(!e.toLowerCase().startsWith(t.toLowerCase()))return null;let n=t.endsWith("/")?t.length-1:t.length,r=e.charAt(n);return r&&r!=="/"?null:e.slice(n)||"/"}const iv=/^(?:[a-z][a-z0-9+.-]*:|\/\/)/i,ov=e=>iv.test(e);function av(e,t){t===void 0&&(t="/");let{pathname:n,search:r="",hash:l=""}=typeof e=="string"?pn(e):e,i;if(n)if(ov(n))i=n;else{if(n.includes("//")){let o=n;n=n.replace(/\/\/+/g,"/"),Mn(!1,"Pathnames cannot have embedded double slashes - normalizing "+(o+" -> "+n))}n.startsWith("/")?i=bs(n.substring(1),"/"):i=bs(n,t)}else i=t;return{pathname:i,search:sv(r),hash:cv(l)}}function bs(e,t){let n=t.replace(/\/+$/,"").split("/");return e.split("/").forEach(l=>{l===".."?n.length>1&&n.pop():l!=="."&&n.push(l)}),n.length>1?n.join("/"):"/"}function No(e,t,n,r){return"Cannot include a '"+e+"' character in a manually specified "+("`to."+t+"` field ["+JSON.stringify(r)+"]. Please separate it out to the ")+("`to."+n+"` field. Alternatively you may provide the full path as ")+'a string in and the router will parse it for you.'}function Od(e){return e.filter((t,n)=>n===0||t.route.path&&t.route.path.length>0)}function Gi(e,t){let n=Od(e);return t?n.map((r,l)=>l===n.length-1?r.pathname:r.pathnameBase):n.map(r=>r.pathnameBase)}function Ji(e,t,n,r){r===void 0&&(r=!1);let l;typeof e=="string"?l=pn(e):(l=ue({},e),K(!l.pathname||!l.pathname.includes("?"),No("?","pathname","search",l)),K(!l.pathname||!l.pathname.includes("#"),No("#","pathname","hash",l)),K(!l.search||!l.search.includes("#"),No("#","search","hash",l)));let i=e===""||l.pathname==="",o=i?"/":l.pathname,a;if(o==null)a=n;else{let f=t.length-1;if(!r&&o.startsWith("..")){let p=o.split("/");for(;p[0]==="..";)p.shift(),f-=1;l.pathname=p.join("/")}a=f>=0?t[f]:"/"}let u=av(l,a),s=o&&o!=="/"&&o.endsWith("/"),d=(i||o===".")&&n.endsWith("/");return!u.pathname.endsWith("/")&&(s||d)&&(u.pathname+="/"),u}const Dt=e=>e.join("/").replace(/\/\/+/g,"/"),uv=e=>e.replace(/\/+$/,"").replace(/^\/*/,"/"),sv=e=>!e||e==="?"?"":e.startsWith("?")?e:"?"+e,cv=e=>!e||e==="#"?"":e.startsWith("#")?e:"#"+e;class Di{constructor(t,n,r,l){l===void 0&&(l=!1),this.status=t,this.statusText=n||"",this.internal=l,r instanceof Error?(this.data=r.toString(),this.error=r):this.data=r}}function pl(e){return e!=null&&typeof e.status=="number"&&typeof e.statusText=="string"&&typeof e.internal=="boolean"&&"data"in e}const Fd=["post","put","patch","delete"],fv=new Set(Fd),dv=["get",...Fd],pv=new Set(dv),hv=new Set([301,302,303,307,308]),mv=new Set([307,308]),To={state:"idle",location:void 0,formMethod:void 0,formAction:void 0,formEncType:void 0,formData:void 0,json:void 0,text:void 0},vv={state:"idle",data:void 0,formMethod:void 0,formAction:void 0,formEncType:void 0,formData:void 0,json:void 0,text:void 0},Mr={state:"unblocked",proceed:void 0,reset:void 0,location:void 0},Cu=/^(?:[a-z][a-z0-9+.-]*:|\/\/)/i,yv=e=>({hasErrorBoundary:!!e.hasErrorBoundary}),Id="remix-router-transitions";function gv(e){const t=e.window?e.window:typeof window<"u"?window:void 0,n=typeof t<"u"&&typeof t.document<"u"&&typeof t.document.createElement<"u",r=!n;K(e.routes.length>0,"You must provide a non-empty routes array to createRouter");let l;if(e.mapRouteProperties)l=e.mapRouteProperties;else if(e.detectErrorBoundary){let h=e.detectErrorBoundary;l=g=>({hasErrorBoundary:h(g)})}else l=yv;let i={},o=Ni(e.routes,l,void 0,i),a,u=e.basename||"/",s=e.dataStrategy||xv,d=e.patchRoutesOnNavigation,f=ue({v7_fetcherPersist:!1,v7_normalizeFormMethod:!1,v7_partialHydration:!1,v7_prependBasename:!1,v7_relativeSplatPath:!1,v7_skipActionErrorRevalidation:!1},e.future),p=null,w=new Set,x=null,E=null,T=null,m=e.hydrationData!=null,c=wn(o,e.history.location,u),v=!1,k=null;if(c==null&&!d){let h=He(404,{pathname:e.history.location.pathname}),{matches:g,route:S}=cc(o);c=g,k={[S.id]:h}}c&&!e.hydrationData&&kl(c,o,e.history.location.pathname).active&&(c=null);let L;if(c)if(c.some(h=>h.route.lazy))L=!1;else if(!c.some(h=>h.route.loader))L=!0;else if(f.v7_partialHydration){let h=e.hydrationData?e.hydrationData.loaderData:null,g=e.hydrationData?e.hydrationData.errors:null;if(g){let S=c.findIndex(P=>g[P.route.id]!==void 0);L=c.slice(0,S+1).every(P=>!_a(P.route,h,g))}else L=c.every(S=>!_a(S.route,h,g))}else L=e.hydrationData!=null;else if(L=!1,c=[],f.v7_partialHydration){let h=kl(null,o,e.history.location.pathname);h.active&&h.matches&&(v=!0,c=h.matches)}let z,y={historyAction:e.history.action,location:e.history.location,matches:c,initialized:L,navigation:To,restoreScrollPosition:e.hydrationData!=null?!1:null,preventScrollReset:!1,revalidation:"idle",loaderData:e.hydrationData&&e.hydrationData.loaderData||{},actionData:e.hydrationData&&e.hydrationData.actionData||null,errors:e.hydrationData&&e.hydrationData.errors||k,fetchers:new Map,blockers:new Map},C=ge.Pop,H=!1,D,q=!1,re=new Map,Se=null,Pe=!1,ut=!1,Bt=[],Ht=new Set,N=new Map,V=0,$=-1,te=new Map,ne=new Set,st=new Map,Ye=new Map,je=new Set,Oe=new Map,tt=new Map,Sl;function Jd(){if(p=e.history.listen(h=>{let{action:g,location:S,delta:P}=h;if(Sl){Sl(),Sl=void 0;return}Mn(tt.size===0||P!=null,"You are trying to use a blocker on a POP navigation to a location that was not created by @remix-run/router. This will fail silently in production. This can happen if you are navigating outside the router via `window.history.pushState`/`window.location.hash` instead of using router navigation APIs. This can also happen if you are using createHashRouter and the user manually changes the URL.");let M=Iu({currentLocation:y.location,nextLocation:S,historyAction:g});if(M&&P!=null){let B=new Promise(W=>{Sl=W});e.history.go(P*-1),xl(M,{state:"blocked",location:S,proceed(){xl(M,{state:"proceeding",proceed:void 0,reset:void 0,location:S}),B.then(()=>e.history.go(P))},reset(){let W=new Map(y.blockers);W.set(M,Mr),Fe({blockers:W})}});return}return hn(g,S)}),n){Fv(t,re);let h=()=>Iv(t,re);t.addEventListener("pagehide",h),Se=()=>t.removeEventListener("pagehide",h)}return y.initialized||hn(ge.Pop,y.location,{initialHydration:!0}),z}function Zd(){p&&p(),Se&&Se(),w.clear(),D&&D.abort(),y.fetchers.forEach((h,g)=>El(g)),y.blockers.forEach((h,g)=>Fu(g))}function qd(h){return w.add(h),()=>w.delete(h)}function Fe(h,g){g===void 0&&(g={}),y=ue({},y,h);let S=[],P=[];f.v7_fetcherPersist&&y.fetchers.forEach((M,B)=>{M.state==="idle"&&(je.has(B)?P.push(B):S.push(B))}),je.forEach(M=>{!y.fetchers.has(M)&&!N.has(M)&&P.push(M)}),[...w].forEach(M=>M(y,{deletedFetchers:P,viewTransitionOpts:g.viewTransitionOpts,flushSync:g.flushSync===!0})),f.v7_fetcherPersist?(S.forEach(M=>y.fetchers.delete(M)),P.forEach(M=>El(M))):P.forEach(M=>je.delete(M))}function In(h,g,S){var P,M;let{flushSync:B}=S===void 0?{}:S,W=y.actionData!=null&&y.navigation.formMethod!=null&&pt(y.navigation.formMethod)&&y.navigation.state==="loading"&&((P=h.state)==null?void 0:P._isRedirect)!==!0,F;g.actionData?Object.keys(g.actionData).length>0?F=g.actionData:F=null:W?F=y.actionData:F=null;let I=g.loaderData?uc(y.loaderData,g.loaderData,g.matches||[],g.errors):y.loaderData,O=y.blockers;O.size>0&&(O=new Map(O),O.forEach((X,_e)=>O.set(_e,Mr)));let A=H===!0||y.navigation.formMethod!=null&&pt(y.navigation.formMethod)&&((M=h.state)==null?void 0:M._isRedirect)!==!0;a&&(o=a,a=void 0),Pe||C===ge.Pop||(C===ge.Push?e.history.push(h,h.state):C===ge.Replace&&e.history.replace(h,h.state));let Q;if(C===ge.Pop){let X=re.get(y.location.pathname);X&&X.has(h.pathname)?Q={currentLocation:y.location,nextLocation:h}:re.has(h.pathname)&&(Q={currentLocation:h,nextLocation:y.location})}else if(q){let X=re.get(y.location.pathname);X?X.add(h.pathname):(X=new Set([h.pathname]),re.set(y.location.pathname,X)),Q={currentLocation:y.location,nextLocation:h}}Fe(ue({},g,{actionData:F,loaderData:I,historyAction:C,location:h,initialized:!0,navigation:To,revalidation:"idle",restoreScrollPosition:Au(h,g.matches||y.matches),preventScrollReset:A,blockers:O}),{viewTransitionOpts:Q,flushSync:B===!0}),C=ge.Pop,H=!1,q=!1,Pe=!1,ut=!1,Bt=[]}async function Nu(h,g){if(typeof h=="number"){e.history.go(h);return}let S=Pa(y.location,y.matches,u,f.v7_prependBasename,h,f.v7_relativeSplatPath,g==null?void 0:g.fromRouteId,g==null?void 0:g.relative),{path:P,submission:M,error:B}=ec(f.v7_normalizeFormMethod,!1,S,g),W=y.location,F=dl(y.location,P,g&&g.state);F=ue({},F,e.history.encodeLocation(F));let I=g&&g.replace!=null?g.replace:void 0,O=ge.Push;I===!0?O=ge.Replace:I===!1||M!=null&&pt(M.formMethod)&&M.formAction===y.location.pathname+y.location.search&&(O=ge.Replace);let A=g&&"preventScrollReset"in g?g.preventScrollReset===!0:void 0,Q=(g&&g.flushSync)===!0,X=Iu({currentLocation:W,nextLocation:F,historyAction:O});if(X){xl(X,{state:"blocked",location:F,proceed(){xl(X,{state:"proceeding",proceed:void 0,reset:void 0,location:F}),Nu(h,g)},reset(){let _e=new Map(y.blockers);_e.set(X,Mr),Fe({blockers:_e})}});return}return await hn(O,F,{submission:M,pendingError:B,preventScrollReset:A,replace:g&&g.replace,enableViewTransition:g&&g.viewTransition,flushSync:Q})}function bd(){if(qi(),Fe({revalidation:"loading"}),y.navigation.state!=="submitting"){if(y.navigation.state==="idle"){hn(y.historyAction,y.location,{startUninterruptedRevalidation:!0});return}hn(C||y.historyAction,y.navigation.location,{overrideNavigation:y.navigation,enableViewTransition:q===!0})}}async function hn(h,g,S){D&&D.abort(),D=null,C=h,Pe=(S&&S.startUninterruptedRevalidation)===!0,sp(y.location,y.matches),H=(S&&S.preventScrollReset)===!0,q=(S&&S.enableViewTransition)===!0;let P=a||o,M=S&&S.overrideNavigation,B=S!=null&&S.initialHydration&&y.matches&&y.matches.length>0&&!v?y.matches:wn(P,g,u),W=(S&&S.flushSync)===!0;if(B&&y.initialized&&!ut&&Lv(y.location,g)&&!(S&&S.submission&&pt(S.submission.formMethod))){In(g,{matches:B},{flushSync:W});return}let F=kl(B,P,g.pathname);if(F.active&&F.matches&&(B=F.matches),!B){let{error:le,notFoundMatches:Z,route:pe}=bi(g.pathname);In(g,{matches:Z,loaderData:{},errors:{[pe.id]:le}},{flushSync:W});return}D=new AbortController;let I=Vn(e.history,g,D.signal,S&&S.submission),O;if(S&&S.pendingError)O=[Sn(B).route.id,{type:b.error,error:S.pendingError}];else if(S&&S.submission&&pt(S.submission.formMethod)){let le=await ep(I,g,S.submission,B,F.active,{replace:S.replace,flushSync:W});if(le.shortCircuited)return;if(le.pendingActionResult){let[Z,pe]=le.pendingActionResult;if(Ge(pe)&&pl(pe.error)&&pe.error.status===404){D=null,In(g,{matches:le.matches,loaderData:{},errors:{[Z]:pe.error}});return}}B=le.matches||B,O=le.pendingActionResult,M=Do(g,S.submission),W=!1,F.active=!1,I=Vn(e.history,I.url,I.signal)}let{shortCircuited:A,matches:Q,loaderData:X,errors:_e}=await tp(I,g,B,F.active,M,S&&S.submission,S&&S.fetcherSubmission,S&&S.replace,S&&S.initialHydration===!0,W,O);A||(D=null,In(g,ue({matches:Q||B},sc(O),{loaderData:X,errors:_e})))}async function ep(h,g,S,P,M,B){B===void 0&&(B={}),qi();let W=jv(g,S);if(Fe({navigation:W},{flushSync:B.flushSync===!0}),M){let O=await Cl(P,g.pathname,h.signal);if(O.type==="aborted")return{shortCircuited:!0};if(O.type==="error"){let A=Sn(O.partialMatches).route.id;return{matches:O.partialMatches,pendingActionResult:[A,{type:b.error,error:O.error}]}}else if(O.matches)P=O.matches;else{let{notFoundMatches:A,error:Q,route:X}=bi(g.pathname);return{matches:A,pendingActionResult:[X.id,{type:b.error,error:Q}]}}}let F,I=Ar(P,g);if(!I.route.action&&!I.route.lazy)F={type:b.error,error:He(405,{method:h.method,pathname:g.pathname,routeId:I.route.id})};else if(F=(await Sr("action",y,h,[I],P,null))[I.route.id],h.signal.aborted)return{shortCircuited:!0};if(Cn(F)){let O;return B&&B.replace!=null?O=B.replace:O=ic(F.response.headers.get("Location"),new URL(h.url),u,e.history)===y.location.pathname+y.location.search,await mn(h,F,!0,{submission:S,replace:O}),{shortCircuited:!0}}if(qt(F))throw He(400,{type:"defer-action"});if(Ge(F)){let O=Sn(P,I.route.id);return(B&&B.replace)!==!0&&(C=ge.Push),{matches:P,pendingActionResult:[O.route.id,F]}}return{matches:P,pendingActionResult:[I.route.id,F]}}async function tp(h,g,S,P,M,B,W,F,I,O,A){let Q=M||Do(g,B),X=B||W||dc(Q),_e=!Pe&&(!f.v7_partialHydration||!I);if(P){if(_e){let he=Tu(A);Fe(ue({navigation:Q},he!==void 0?{actionData:he}:{}),{flushSync:O})}let J=await Cl(S,g.pathname,h.signal);if(J.type==="aborted")return{shortCircuited:!0};if(J.type==="error"){let he=Sn(J.partialMatches).route.id;return{matches:J.partialMatches,loaderData:{},errors:{[he]:J.error}}}else if(J.matches)S=J.matches;else{let{error:he,notFoundMatches:An,route:kr}=bi(g.pathname);return{matches:An,loaderData:{},errors:{[kr.id]:he}}}}let le=a||o,[Z,pe]=nc(e.history,y,S,X,g,f.v7_partialHydration&&I===!0,f.v7_skipActionErrorRevalidation,ut,Bt,Ht,je,st,ne,le,u,A);if(eo(J=>!(S&&S.some(he=>he.route.id===J))||Z&&Z.some(he=>he.route.id===J)),$=++V,Z.length===0&&pe.length===0){let J=ju();return In(g,ue({matches:S,loaderData:{},errors:A&&Ge(A[1])?{[A[0]]:A[1].error}:null},sc(A),J?{fetchers:new Map(y.fetchers)}:{}),{flushSync:O}),{shortCircuited:!0}}if(_e){let J={};if(!P){J.navigation=Q;let he=Tu(A);he!==void 0&&(J.actionData=he)}pe.length>0&&(J.fetchers=np(pe)),Fe(J,{flushSync:O})}pe.forEach(J=>{Wt(J.key),J.controller&&N.set(J.key,J.controller)});let Un=()=>pe.forEach(J=>Wt(J.key));D&&D.signal.addEventListener("abort",Un);let{loaderResults:Er,fetcherResults:Pt}=await Du(y,S,Z,pe,h);if(h.signal.aborted)return{shortCircuited:!0};D&&D.signal.removeEventListener("abort",Un),pe.forEach(J=>N.delete(J.key));let gt=$l(Er);if(gt)return await mn(h,gt.result,!0,{replace:F}),{shortCircuited:!0};if(gt=$l(Pt),gt)return ne.add(gt.key),await mn(h,gt.result,!0,{replace:F}),{shortCircuited:!0};let{loaderData:to,errors:xr}=ac(y,S,Er,A,pe,Pt,Oe);Oe.forEach((J,he)=>{J.subscribe(An=>{(An||J.done)&&Oe.delete(he)})}),f.v7_partialHydration&&I&&y.errors&&(xr=ue({},y.errors,xr));let vn=ju(),Pl=Ou($),_l=vn||Pl||pe.length>0;return ue({matches:S,loaderData:to,errors:xr},_l?{fetchers:new Map(y.fetchers)}:{})}function Tu(h){if(h&&!Ge(h[1]))return{[h[0]]:h[1].data};if(y.actionData)return Object.keys(y.actionData).length===0?null:y.actionData}function np(h){return h.forEach(g=>{let S=y.fetchers.get(g.key),P=zr(void 0,S?S.data:void 0);y.fetchers.set(g.key,P)}),new Map(y.fetchers)}function rp(h,g,S,P){if(r)throw new Error("router.fetch() was called during the server render, but it shouldn't be. You are likely calling a useFetcher() method in the body of your component. Try moving it to a useEffect or a callback.");Wt(h);let M=(P&&P.flushSync)===!0,B=a||o,W=Pa(y.location,y.matches,u,f.v7_prependBasename,S,f.v7_relativeSplatPath,g,P==null?void 0:P.relative),F=wn(B,W,u),I=kl(F,B,W);if(I.active&&I.matches&&(F=I.matches),!F){Ct(h,g,He(404,{pathname:W}),{flushSync:M});return}let{path:O,submission:A,error:Q}=ec(f.v7_normalizeFormMethod,!0,W,P);if(Q){Ct(h,g,Q,{flushSync:M});return}let X=Ar(F,O),_e=(P&&P.preventScrollReset)===!0;if(A&&pt(A.formMethod)){lp(h,g,O,X,F,I.active,M,_e,A);return}st.set(h,{routeId:g,path:O}),ip(h,g,O,X,F,I.active,M,_e,A)}async function lp(h,g,S,P,M,B,W,F,I){qi(),st.delete(h);function O(ye){if(!ye.route.action&&!ye.route.lazy){let Bn=He(405,{method:I.formMethod,pathname:S,routeId:g});return Ct(h,g,Bn,{flushSync:W}),!0}return!1}if(!B&&O(P))return;let A=y.fetchers.get(h);Vt(h,Ov(I,A),{flushSync:W});let Q=new AbortController,X=Vn(e.history,S,Q.signal,I);if(B){let ye=await Cl(M,new URL(X.url).pathname,X.signal,h);if(ye.type==="aborted")return;if(ye.type==="error"){Ct(h,g,ye.error,{flushSync:W});return}else if(ye.matches){if(M=ye.matches,P=Ar(M,S),O(P))return}else{Ct(h,g,He(404,{pathname:S}),{flushSync:W});return}}N.set(h,Q);let _e=V,Z=(await Sr("action",y,X,[P],M,h))[P.route.id];if(X.signal.aborted){N.get(h)===Q&&N.delete(h);return}if(f.v7_fetcherPersist&&je.has(h)){if(Cn(Z)||Ge(Z)){Vt(h,Qt(void 0));return}}else{if(Cn(Z))if(N.delete(h),$>_e){Vt(h,Qt(void 0));return}else return ne.add(h),Vt(h,zr(I)),mn(X,Z,!1,{fetcherSubmission:I,preventScrollReset:F});if(Ge(Z)){Ct(h,g,Z.error);return}}if(qt(Z))throw He(400,{type:"defer-action"});let pe=y.navigation.location||y.location,Un=Vn(e.history,pe,Q.signal),Er=a||o,Pt=y.navigation.state!=="idle"?wn(Er,y.navigation.location,u):y.matches;K(Pt,"Didn't find any matches after fetcher action");let gt=++V;te.set(h,gt);let to=zr(I,Z.data);y.fetchers.set(h,to);let[xr,vn]=nc(e.history,y,Pt,I,pe,!1,f.v7_skipActionErrorRevalidation,ut,Bt,Ht,je,st,ne,Er,u,[P.route.id,Z]);vn.filter(ye=>ye.key!==h).forEach(ye=>{let Bn=ye.key,Bu=y.fetchers.get(Bn),dp=zr(void 0,Bu?Bu.data:void 0);y.fetchers.set(Bn,dp),Wt(Bn),ye.controller&&N.set(Bn,ye.controller)}),Fe({fetchers:new Map(y.fetchers)});let Pl=()=>vn.forEach(ye=>Wt(ye.key));Q.signal.addEventListener("abort",Pl);let{loaderResults:_l,fetcherResults:J}=await Du(y,Pt,xr,vn,Un);if(Q.signal.aborted)return;Q.signal.removeEventListener("abort",Pl),te.delete(h),N.delete(h),vn.forEach(ye=>N.delete(ye.key));let he=$l(_l);if(he)return mn(Un,he.result,!1,{preventScrollReset:F});if(he=$l(J),he)return ne.add(he.key),mn(Un,he.result,!1,{preventScrollReset:F});let{loaderData:An,errors:kr}=ac(y,Pt,_l,void 0,vn,J,Oe);if(y.fetchers.has(h)){let ye=Qt(Z.data);y.fetchers.set(h,ye)}Ou(gt),y.navigation.state==="loading"&>>$?(K(C,"Expected pending action"),D&&D.abort(),In(y.navigation.location,{matches:Pt,loaderData:An,errors:kr,fetchers:new Map(y.fetchers)})):(Fe({errors:kr,loaderData:uc(y.loaderData,An,Pt,kr),fetchers:new Map(y.fetchers)}),ut=!1)}async function ip(h,g,S,P,M,B,W,F,I){let O=y.fetchers.get(h);Vt(h,zr(I,O?O.data:void 0),{flushSync:W});let A=new AbortController,Q=Vn(e.history,S,A.signal);if(B){let Z=await Cl(M,new URL(Q.url).pathname,Q.signal,h);if(Z.type==="aborted")return;if(Z.type==="error"){Ct(h,g,Z.error,{flushSync:W});return}else if(Z.matches)M=Z.matches,P=Ar(M,S);else{Ct(h,g,He(404,{pathname:S}),{flushSync:W});return}}N.set(h,A);let X=V,le=(await Sr("loader",y,Q,[P],M,h))[P.route.id];if(qt(le)&&(le=await Pu(le,Q.signal,!0)||le),N.get(h)===A&&N.delete(h),!Q.signal.aborted){if(je.has(h)){Vt(h,Qt(void 0));return}if(Cn(le))if($>X){Vt(h,Qt(void 0));return}else{ne.add(h),await mn(Q,le,!1,{preventScrollReset:F});return}if(Ge(le)){Ct(h,g,le.error);return}K(!qt(le),"Unhandled fetcher deferred data"),Vt(h,Qt(le.data))}}async function mn(h,g,S,P){let{submission:M,fetcherSubmission:B,preventScrollReset:W,replace:F}=P===void 0?{}:P;g.response.headers.has("X-Remix-Revalidate")&&(ut=!0);let I=g.response.headers.get("Location");K(I,"Expected a Location header on the redirect Response"),I=ic(I,new URL(h.url),u,e.history);let O=dl(y.location,I,{_isRedirect:!0});if(n){let Z=!1;if(g.response.headers.has("X-Remix-Reload-Document"))Z=!0;else if(Cu.test(I)){const pe=e.history.createURL(I);Z=pe.origin!==t.location.origin||Ft(pe.pathname,u)==null}if(Z){F?t.location.replace(I):t.location.assign(I);return}}D=null;let A=F===!0||g.response.headers.has("X-Remix-Replace")?ge.Replace:ge.Push,{formMethod:Q,formAction:X,formEncType:_e}=y.navigation;!M&&!B&&Q&&X&&_e&&(M=dc(y.navigation));let le=M||B;if(mv.has(g.response.status)&&le&&pt(le.formMethod))await hn(A,O,{submission:ue({},le,{formAction:I}),preventScrollReset:W||H,enableViewTransition:S?q:void 0});else{let Z=Do(O,M);await hn(A,O,{overrideNavigation:Z,fetcherSubmission:B,preventScrollReset:W||H,enableViewTransition:S?q:void 0})}}async function Sr(h,g,S,P,M,B){let W,F={};try{W=await kv(s,h,g,S,P,M,B,i,l)}catch(I){return P.forEach(O=>{F[O.route.id]={type:b.error,error:I}}),F}for(let[I,O]of Object.entries(W))if(Nv(O)){let A=O.result;F[I]={type:b.redirect,response:_v(A,S,I,M,u,f.v7_relativeSplatPath)}}else F[I]=await Pv(O);return F}async function Du(h,g,S,P,M){let B=h.matches,W=Sr("loader",h,M,S,g,null),F=Promise.all(P.map(async A=>{if(A.matches&&A.match&&A.controller){let X=(await Sr("loader",h,Vn(e.history,A.path,A.controller.signal),[A.match],A.matches,A.key))[A.match.route.id];return{[A.key]:X}}else return Promise.resolve({[A.key]:{type:b.error,error:He(404,{pathname:A.path})}})})),I=await W,O=(await F).reduce((A,Q)=>Object.assign(A,Q),{});return await Promise.all([Mv(g,I,M.signal,B,h.loaderData),zv(g,O,P)]),{loaderResults:I,fetcherResults:O}}function qi(){ut=!0,Bt.push(...eo()),st.forEach((h,g)=>{N.has(g)&&Ht.add(g),Wt(g)})}function Vt(h,g,S){S===void 0&&(S={}),y.fetchers.set(h,g),Fe({fetchers:new Map(y.fetchers)},{flushSync:(S&&S.flushSync)===!0})}function Ct(h,g,S,P){P===void 0&&(P={});let M=Sn(y.matches,g);El(h),Fe({errors:{[M.route.id]:S},fetchers:new Map(y.fetchers)},{flushSync:(P&&P.flushSync)===!0})}function Mu(h){return Ye.set(h,(Ye.get(h)||0)+1),je.has(h)&&je.delete(h),y.fetchers.get(h)||vv}function El(h){let g=y.fetchers.get(h);N.has(h)&&!(g&&g.state==="loading"&&te.has(h))&&Wt(h),st.delete(h),te.delete(h),ne.delete(h),f.v7_fetcherPersist&&je.delete(h),Ht.delete(h),y.fetchers.delete(h)}function op(h){let g=(Ye.get(h)||0)-1;g<=0?(Ye.delete(h),je.add(h),f.v7_fetcherPersist||El(h)):Ye.set(h,g),Fe({fetchers:new Map(y.fetchers)})}function Wt(h){let g=N.get(h);g&&(g.abort(),N.delete(h))}function zu(h){for(let g of h){let S=Mu(g),P=Qt(S.data);y.fetchers.set(g,P)}}function ju(){let h=[],g=!1;for(let S of ne){let P=y.fetchers.get(S);K(P,"Expected fetcher: "+S),P.state==="loading"&&(ne.delete(S),h.push(S),g=!0)}return zu(h),g}function Ou(h){let g=[];for(let[S,P]of te)if(P0}function ap(h,g){let S=y.blockers.get(h)||Mr;return tt.get(h)!==g&&tt.set(h,g),S}function Fu(h){y.blockers.delete(h),tt.delete(h)}function xl(h,g){let S=y.blockers.get(h)||Mr;K(S.state==="unblocked"&&g.state==="blocked"||S.state==="blocked"&&g.state==="blocked"||S.state==="blocked"&&g.state==="proceeding"||S.state==="blocked"&&g.state==="unblocked"||S.state==="proceeding"&&g.state==="unblocked","Invalid blocker state transition: "+S.state+" -> "+g.state);let P=new Map(y.blockers);P.set(h,g),Fe({blockers:P})}function Iu(h){let{currentLocation:g,nextLocation:S,historyAction:P}=h;if(tt.size===0)return;tt.size>1&&Mn(!1,"A router only supports one blocker at a time");let M=Array.from(tt.entries()),[B,W]=M[M.length-1],F=y.blockers.get(B);if(!(F&&F.state==="proceeding")&&W({currentLocation:g,nextLocation:S,historyAction:P}))return B}function bi(h){let g=He(404,{pathname:h}),S=a||o,{matches:P,route:M}=cc(S);return eo(),{notFoundMatches:P,route:M,error:g}}function eo(h){let g=[];return Oe.forEach((S,P)=>{(!h||h(P))&&(S.cancel(),g.push(P),Oe.delete(P))}),g}function up(h,g,S){if(x=h,T=g,E=S||null,!m&&y.navigation===To){m=!0;let P=Au(y.location,y.matches);P!=null&&Fe({restoreScrollPosition:P})}return()=>{x=null,T=null,E=null}}function Uu(h,g){return E&&E(h,g.map(P=>Km(P,y.loaderData)))||h.key}function sp(h,g){if(x&&T){let S=Uu(h,g);x[S]=T()}}function Au(h,g){if(x){let S=Uu(h,g),P=x[S];if(typeof P=="number")return P}return null}function kl(h,g,S){if(d)if(h){if(Object.keys(h[0].params).length>0)return{active:!0,matches:li(g,S,u,!0)}}else return{active:!0,matches:li(g,S,u,!0)||[]};return{active:!1,matches:null}}async function Cl(h,g,S,P){if(!d)return{type:"success",matches:h};let M=h;for(;;){let B=a==null,W=a||o,F=i;try{await d({signal:S,path:g,matches:M,fetcherKey:P,patch:(A,Q)=>{S.aborted||lc(A,Q,W,F,l)}})}catch(A){return{type:"error",error:A,partialMatches:M}}finally{B&&!S.aborted&&(o=[...o])}if(S.aborted)return{type:"aborted"};let I=wn(W,g,u);if(I)return{type:"success",matches:I};let O=li(W,g,u,!0);if(!O||M.length===O.length&&M.every((A,Q)=>A.route.id===O[Q].route.id))return{type:"success",matches:null};M=O}}function cp(h){i={},a=Ni(h,l,void 0,i)}function fp(h,g){let S=a==null;lc(h,g,a||o,i,l),S&&(o=[...o],Fe({}))}return z={get basename(){return u},get future(){return f},get state(){return y},get routes(){return o},get window(){return t},initialize:Jd,subscribe:qd,enableScrollRestoration:up,navigate:Nu,fetch:rp,revalidate:bd,createHref:h=>e.history.createHref(h),encodeLocation:h=>e.history.encodeLocation(h),getFetcher:Mu,deleteFetcher:op,dispose:Zd,getBlocker:ap,deleteBlocker:Fu,patchRoutes:fp,_internalFetchControllers:N,_internalActiveDeferreds:Oe,_internalSetRoutes:cp},z}function wv(e){return e!=null&&("formData"in e&&e.formData!=null||"body"in e&&e.body!==void 0)}function Pa(e,t,n,r,l,i,o,a){let u,s;if(o){u=[];for(let f of t)if(u.push(f),f.route.id===o){s=f;break}}else u=t,s=t[t.length-1];let d=Ji(l||".",Gi(u,i),Ft(e.pathname,n)||e.pathname,a==="path");if(l==null&&(d.search=e.search,d.hash=e.hash),(l==null||l===""||l===".")&&s){let f=_u(d.search);if(s.route.index&&!f)d.search=d.search?d.search.replace(/^\?/,"?index&"):"?index";else if(!s.route.index&&f){let p=new URLSearchParams(d.search),w=p.getAll("index");p.delete("index"),w.filter(E=>E).forEach(E=>p.append("index",E));let x=p.toString();d.search=x?"?"+x:""}}return r&&n!=="/"&&(d.pathname=d.pathname==="/"?n:Dt([n,d.pathname])),zn(d)}function ec(e,t,n,r){if(!r||!wv(r))return{path:n};if(r.formMethod&&!Dv(r.formMethod))return{path:n,error:He(405,{method:r.formMethod})};let l=()=>({path:n,error:He(400,{type:"invalid-body"})}),i=r.formMethod||"get",o=e?i.toUpperCase():i.toLowerCase(),a=Bd(n);if(r.body!==void 0){if(r.formEncType==="text/plain"){if(!pt(o))return l();let p=typeof r.body=="string"?r.body:r.body instanceof FormData||r.body instanceof URLSearchParams?Array.from(r.body.entries()).reduce((w,x)=>{let[E,T]=x;return""+w+E+"="+T+` -`},""):String(r.body);return{path:n,submission:{formMethod:o,formAction:a,formEncType:r.formEncType,formData:void 0,json:void 0,text:p}}}else if(r.formEncType==="application/json"){if(!pt(o))return l();try{let p=typeof r.body=="string"?JSON.parse(r.body):r.body;return{path:n,submission:{formMethod:o,formAction:a,formEncType:r.formEncType,formData:void 0,json:p,text:void 0}}}catch{return l()}}}K(typeof FormData=="function","FormData is not available in this environment");let u,s;if(r.formData)u=Ra(r.formData),s=r.formData;else if(r.body instanceof FormData)u=Ra(r.body),s=r.body;else if(r.body instanceof URLSearchParams)u=r.body,s=oc(u);else if(r.body==null)u=new URLSearchParams,s=new FormData;else try{u=new URLSearchParams(r.body),s=oc(u)}catch{return l()}let d={formMethod:o,formAction:a,formEncType:r&&r.formEncType||"application/x-www-form-urlencoded",formData:s,json:void 0,text:void 0};if(pt(d.formMethod))return{path:n,submission:d};let f=pn(n);return t&&f.search&&_u(f.search)&&u.append("index",""),f.search="?"+u,{path:zn(f),submission:d}}function tc(e,t,n){n===void 0&&(n=!1);let r=e.findIndex(l=>l.route.id===t);return r>=0?e.slice(0,n?r+1:r):e}function nc(e,t,n,r,l,i,o,a,u,s,d,f,p,w,x,E){let T=E?Ge(E[1])?E[1].error:E[1].data:void 0,m=e.createURL(t.location),c=e.createURL(l),v=n;i&&t.errors?v=tc(n,Object.keys(t.errors)[0],!0):E&&Ge(E[1])&&(v=tc(n,E[0]));let k=E?E[1].statusCode:void 0,L=o&&k&&k>=400,z=v.filter((C,H)=>{let{route:D}=C;if(D.lazy)return!0;if(D.loader==null)return!1;if(i)return _a(D,t.loaderData,t.errors);if(Sv(t.loaderData,t.matches[H],C)||u.some(Se=>Se===C.route.id))return!0;let q=t.matches[H],re=C;return rc(C,ue({currentUrl:m,currentParams:q.params,nextUrl:c,nextParams:re.params},r,{actionResult:T,actionStatus:k,defaultShouldRevalidate:L?!1:a||m.pathname+m.search===c.pathname+c.search||m.search!==c.search||Ud(q,re)}))}),y=[];return f.forEach((C,H)=>{if(i||!n.some(Pe=>Pe.route.id===C.routeId)||d.has(H))return;let D=wn(w,C.path,x);if(!D){y.push({key:H,routeId:C.routeId,path:C.path,matches:null,match:null,controller:null});return}let q=t.fetchers.get(H),re=Ar(D,C.path),Se=!1;p.has(H)?Se=!1:s.has(H)?(s.delete(H),Se=!0):q&&q.state!=="idle"&&q.data===void 0?Se=a:Se=rc(re,ue({currentUrl:m,currentParams:t.matches[t.matches.length-1].params,nextUrl:c,nextParams:n[n.length-1].params},r,{actionResult:T,actionStatus:k,defaultShouldRevalidate:L?!1:a})),Se&&y.push({key:H,routeId:C.routeId,path:C.path,matches:D,match:re,controller:new AbortController})}),[z,y]}function _a(e,t,n){if(e.lazy)return!0;if(!e.loader)return!1;let r=t!=null&&t[e.id]!==void 0,l=n!=null&&n[e.id]!==void 0;return!r&&l?!1:typeof e.loader=="function"&&e.loader.hydrate===!0?!0:!r&&!l}function Sv(e,t,n){let r=!t||n.route.id!==t.route.id,l=e[n.route.id]===void 0;return r||l}function Ud(e,t){let n=e.route.path;return e.pathname!==t.pathname||n!=null&&n.endsWith("*")&&e.params["*"]!==t.params["*"]}function rc(e,t){if(e.route.shouldRevalidate){let n=e.route.shouldRevalidate(t);if(typeof n=="boolean")return n}return t.defaultShouldRevalidate}function lc(e,t,n,r,l){var i;let o;if(e){let s=r[e];K(s,"No route found to patch children into: routeId = "+e),s.children||(s.children=[]),o=s.children}else o=n;let a=t.filter(s=>!o.some(d=>Ad(s,d))),u=Ni(a,l,[e||"_","patch",String(((i=o)==null?void 0:i.length)||"0")],r);o.push(...u)}function Ad(e,t){return"id"in e&&"id"in t&&e.id===t.id?!0:e.index===t.index&&e.path===t.path&&e.caseSensitive===t.caseSensitive?(!e.children||e.children.length===0)&&(!t.children||t.children.length===0)?!0:e.children.every((n,r)=>{var l;return(l=t.children)==null?void 0:l.some(i=>Ad(n,i))}):!1}async function Ev(e,t,n){if(!e.lazy)return;let r=await e.lazy();if(!e.lazy)return;let l=n[e.id];K(l,"No route found in manifest");let i={};for(let o in r){let u=l[o]!==void 0&&o!=="hasErrorBoundary";Mn(!u,'Route "'+l.id+'" has a static property "'+o+'" defined but its lazy function is also returning a value for this property. '+('The lazy route property "'+o+'" will be ignored.')),!u&&!$m.has(o)&&(i[o]=r[o])}Object.assign(l,i),Object.assign(l,ue({},t(l),{lazy:void 0}))}async function xv(e){let{matches:t}=e,n=t.filter(l=>l.shouldLoad);return(await Promise.all(n.map(l=>l.resolve()))).reduce((l,i,o)=>Object.assign(l,{[n[o].route.id]:i}),{})}async function kv(e,t,n,r,l,i,o,a,u,s){let d=i.map(w=>w.route.lazy?Ev(w.route,u,a):void 0),f=i.map((w,x)=>{let E=d[x],T=l.some(c=>c.route.id===w.route.id);return ue({},w,{shouldLoad:T,resolve:async c=>(c&&r.method==="GET"&&(w.route.lazy||w.route.loader)&&(T=!0),T?Cv(t,r,w,E,c,s):Promise.resolve({type:b.data,result:void 0}))})}),p=await e({matches:f,request:r,params:i[0].params,fetcherKey:o,context:s});try{await Promise.all(d)}catch{}return p}async function Cv(e,t,n,r,l,i){let o,a,u=s=>{let d,f=new Promise((x,E)=>d=E);a=()=>d(),t.signal.addEventListener("abort",a);let p=x=>typeof s!="function"?Promise.reject(new Error("You cannot call the handler for a route which defines a boolean "+('"'+e+'" [routeId: '+n.route.id+"]"))):s({request:t,params:n.params,context:i},...x!==void 0?[x]:[]),w=(async()=>{try{return{type:"data",result:await(l?l(E=>p(E)):p())}}catch(x){return{type:"error",result:x}}})();return Promise.race([w,f])};try{let s=n.route[e];if(r)if(s){let d,[f]=await Promise.all([u(s).catch(p=>{d=p}),r]);if(d!==void 0)throw d;o=f}else if(await r,s=n.route[e],s)o=await u(s);else if(e==="action"){let d=new URL(t.url),f=d.pathname+d.search;throw He(405,{method:t.method,pathname:f,routeId:n.route.id})}else return{type:b.data,result:void 0};else if(s)o=await u(s);else{let d=new URL(t.url),f=d.pathname+d.search;throw He(404,{pathname:f})}K(o.result!==void 0,"You defined "+(e==="action"?"an action":"a loader")+" for route "+('"'+n.route.id+"\" but didn't return anything from your `"+e+"` ")+"function. Please return a value or `null`.")}catch(s){return{type:b.error,result:s}}finally{a&&t.signal.removeEventListener("abort",a)}return o}async function Pv(e){let{result:t,type:n}=e;if(Hd(t)){let f;try{let p=t.headers.get("Content-Type");p&&/\bapplication\/json\b/.test(p)?t.body==null?f=null:f=await t.json():f=await t.text()}catch(p){return{type:b.error,error:p}}return n===b.error?{type:b.error,error:new Di(t.status,t.statusText,f),statusCode:t.status,headers:t.headers}:{type:b.data,data:f,statusCode:t.status,headers:t.headers}}if(n===b.error){if(fc(t)){var r,l;if(t.data instanceof Error){var i,o;return{type:b.error,error:t.data,statusCode:(i=t.init)==null?void 0:i.status,headers:(o=t.init)!=null&&o.headers?new Headers(t.init.headers):void 0}}return{type:b.error,error:new Di(((r=t.init)==null?void 0:r.status)||500,void 0,t.data),statusCode:pl(t)?t.status:void 0,headers:(l=t.init)!=null&&l.headers?new Headers(t.init.headers):void 0}}return{type:b.error,error:t,statusCode:pl(t)?t.status:void 0}}if(Tv(t)){var a,u;return{type:b.deferred,deferredData:t,statusCode:(a=t.init)==null?void 0:a.status,headers:((u=t.init)==null?void 0:u.headers)&&new Headers(t.init.headers)}}if(fc(t)){var s,d;return{type:b.data,data:t.data,statusCode:(s=t.init)==null?void 0:s.status,headers:(d=t.init)!=null&&d.headers?new Headers(t.init.headers):void 0}}return{type:b.data,data:t}}function _v(e,t,n,r,l,i){let o=e.headers.get("Location");if(K(o,"Redirects returned/thrown from loaders/actions must have a Location header"),!Cu.test(o)){let a=r.slice(0,r.findIndex(u=>u.route.id===n)+1);o=Pa(new URL(t.url),a,l,!0,o,i),e.headers.set("Location",o)}return e}function ic(e,t,n,r){let l=["about:","blob:","chrome:","chrome-untrusted:","content:","data:","devtools:","file:","filesystem:","javascript:"];if(Cu.test(e)){let i=e,o=i.startsWith("//")?new URL(t.protocol+i):new URL(i);if(l.includes(o.protocol))throw new Error("Invalid redirect location");let a=Ft(o.pathname,n)!=null;if(o.origin===t.origin&&a)return o.pathname+o.search+o.hash}try{let i=r.createURL(e);if(l.includes(i.protocol))throw new Error("Invalid redirect location")}catch{}return e}function Vn(e,t,n,r){let l=e.createURL(Bd(t)).toString(),i={signal:n};if(r&&pt(r.formMethod)){let{formMethod:o,formEncType:a}=r;i.method=o.toUpperCase(),a==="application/json"?(i.headers=new Headers({"Content-Type":a}),i.body=JSON.stringify(r.json)):a==="text/plain"?i.body=r.text:a==="application/x-www-form-urlencoded"&&r.formData?i.body=Ra(r.formData):i.body=r.formData}return new Request(l,i)}function Ra(e){let t=new URLSearchParams;for(let[n,r]of e.entries())t.append(n,typeof r=="string"?r:r.name);return t}function oc(e){let t=new FormData;for(let[n,r]of e.entries())t.append(n,r);return t}function Rv(e,t,n,r,l){let i={},o=null,a,u=!1,s={},d=n&&Ge(n[1])?n[1].error:void 0;return e.forEach(f=>{if(!(f.route.id in t))return;let p=f.route.id,w=t[p];if(K(!Cn(w),"Cannot handle redirect results in processLoaderData"),Ge(w)){let x=w.error;d!==void 0&&(x=d,d=void 0),o=o||{};{let E=Sn(e,p);o[E.route.id]==null&&(o[E.route.id]=x)}i[p]=void 0,u||(u=!0,a=pl(w.error)?w.error.status:500),w.headers&&(s[p]=w.headers)}else qt(w)?(r.set(p,w.deferredData),i[p]=w.deferredData.data,w.statusCode!=null&&w.statusCode!==200&&!u&&(a=w.statusCode),w.headers&&(s[p]=w.headers)):(i[p]=w.data,w.statusCode&&w.statusCode!==200&&!u&&(a=w.statusCode),w.headers&&(s[p]=w.headers))}),d!==void 0&&n&&(o={[n[0]]:d},i[n[0]]=void 0),{loaderData:i,errors:o,statusCode:a||200,loaderHeaders:s}}function ac(e,t,n,r,l,i,o){let{loaderData:a,errors:u}=Rv(t,n,r,o);return l.forEach(s=>{let{key:d,match:f,controller:p}=s,w=i[d];if(K(w,"Did not find corresponding fetcher result"),!(p&&p.signal.aborted))if(Ge(w)){let x=Sn(e.matches,f==null?void 0:f.route.id);u&&u[x.route.id]||(u=ue({},u,{[x.route.id]:w.error})),e.fetchers.delete(d)}else if(Cn(w))K(!1,"Unhandled fetcher revalidation redirect");else if(qt(w))K(!1,"Unhandled fetcher deferred data");else{let x=Qt(w.data);e.fetchers.set(d,x)}}),{loaderData:a,errors:u}}function uc(e,t,n,r){let l=ue({},t);for(let i of n){let o=i.route.id;if(t.hasOwnProperty(o)?t[o]!==void 0&&(l[o]=t[o]):e[o]!==void 0&&i.route.loader&&(l[o]=e[o]),r&&r.hasOwnProperty(o))break}return l}function sc(e){return e?Ge(e[1])?{actionData:{}}:{actionData:{[e[0]]:e[1].data}}:{}}function Sn(e,t){return(t?e.slice(0,e.findIndex(r=>r.route.id===t)+1):[...e]).reverse().find(r=>r.route.hasErrorBoundary===!0)||e[0]}function cc(e){let t=e.length===1?e[0]:e.find(n=>n.index||!n.path||n.path==="/")||{id:"__shim-error-route__"};return{matches:[{params:{},pathname:"",pathnameBase:"",route:t}],route:t}}function He(e,t){let{pathname:n,routeId:r,method:l,type:i,message:o}=t===void 0?{}:t,a="Unknown Server Error",u="Unknown @remix-run/router error";return e===400?(a="Bad Request",l&&n&&r?u="You made a "+l+' request to "'+n+'" but '+('did not provide a `loader` for route "'+r+'", ')+"so there is no way to handle the request.":i==="defer-action"?u="defer() is not supported in actions":i==="invalid-body"&&(u="Unable to encode submission body")):e===403?(a="Forbidden",u='Route "'+r+'" does not match URL "'+n+'"'):e===404?(a="Not Found",u='No route matches URL "'+n+'"'):e===405&&(a="Method Not Allowed",l&&n&&r?u="You made a "+l.toUpperCase()+' request to "'+n+'" but '+('did not provide an `action` for route "'+r+'", ')+"so there is no way to handle the request.":l&&(u='Invalid request method "'+l.toUpperCase()+'"')),new Di(e||500,a,new Error(u),!0)}function $l(e){let t=Object.entries(e);for(let n=t.length-1;n>=0;n--){let[r,l]=t[n];if(Cn(l))return{key:r,result:l}}}function Bd(e){let t=typeof e=="string"?pn(e):e;return zn(ue({},t,{hash:""}))}function Lv(e,t){return e.pathname!==t.pathname||e.search!==t.search?!1:e.hash===""?t.hash!=="":e.hash===t.hash?!0:t.hash!==""}function Nv(e){return Hd(e.result)&&hv.has(e.result.status)}function qt(e){return e.type===b.deferred}function Ge(e){return e.type===b.error}function Cn(e){return(e&&e.type)===b.redirect}function fc(e){return typeof e=="object"&&e!=null&&"type"in e&&"data"in e&&"init"in e&&e.type==="DataWithResponseInit"}function Tv(e){let t=e;return t&&typeof t=="object"&&typeof t.data=="object"&&typeof t.subscribe=="function"&&typeof t.cancel=="function"&&typeof t.resolveData=="function"}function Hd(e){return e!=null&&typeof e.status=="number"&&typeof e.statusText=="string"&&typeof e.headers=="object"&&typeof e.body<"u"}function Dv(e){return pv.has(e.toLowerCase())}function pt(e){return fv.has(e.toLowerCase())}async function Mv(e,t,n,r,l){let i=Object.entries(t);for(let o=0;o(p==null?void 0:p.route.id)===a);if(!s)continue;let d=r.find(p=>p.route.id===s.route.id),f=d!=null&&!Ud(d,s)&&(l&&l[s.route.id])!==void 0;qt(u)&&f&&await Pu(u,n,!1).then(p=>{p&&(t[a]=p)})}}async function zv(e,t,n){for(let r=0;r(s==null?void 0:s.route.id)===i)&&qt(a)&&(K(o,"Expected an AbortController for revalidating fetcher deferred result"),await Pu(a,o.signal,!0).then(s=>{s&&(t[l]=s)}))}}async function Pu(e,t,n){if(n===void 0&&(n=!1),!await e.deferredData.resolveData(t)){if(n)try{return{type:b.data,data:e.deferredData.unwrappedData}}catch(l){return{type:b.error,error:l}}return{type:b.data,data:e.deferredData.data}}}function _u(e){return new URLSearchParams(e).getAll("index").some(t=>t==="")}function Ar(e,t){let n=typeof t=="string"?pn(t).search:t.search;if(e[e.length-1].route.index&&_u(n||""))return e[e.length-1];let r=Od(e);return r[r.length-1]}function dc(e){let{formMethod:t,formAction:n,formEncType:r,text:l,formData:i,json:o}=e;if(!(!t||!n||!r)){if(l!=null)return{formMethod:t,formAction:n,formEncType:r,formData:void 0,json:void 0,text:l};if(i!=null)return{formMethod:t,formAction:n,formEncType:r,formData:i,json:void 0,text:void 0};if(o!==void 0)return{formMethod:t,formAction:n,formEncType:r,formData:void 0,json:o,text:void 0}}}function Do(e,t){return t?{state:"loading",location:e,formMethod:t.formMethod,formAction:t.formAction,formEncType:t.formEncType,formData:t.formData,json:t.json,text:t.text}:{state:"loading",location:e,formMethod:void 0,formAction:void 0,formEncType:void 0,formData:void 0,json:void 0,text:void 0}}function jv(e,t){return{state:"submitting",location:e,formMethod:t.formMethod,formAction:t.formAction,formEncType:t.formEncType,formData:t.formData,json:t.json,text:t.text}}function zr(e,t){return e?{state:"loading",formMethod:e.formMethod,formAction:e.formAction,formEncType:e.formEncType,formData:e.formData,json:e.json,text:e.text,data:t}:{state:"loading",formMethod:void 0,formAction:void 0,formEncType:void 0,formData:void 0,json:void 0,text:void 0,data:t}}function Ov(e,t){return{state:"submitting",formMethod:e.formMethod,formAction:e.formAction,formEncType:e.formEncType,formData:e.formData,json:e.json,text:e.text,data:t?t.data:void 0}}function Qt(e){return{state:"idle",formMethod:void 0,formAction:void 0,formEncType:void 0,formData:void 0,json:void 0,text:void 0,data:e}}function Fv(e,t){try{let n=e.sessionStorage.getItem(Id);if(n){let r=JSON.parse(n);for(let[l,i]of Object.entries(r||{}))i&&Array.isArray(i)&&t.set(l,new Set(i||[]))}}catch{}}function Iv(e,t){if(t.size>0){let n={};for(let[r,l]of t)n[r]=[...l];try{e.sessionStorage.setItem(Id,JSON.stringify(n))}catch(r){Mn(!1,"Failed to save applied view transitions in sessionStorage ("+r+").")}}}/** - * React Router v6.30.3 - * - * Copyright (c) Remix Software Inc. - * - * This source code is licensed under the MIT license found in the - * LICENSE.md file in the root directory of this source tree. - * - * @license MIT - */function Mi(){return Mi=Object.assign?Object.assign.bind():function(e){for(var t=1;t{a.current=!0}),_.useCallback(function(s,d){if(d===void 0&&(d={}),!a.current)return;if(typeof s=="number"){r.go(s);return}let f=Ji(s,JSON.parse(o),i,d.relative==="path");e==null&&t!=="/"&&(f.pathname=f.pathname==="/"?t:Dt([t,f.pathname])),(d.replace?r.replace:r.push)(f,d.state,d)},[t,r,o,i,e])}const Bv=_.createContext(null);function Hv(e){let t=_.useContext(At).outlet;return t&&_.createElement(Bv.Provider,{value:e},t)}function Zi(e,t){let{relative:n}=t===void 0?{}:t,{future:r}=_.useContext(Ut),{matches:l}=_.useContext(At),{pathname:i}=wr(),o=JSON.stringify(Gi(l,r.v7_relativeSplatPath));return _.useMemo(()=>Ji(e,JSON.parse(o),i,n==="path"),[e,o,i,n])}function Vv(e,t,n,r){gr()||K(!1);let{navigator:l}=_.useContext(Ut),{matches:i}=_.useContext(At),o=i[i.length-1],a=o?o.params:{};o&&o.pathname;let u=o?o.pathnameBase:"/";o&&o.route;let s=wr(),d;d=s;let f=d.pathname||"/",p=f;if(u!=="/"){let E=u.replace(/^\//,"").split("/");p="/"+f.replace(/^\//,"").split("/").slice(E.length).join("/")}let w=wn(e,{pathname:p});return Yv(w&&w.map(E=>Object.assign({},E,{params:Object.assign({},a,E.params),pathname:Dt([u,l.encodeLocation?l.encodeLocation(E.pathname).pathname:E.pathname]),pathnameBase:E.pathnameBase==="/"?u:Dt([u,l.encodeLocation?l.encodeLocation(E.pathnameBase).pathname:E.pathnameBase])})),i,n,r)}function Wv(){let e=Zv(),t=pl(e)?e.status+" "+e.statusText:e instanceof Error?e.message:JSON.stringify(e),n=e instanceof Error?e.stack:null,l={padding:"0.5rem",backgroundColor:"rgba(200,200,200, 0.5)"};return _.createElement(_.Fragment,null,_.createElement("h2",null,"Unexpected Application Error!"),_.createElement("h3",{style:{fontStyle:"italic"}},t),n?_.createElement("pre",{style:l},n):null,null)}const $v=_.createElement(Wv,null);class Qv extends _.Component{constructor(t){super(t),this.state={location:t.location,revalidation:t.revalidation,error:t.error}}static getDerivedStateFromError(t){return{error:t}}static getDerivedStateFromProps(t,n){return n.location!==t.location||n.revalidation!=="idle"&&t.revalidation==="idle"?{error:t.error,location:t.location,revalidation:t.revalidation}:{error:t.error!==void 0?t.error:n.error,location:n.location,revalidation:t.revalidation||n.revalidation}}componentDidCatch(t,n){console.error("React Router caught the following error during render",t,n)}render(){return this.state.error!==void 0?_.createElement(At.Provider,{value:this.props.routeContext},_.createElement(Vd.Provider,{value:this.state.error,children:this.props.component})):this.props.children}}function Kv(e){let{routeContext:t,match:n,children:r}=e,l=_.useContext(wl);return l&&l.static&&l.staticContext&&(n.route.errorElement||n.route.ErrorBoundary)&&(l.staticContext._deepestRenderedBoundaryId=n.route.id),_.createElement(At.Provider,{value:t},r)}function Yv(e,t,n,r){var l;if(t===void 0&&(t=[]),n===void 0&&(n=null),r===void 0&&(r=null),e==null){var i;if(!n)return null;if(n.errors)e=n.matches;else if((i=r)!=null&&i.v7_partialHydration&&t.length===0&&!n.initialized&&n.matches.length>0)e=n.matches;else return null}let o=e,a=(l=n)==null?void 0:l.errors;if(a!=null){let d=o.findIndex(f=>f.route.id&&(a==null?void 0:a[f.route.id])!==void 0);d>=0||K(!1),o=o.slice(0,Math.min(o.length,d+1))}let u=!1,s=-1;if(n&&r&&r.v7_partialHydration)for(let d=0;d=0?o=o.slice(0,s+1):o=[o[0]];break}}}return o.reduceRight((d,f,p)=>{let w,x=!1,E=null,T=null;n&&(w=a&&f.route.id?a[f.route.id]:void 0,E=f.route.errorElement||$v,u&&(s<0&&p===0?(bv("route-fallback"),x=!0,T=null):s===p&&(x=!0,T=f.route.hydrateFallbackElement||null)));let m=t.concat(o.slice(0,p+1)),c=()=>{let v;return w?v=E:x?v=T:f.route.Component?v=_.createElement(f.route.Component,null):f.route.element?v=f.route.element:v=d,_.createElement(Kv,{match:f,routeContext:{outlet:d,matches:m,isDataRoute:n!=null},children:v})};return n&&(f.route.ErrorBoundary||f.route.errorElement||p===0)?_.createElement(Qv,{location:n.location,revalidation:n.revalidation,component:E,error:w,children:c(),routeContext:{outlet:null,matches:m,isDataRoute:!0}}):c()},null)}var Qd=function(e){return e.UseBlocker="useBlocker",e.UseRevalidator="useRevalidator",e.UseNavigateStable="useNavigate",e}(Qd||{}),Kd=function(e){return e.UseBlocker="useBlocker",e.UseLoaderData="useLoaderData",e.UseActionData="useActionData",e.UseRouteError="useRouteError",e.UseNavigation="useNavigation",e.UseRouteLoaderData="useRouteLoaderData",e.UseMatches="useMatches",e.UseRevalidator="useRevalidator",e.UseNavigateStable="useNavigate",e.UseRouteId="useRouteId",e}(Kd||{});function Xv(e){let t=_.useContext(wl);return t||K(!1),t}function Gv(e){let t=_.useContext(Ru);return t||K(!1),t}function Jv(e){let t=_.useContext(At);return t||K(!1),t}function Yd(e){let t=Jv(),n=t.matches[t.matches.length-1];return n.route.id||K(!1),n.route.id}function Zv(){var e;let t=_.useContext(Vd),n=Gv(Kd.UseRouteError),r=Yd();return t!==void 0?t:(e=n.errors)==null?void 0:e[r]}function qv(){let{router:e}=Xv(Qd.UseNavigateStable),t=Yd(),n=_.useRef(!1);return Wd(()=>{n.current=!0}),_.useCallback(function(l,i){i===void 0&&(i={}),n.current&&(typeof l=="number"?e.navigate(l):e.navigate(l,Mi({fromRouteId:t},i)))},[e,t])}const pc={};function bv(e,t,n){pc[e]||(pc[e]=!0)}function ey(e,t){e==null||e.v7_startTransition,(e==null?void 0:e.v7_relativeSplatPath)===void 0&&(!t||t.v7_relativeSplatPath),t&&(t.v7_fetcherPersist,t.v7_normalizeFormMethod,t.v7_partialHydration,t.v7_skipActionErrorRevalidation)}function ty(e){let{to:t,replace:n,state:r,relative:l}=e;gr()||K(!1);let{future:i,static:o}=_.useContext(Ut),{matches:a}=_.useContext(At),{pathname:u}=wr(),s=$d(),d=Ji(t,Gi(a,i.v7_relativeSplatPath),u,l==="path"),f=JSON.stringify(d);return _.useEffect(()=>s(JSON.parse(f),{replace:n,state:r,relative:l}),[s,f,l,n,r]),null}function ny(e){return Hv(e.context)}function ry(e){let{basename:t="/",children:n=null,location:r,navigationType:l=ge.Pop,navigator:i,static:o=!1,future:a}=e;gr()&&K(!1);let u=t.replace(/^\/*/,"/"),s=_.useMemo(()=>({basename:u,navigator:i,static:o,future:Mi({v7_relativeSplatPath:!1},a)}),[u,a,i,o]);typeof r=="string"&&(r=pn(r));let{pathname:d="/",search:f="",hash:p="",state:w=null,key:x="default"}=r,E=_.useMemo(()=>{let T=Ft(d,u);return T==null?null:{location:{pathname:T,search:f,hash:p,state:w,key:x},navigationType:l}},[u,d,f,p,w,x,l]);return E==null?null:_.createElement(Ut.Provider,{value:s},_.createElement(Lu.Provider,{children:n,value:E}))}new Promise(()=>{});function ly(e){let t={hasErrorBoundary:e.ErrorBoundary!=null||e.errorElement!=null};return e.Component&&Object.assign(t,{element:_.createElement(e.Component),Component:void 0}),e.HydrateFallback&&Object.assign(t,{hydrateFallbackElement:_.createElement(e.HydrateFallback),HydrateFallback:void 0}),e.ErrorBoundary&&Object.assign(t,{errorElement:_.createElement(e.ErrorBoundary),ErrorBoundary:void 0}),t}/** - * React Router DOM v6.30.3 - * - * Copyright (c) Remix Software Inc. - * - * This source code is licensed under the MIT license found in the - * LICENSE.md file in the root directory of this source tree. - * - * @license MIT - */function hr(){return hr=Object.assign?Object.assign.bind():function(e){for(var t=1;t=0)&&(n[l]=e[l]);return n}function iy(e){return!!(e.metaKey||e.altKey||e.ctrlKey||e.shiftKey)}function oy(e,t){return e.button===0&&(!t||t==="_self")&&!iy(e)}const ay=["onClick","relative","reloadDocument","replace","state","target","to","preventScrollReset","viewTransition"],uy=["aria-current","caseSensitive","className","end","style","to","viewTransition","children"],sy="6";try{window.__reactRouterVersion=sy}catch{}function cy(e,t){return gv({basename:void 0,future:hr({},void 0,{v7_prependBasename:!0}),history:Hm({window:void 0}),hydrationData:fy(),routes:e,mapRouteProperties:ly,dataStrategy:void 0,patchRoutesOnNavigation:void 0,window:void 0}).initialize()}function fy(){var e;let t=(e=window)==null?void 0:e.__staticRouterHydrationData;return t&&t.errors&&(t=hr({},t,{errors:dy(t.errors)})),t}function dy(e){if(!e)return null;let t=Object.entries(e),n={};for(let[r,l]of t)if(l&&l.__type==="RouteErrorResponse")n[r]=new Di(l.status,l.statusText,l.data,l.internal===!0);else if(l&&l.__type==="Error"){if(l.__subType){let i=window[l.__subType];if(typeof i=="function")try{let o=new i(l.message);o.stack="",n[r]=o}catch{}}if(n[r]==null){let i=new Error(l.message);i.stack="",n[r]=i}}else n[r]=l;return n}const Gd=_.createContext({isTransitioning:!1}),py=_.createContext(new Map),hy="startTransition",hc=Lp[hy],my="flushSync",mc=Bm[my];function vy(e){hc?hc(e):e()}function jr(e){mc?mc(e):e()}class yy{constructor(){this.status="pending",this.promise=new Promise((t,n)=>{this.resolve=r=>{this.status==="pending"&&(this.status="resolved",t(r))},this.reject=r=>{this.status==="pending"&&(this.status="rejected",n(r))}})}}function gy(e){let{fallbackElement:t,router:n,future:r}=e,[l,i]=_.useState(n.state),[o,a]=_.useState(),[u,s]=_.useState({isTransitioning:!1}),[d,f]=_.useState(),[p,w]=_.useState(),[x,E]=_.useState(),T=_.useRef(new Map),{v7_startTransition:m}=r||{},c=_.useCallback(C=>{m?vy(C):C()},[m]),v=_.useCallback((C,H)=>{let{deletedFetchers:D,flushSync:q,viewTransitionOpts:re}=H;C.fetchers.forEach((Pe,ut)=>{Pe.data!==void 0&&T.current.set(ut,Pe.data)}),D.forEach(Pe=>T.current.delete(Pe));let Se=n.window==null||n.window.document==null||typeof n.window.document.startViewTransition!="function";if(!re||Se){q?jr(()=>i(C)):c(()=>i(C));return}if(q){jr(()=>{p&&(d&&d.resolve(),p.skipTransition()),s({isTransitioning:!0,flushSync:!0,currentLocation:re.currentLocation,nextLocation:re.nextLocation})});let Pe=n.window.document.startViewTransition(()=>{jr(()=>i(C))});Pe.finished.finally(()=>{jr(()=>{f(void 0),w(void 0),a(void 0),s({isTransitioning:!1})})}),jr(()=>w(Pe));return}p?(d&&d.resolve(),p.skipTransition(),E({state:C,currentLocation:re.currentLocation,nextLocation:re.nextLocation})):(a(C),s({isTransitioning:!0,flushSync:!1,currentLocation:re.currentLocation,nextLocation:re.nextLocation}))},[n.window,p,d,T,c]);_.useLayoutEffect(()=>n.subscribe(v),[n,v]),_.useEffect(()=>{u.isTransitioning&&!u.flushSync&&f(new yy)},[u]),_.useEffect(()=>{if(d&&o&&n.window){let C=o,H=d.promise,D=n.window.document.startViewTransition(async()=>{c(()=>i(C)),await H});D.finished.finally(()=>{f(void 0),w(void 0),a(void 0),s({isTransitioning:!1})}),w(D)}},[c,o,d,n.window]),_.useEffect(()=>{d&&o&&l.location.key===o.location.key&&d.resolve()},[d,p,l.location,o]),_.useEffect(()=>{!u.isTransitioning&&x&&(a(x.state),s({isTransitioning:!0,flushSync:!1,currentLocation:x.currentLocation,nextLocation:x.nextLocation}),E(void 0))},[u.isTransitioning,x]),_.useEffect(()=>{},[]);let k=_.useMemo(()=>({createHref:n.createHref,encodeLocation:n.encodeLocation,go:C=>n.navigate(C),push:(C,H,D)=>n.navigate(C,{state:H,preventScrollReset:D==null?void 0:D.preventScrollReset}),replace:(C,H,D)=>n.navigate(C,{replace:!0,state:H,preventScrollReset:D==null?void 0:D.preventScrollReset})}),[n]),L=n.basename||"/",z=_.useMemo(()=>({router:n,navigator:k,static:!1,basename:L}),[n,k,L]),y=_.useMemo(()=>({v7_relativeSplatPath:n.future.v7_relativeSplatPath}),[n.future.v7_relativeSplatPath]);return _.useEffect(()=>ey(r,n.future),[r,n.future]),_.createElement(_.Fragment,null,_.createElement(wl.Provider,{value:z},_.createElement(Ru.Provider,{value:l},_.createElement(py.Provider,{value:T.current},_.createElement(Gd.Provider,{value:u},_.createElement(ry,{basename:L,location:l.location,navigationType:l.historyAction,navigator:k,future:y},l.initialized||n.future.v7_partialHydration?_.createElement(wy,{routes:n.routes,future:n.future,state:l}):t))))),null)}const wy=_.memo(Sy);function Sy(e){let{routes:t,future:n,state:r}=e;return Vv(t,void 0,r,n)}const Ey=typeof window<"u"&&typeof window.document<"u"&&typeof window.document.createElement<"u",xy=/^(?:[a-z][a-z0-9+.-]*:|\/\/)/i,ky=_.forwardRef(function(t,n){let{onClick:r,relative:l,reloadDocument:i,replace:o,state:a,target:u,to:s,preventScrollReset:d,viewTransition:f}=t,p=Xd(t,ay),{basename:w}=_.useContext(Ut),x,E=!1;if(typeof s=="string"&&xy.test(s)&&(x=s,Ey))try{let v=new URL(window.location.href),k=s.startsWith("//")?new URL(v.protocol+s):new URL(s),L=Ft(k.pathname,w);k.origin===v.origin&&L!=null?s=L+k.search+k.hash:E=!0}catch{}let T=Uv(s,{relative:l}),m=_y(s,{replace:o,state:a,target:u,preventScrollReset:d,relative:l,viewTransition:f});function c(v){r&&r(v),v.defaultPrevented||m(v)}return _.createElement("a",hr({},p,{href:x||T,onClick:E||i?r:c,ref:n,target:u}))}),Cy=_.forwardRef(function(t,n){let{"aria-current":r="page",caseSensitive:l=!1,className:i="",end:o=!1,style:a,to:u,viewTransition:s,children:d}=t,f=Xd(t,uy),p=Zi(u,{relative:f.relative}),w=wr(),x=_.useContext(Ru),{navigator:E,basename:T}=_.useContext(Ut),m=x!=null&&Ry(p)&&s===!0,c=E.encodeLocation?E.encodeLocation(p).pathname:p.pathname,v=w.pathname,k=x&&x.navigation&&x.navigation.location?x.navigation.location.pathname:null;l||(v=v.toLowerCase(),k=k?k.toLowerCase():null,c=c.toLowerCase()),k&&T&&(k=Ft(k,T)||k);const L=c!=="/"&&c.endsWith("/")?c.length-1:c.length;let z=v===c||!o&&v.startsWith(c)&&v.charAt(L)==="/",y=k!=null&&(k===c||!o&&k.startsWith(c)&&k.charAt(c.length)==="/"),C={isActive:z,isPending:y,isTransitioning:m},H=z?r:void 0,D;typeof i=="function"?D=i(C):D=[i,z?"active":null,y?"pending":null,m?"transitioning":null].filter(Boolean).join(" ");let q=typeof a=="function"?a(C):a;return _.createElement(ky,hr({},f,{"aria-current":H,className:D,ref:n,style:q,to:u,viewTransition:s}),typeof d=="function"?d(C):d)});var La;(function(e){e.UseScrollRestoration="useScrollRestoration",e.UseSubmit="useSubmit",e.UseSubmitFetcher="useSubmitFetcher",e.UseFetcher="useFetcher",e.useViewTransitionState="useViewTransitionState"})(La||(La={}));var vc;(function(e){e.UseFetcher="useFetcher",e.UseFetchers="useFetchers",e.UseScrollRestoration="useScrollRestoration"})(vc||(vc={}));function Py(e){let t=_.useContext(wl);return t||K(!1),t}function _y(e,t){let{target:n,replace:r,state:l,preventScrollReset:i,relative:o,viewTransition:a}=t===void 0?{}:t,u=$d(),s=wr(),d=Zi(e,{relative:o});return _.useCallback(f=>{if(oy(f,n)){f.preventDefault();let p=r!==void 0?r:zn(s)===zn(d);u(e,{replace:p,state:l,preventScrollReset:i,relative:o,viewTransition:a})}},[s,u,d,r,l,n,e,i,o,a])}function Ry(e,t){t===void 0&&(t={});let n=_.useContext(Gd);n==null&&K(!1);let{basename:r}=Py(La.useViewTransitionState),l=Zi(e,{relative:t.relative});if(!n.isTransitioning)return!1;let i=Ft(n.currentLocation.pathname,r)||n.currentLocation.pathname,o=Ft(n.nextLocation.pathname,r)||n.nextLocation.pathname;return Ti(l.pathname,o)!=null||Ti(l.pathname,i)!=null}function Fn(e){return U.jsxs("section",{className:"page",children:[U.jsx("h1",{children:e.title}),U.jsx("p",{children:e.subtitle}),e.children]})}function Ql(e){return U.jsxs("div",{className:"card",children:[U.jsx("h3",{children:e.title}),e.children]})}function Ly(){return U.jsx(Fn,{title:"Dashboard",subtitle:"High-level operational overview for Osham.",children:U.jsxs("div",{className:"card-grid",children:[U.jsx(Ql,{title:"Service Health",children:U.jsxs("p",{children:["Wire to ",U.jsx("code",{children:"/__osham/admin/health"}),"."]})}),U.jsx(Ql,{title:"Startup Summary",children:U.jsxs("p",{children:["Wire to ",U.jsx("code",{children:"/__osham/admin/startup-summary"}),"."]})}),U.jsx(Ql,{title:"Metrics Summary",children:U.jsxs("p",{children:["Wire to ",U.jsx("code",{children:"/__osham/admin/metrics/summary"}),"."]})}),U.jsx(Ql,{title:"Revision",children:U.jsx("p",{children:"Show current config revision and apply state."})})]})})}function Ny(){return U.jsx(Fn,{title:"Config",subtitle:"Landing page for global settings and namespace editors.",children:U.jsxs("div",{className:"code-block",children:[U.jsx("strong",{children:"Planned next:"}),U.jsxs("ul",{children:[U.jsxs("li",{children:["load current config from ",U.jsx("code",{children:"/__osham/admin/config"})]}),U.jsx("li",{children:"draft editing for global config + namespaces"}),U.jsx("li",{children:"validate, save, and reload actions"})]})]})})}function Ty(){return U.jsx(Fn,{title:"Metrics",subtitle:"Namespace and aggregate cache metrics.",children:U.jsxs("div",{className:"code-block",children:["Hook into ",U.jsx("code",{children:"/__osham/admin/metrics/summary"})," and ",U.jsx("code",{children:"/__osham/admin/metrics/namespaces"}),"."]})})}function Dy(){return U.jsx(Fn,{title:"Health",subtitle:"Operational health and startup visibility.",children:U.jsx("div",{className:"code-block",children:"Surface cache backend, uptime, revision, and feature flags here."})})}function My(){return U.jsx(Fn,{title:"Purge",subtitle:"Safe cache invalidation tools.",children:U.jsxs("div",{className:"code-block",children:["Next step: connect form to ",U.jsx("code",{children:"POST /__osham/admin/purge"})," with warning display and dry-run support."]})})}function zy(){return U.jsx(Fn,{title:"Audit",subtitle:"Recent admin actions.",children:U.jsxs("div",{className:"code-block",children:["Next step: table view over ",U.jsx("code",{children:"/__osham/admin/audit"}),"."]})})}function jy(){return U.jsx(Fn,{title:"Not Found",subtitle:"That route does not exist."})}const Oy=[["/admin/dashboard","Dashboard"],["/admin/config","Config"],["/admin/metrics","Metrics"],["/admin/health","Health"],["/admin/purge","Purge"],["/admin/audit","Audit"]];function Fy(){return U.jsxs("aside",{className:"sidebar",children:[U.jsx("div",{className:"brand",children:"Osham Admin"}),U.jsx("nav",{className:"nav-list",children:Oy.map(([e,t])=>U.jsx(Cy,{to:e,className:({isActive:n})=>`nav-link${n?" active":""}`,children:t},e))})]})}function Iy(){return U.jsxs("div",{className:"app-shell",children:[U.jsx(Fy,{}),U.jsx("main",{className:"content-shell",children:U.jsx(ny,{})})]})}const Uy=cy([{path:"/",element:U.jsx(Iy,{}),children:[{index:!0,element:U.jsx(ty,{to:"/admin/dashboard",replace:!0})},{path:"/admin/dashboard",element:U.jsx(Ly,{})},{path:"/admin/config",element:U.jsx(Ny,{})},{path:"/admin/metrics",element:U.jsx(Ty,{})},{path:"/admin/health",element:U.jsx(Dy,{})},{path:"/admin/purge",element:U.jsx(My,{})},{path:"/admin/audit",element:U.jsx(zy,{})},{path:"*",element:U.jsx(jy,{})}]}]);Mo.createRoot(document.getElementById("root")).render(U.jsx(Nc.StrictMode,{children:U.jsx(gy,{router:Uy})})); diff --git a/admin-ui/dist/assets/index-CtLrP-Jx.js b/admin-ui/dist/assets/index-CtLrP-Jx.js new file mode 100644 index 0000000..abd2720 --- /dev/null +++ b/admin-ui/dist/assets/index-CtLrP-Jx.js @@ -0,0 +1,68 @@ +function wc(e,t){for(var n=0;nr[l]})}}}return Object.freeze(Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}))}(function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const l of document.querySelectorAll('link[rel="modulepreload"]'))r(l);new MutationObserver(l=>{for(const i of l)if(i.type==="childList")for(const o of i.addedNodes)o.tagName==="LINK"&&o.rel==="modulepreload"&&r(o)}).observe(document,{childList:!0,subtree:!0});function n(l){const i={};return l.integrity&&(i.integrity=l.integrity),l.referrerPolicy&&(i.referrerPolicy=l.referrerPolicy),l.crossOrigin==="use-credentials"?i.credentials="include":l.crossOrigin==="anonymous"?i.credentials="omit":i.credentials="same-origin",i}function r(l){if(l.ep)return;l.ep=!0;const i=n(l);fetch(l.href,i)}})();function Sc(e){return e&&e.__esModule&&Object.prototype.hasOwnProperty.call(e,"default")?e.default:e}var Ec={exports:{}},Oi={},xc={exports:{}},Y={};/** + * @license React + * react.production.min.js + * + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */var vl=Symbol.for("react.element"),hp=Symbol.for("react.portal"),mp=Symbol.for("react.fragment"),vp=Symbol.for("react.strict_mode"),yp=Symbol.for("react.profiler"),gp=Symbol.for("react.provider"),wp=Symbol.for("react.context"),Sp=Symbol.for("react.forward_ref"),Ep=Symbol.for("react.suspense"),xp=Symbol.for("react.memo"),kp=Symbol.for("react.lazy"),$u=Symbol.iterator;function Cp(e){return e===null||typeof e!="object"?null:(e=$u&&e[$u]||e["@@iterator"],typeof e=="function"?e:null)}var kc={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},Cc=Object.assign,Pc={};function vr(e,t,n){this.props=e,this.context=t,this.refs=Pc,this.updater=n||kc}vr.prototype.isReactComponent={};vr.prototype.setState=function(e,t){if(typeof e!="object"&&typeof e!="function"&&e!=null)throw Error("setState(...): takes an object of state variables to update or a function which returns an object of state variables.");this.updater.enqueueSetState(this,e,t,"setState")};vr.prototype.forceUpdate=function(e){this.updater.enqueueForceUpdate(this,e,"forceUpdate")};function _c(){}_c.prototype=vr.prototype;function Da(e,t,n){this.props=e,this.context=t,this.refs=Pc,this.updater=n||kc}var Ma=Da.prototype=new _c;Ma.constructor=Da;Cc(Ma,vr.prototype);Ma.isPureReactComponent=!0;var Wu=Array.isArray,Rc=Object.prototype.hasOwnProperty,ja={current:null},Lc={key:!0,ref:!0,__self:!0,__source:!0};function Nc(e,t,n){var r,l={},i=null,o=null;if(t!=null)for(r in t.ref!==void 0&&(o=t.ref),t.key!==void 0&&(i=""+t.key),t)Rc.call(t,r)&&!Lc.hasOwnProperty(r)&&(l[r]=t[r]);var a=arguments.length-2;if(a===1)l.children=n;else if(1>>1,ne=N[te];if(0>>1;tel(Oe,W))Fel(nt,Oe)?(N[te]=nt,N[Fe]=W,te=Fe):(N[te]=Oe,N[Xe]=W,te=Xe);else if(Fel(nt,W))N[te]=nt,N[Fe]=W,te=Fe;else break e}}return V}function l(N,V){var W=N.sortIndex-V.sortIndex;return W!==0?W:N.id-V.id}if(typeof performance=="object"&&typeof performance.now=="function"){var i=performance;e.unstable_now=function(){return i.now()}}else{var o=Date,a=o.now();e.unstable_now=function(){return o.now()-a}}var u=[],s=[],d=1,f=null,p=3,w=!1,x=!1,E=!1,D=typeof setTimeout=="function"?setTimeout:null,m=typeof clearTimeout=="function"?clearTimeout:null,c=typeof setImmediate<"u"?setImmediate:null;typeof navigator<"u"&&navigator.scheduling!==void 0&&navigator.scheduling.isInputPending!==void 0&&navigator.scheduling.isInputPending.bind(navigator.scheduling);function v(N){for(var V=n(s);V!==null;){if(V.callback===null)r(s);else if(V.startTime<=N)r(s),V.sortIndex=V.expirationTime,t(u,V);else break;V=n(s)}}function k(N){if(E=!1,v(N),!x)if(n(u)!==null)x=!0,Ht(L);else{var V=n(s);V!==null&&Vt(k,V.startTime-N)}}function L(N,V){x=!1,E&&(E=!1,m(C),C=-1),w=!0;var W=p;try{for(v(V),f=n(u);f!==null&&(!(f.expirationTime>V)||N&&!q());){var te=f.callback;if(typeof te=="function"){f.callback=null,p=f.priorityLevel;var ne=te(f.expirationTime<=V);V=e.unstable_now(),typeof ne=="function"?f.callback=ne:f===n(u)&&r(u),v(V)}else r(u);f=n(u)}if(f!==null)var ct=!0;else{var Xe=n(s);Xe!==null&&Vt(k,Xe.startTime-V),ct=!1}return ct}finally{f=null,p=W,w=!1}}var z=!1,y=null,C=-1,H=5,M=-1;function q(){return!(e.unstable_now()-MN||125te?(N.sortIndex=W,t(s,N),n(u)===null&&N===n(s)&&(E?(m(C),C=-1):E=!0,Vt(k,W-te))):(N.sortIndex=ne,t(u,N),x||w||(x=!0,Ht(L))),N},e.unstable_shouldYield=q,e.unstable_wrapCallback=function(N){var V=p;return function(){var W=p;p=V;try{return N.apply(this,arguments)}finally{p=W}}}})(zc);jc.exports=zc;var Fp=jc.exports;/** + * @license React + * react-dom.production.min.js + * + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */var Ip=_,be=Fp;function R(e){for(var t="https://reactjs.org/docs/error-decoder.html?invariant="+e,n=1;n"u"||typeof window.document>"u"||typeof window.document.createElement>"u"),Oo=Object.prototype.hasOwnProperty,Up=/^[:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD][:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD\-.0-9\u00B7\u0300-\u036F\u203F-\u2040]*$/,Ku={},Yu={};function Ap(e){return Oo.call(Yu,e)?!0:Oo.call(Ku,e)?!1:Up.test(e)?Yu[e]=!0:(Ku[e]=!0,!1)}function Bp(e,t,n,r){if(n!==null&&n.type===0)return!1;switch(typeof t){case"function":case"symbol":return!0;case"boolean":return r?!1:n!==null?!n.acceptsBooleans:(e=e.toLowerCase().slice(0,5),e!=="data-"&&e!=="aria-");default:return!1}}function Hp(e,t,n,r){if(t===null||typeof t>"u"||Bp(e,t,n,r))return!0;if(r)return!1;if(n!==null)switch(n.type){case 3:return!t;case 4:return t===!1;case 5:return isNaN(t);case 6:return isNaN(t)||1>t}return!1}function He(e,t,n,r,l,i,o){this.acceptsBooleans=t===2||t===3||t===4,this.attributeName=r,this.attributeNamespace=l,this.mustUseProperty=n,this.propertyName=e,this.type=t,this.sanitizeURL=i,this.removeEmptyString=o}var Te={};"children dangerouslySetInnerHTML defaultValue defaultChecked innerHTML suppressContentEditableWarning suppressHydrationWarning style".split(" ").forEach(function(e){Te[e]=new He(e,0,!1,e,null,!1,!1)});[["acceptCharset","accept-charset"],["className","class"],["htmlFor","for"],["httpEquiv","http-equiv"]].forEach(function(e){var t=e[0];Te[t]=new He(t,1,!1,e[1],null,!1,!1)});["contentEditable","draggable","spellCheck","value"].forEach(function(e){Te[e]=new He(e,2,!1,e.toLowerCase(),null,!1,!1)});["autoReverse","externalResourcesRequired","focusable","preserveAlpha"].forEach(function(e){Te[e]=new He(e,2,!1,e,null,!1,!1)});"allowFullScreen async autoFocus autoPlay controls default defer disabled disablePictureInPicture disableRemotePlayback formNoValidate hidden loop noModule noValidate open playsInline readOnly required reversed scoped seamless itemScope".split(" ").forEach(function(e){Te[e]=new He(e,3,!1,e.toLowerCase(),null,!1,!1)});["checked","multiple","muted","selected"].forEach(function(e){Te[e]=new He(e,3,!0,e,null,!1,!1)});["capture","download"].forEach(function(e){Te[e]=new He(e,4,!1,e,null,!1,!1)});["cols","rows","size","span"].forEach(function(e){Te[e]=new He(e,6,!1,e,null,!1,!1)});["rowSpan","start"].forEach(function(e){Te[e]=new He(e,5,!1,e.toLowerCase(),null,!1,!1)});var Oa=/[\-:]([a-z])/g;function Fa(e){return e[1].toUpperCase()}"accent-height alignment-baseline arabic-form baseline-shift cap-height clip-path clip-rule color-interpolation color-interpolation-filters color-profile color-rendering dominant-baseline enable-background fill-opacity fill-rule flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-name glyph-orientation-horizontal glyph-orientation-vertical horiz-adv-x horiz-origin-x image-rendering letter-spacing lighting-color marker-end marker-mid marker-start overline-position overline-thickness paint-order panose-1 pointer-events rendering-intent shape-rendering stop-color stop-opacity strikethrough-position strikethrough-thickness stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width text-anchor text-decoration text-rendering underline-position underline-thickness unicode-bidi unicode-range units-per-em v-alphabetic v-hanging v-ideographic v-mathematical vector-effect vert-adv-y vert-origin-x vert-origin-y word-spacing writing-mode xmlns:xlink x-height".split(" ").forEach(function(e){var t=e.replace(Oa,Fa);Te[t]=new He(t,1,!1,e,null,!1,!1)});"xlink:actuate xlink:arcrole xlink:role xlink:show xlink:title xlink:type".split(" ").forEach(function(e){var t=e.replace(Oa,Fa);Te[t]=new He(t,1,!1,e,"http://www.w3.org/1999/xlink",!1,!1)});["xml:base","xml:lang","xml:space"].forEach(function(e){var t=e.replace(Oa,Fa);Te[t]=new He(t,1,!1,e,"http://www.w3.org/XML/1998/namespace",!1,!1)});["tabIndex","crossOrigin"].forEach(function(e){Te[e]=new He(e,1,!1,e.toLowerCase(),null,!1,!1)});Te.xlinkHref=new He("xlinkHref",1,!1,"xlink:href","http://www.w3.org/1999/xlink",!0,!1);["src","href","action","formAction"].forEach(function(e){Te[e]=new He(e,1,!1,e.toLowerCase(),null,!0,!0)});function Ia(e,t,n,r){var l=Te.hasOwnProperty(t)?Te[t]:null;(l!==null?l.type!==0:r||!(2a||l[o]!==i[a]){var u=` +`+l[o].replace(" at new "," at ");return e.displayName&&u.includes("")&&(u=u.replace("",e.displayName)),u}while(1<=o&&0<=a);break}}}finally{oo=!1,Error.prepareStackTrace=n}return(e=e?e.displayName||e.name:"")?Fr(e):""}function Vp(e){switch(e.tag){case 5:return Fr(e.type);case 16:return Fr("Lazy");case 13:return Fr("Suspense");case 19:return Fr("SuspenseList");case 0:case 2:case 15:return e=ao(e.type,!1),e;case 11:return e=ao(e.type.render,!1),e;case 1:return e=ao(e.type,!0),e;default:return""}}function Ao(e){if(e==null)return null;if(typeof e=="function")return e.displayName||e.name||null;if(typeof e=="string")return e;switch(e){case Qn:return"Fragment";case Wn:return"Portal";case Fo:return"Profiler";case Ua:return"StrictMode";case Io:return"Suspense";case Uo:return"SuspenseList"}if(typeof e=="object")switch(e.$$typeof){case Ic:return(e.displayName||"Context")+".Consumer";case Fc:return(e._context.displayName||"Context")+".Provider";case Aa:var t=e.render;return e=e.displayName,e||(e=t.displayName||t.name||"",e=e!==""?"ForwardRef("+e+")":"ForwardRef"),e;case Ba:return t=e.displayName||null,t!==null?t:Ao(e.type)||"Memo";case Yt:t=e._payload,e=e._init;try{return Ao(e(t))}catch{}}return null}function $p(e){var t=e.type;switch(e.tag){case 24:return"Cache";case 9:return(t.displayName||"Context")+".Consumer";case 10:return(t._context.displayName||"Context")+".Provider";case 18:return"DehydratedFragment";case 11:return e=t.render,e=e.displayName||e.name||"",t.displayName||(e!==""?"ForwardRef("+e+")":"ForwardRef");case 7:return"Fragment";case 5:return t;case 4:return"Portal";case 3:return"Root";case 6:return"Text";case 16:return Ao(t);case 8:return t===Ua?"StrictMode":"Mode";case 22:return"Offscreen";case 12:return"Profiler";case 21:return"Scope";case 13:return"Suspense";case 19:return"SuspenseList";case 25:return"TracingMarker";case 1:case 0:case 17:case 2:case 14:case 15:if(typeof t=="function")return t.displayName||t.name||null;if(typeof t=="string")return t}return null}function sn(e){switch(typeof e){case"boolean":case"number":case"string":case"undefined":return e;case"object":return e;default:return""}}function Ac(e){var t=e.type;return(e=e.nodeName)&&e.toLowerCase()==="input"&&(t==="checkbox"||t==="radio")}function Wp(e){var t=Ac(e)?"checked":"value",n=Object.getOwnPropertyDescriptor(e.constructor.prototype,t),r=""+e[t];if(!e.hasOwnProperty(t)&&typeof n<"u"&&typeof n.get=="function"&&typeof n.set=="function"){var l=n.get,i=n.set;return Object.defineProperty(e,t,{configurable:!0,get:function(){return l.call(this)},set:function(o){r=""+o,i.call(this,o)}}),Object.defineProperty(e,t,{enumerable:n.enumerable}),{getValue:function(){return r},setValue:function(o){r=""+o},stopTracking:function(){e._valueTracker=null,delete e[t]}}}}function Dl(e){e._valueTracker||(e._valueTracker=Wp(e))}function Bc(e){if(!e)return!1;var t=e._valueTracker;if(!t)return!0;var n=t.getValue(),r="";return e&&(r=Ac(e)?e.checked?"true":"false":e.value),e=r,e!==n?(t.setValue(e),!0):!1}function ai(e){if(e=e||(typeof document<"u"?document:void 0),typeof e>"u")return null;try{return e.activeElement||e.body}catch{return e.body}}function Bo(e,t){var n=t.checked;return de({},t,{defaultChecked:void 0,defaultValue:void 0,value:void 0,checked:n??e._wrapperState.initialChecked})}function Gu(e,t){var n=t.defaultValue==null?"":t.defaultValue,r=t.checked!=null?t.checked:t.defaultChecked;n=sn(t.value!=null?t.value:n),e._wrapperState={initialChecked:r,initialValue:n,controlled:t.type==="checkbox"||t.type==="radio"?t.checked!=null:t.value!=null}}function Hc(e,t){t=t.checked,t!=null&&Ia(e,"checked",t,!1)}function Ho(e,t){Hc(e,t);var n=sn(t.value),r=t.type;if(n!=null)r==="number"?(n===0&&e.value===""||e.value!=n)&&(e.value=""+n):e.value!==""+n&&(e.value=""+n);else if(r==="submit"||r==="reset"){e.removeAttribute("value");return}t.hasOwnProperty("value")?Vo(e,t.type,n):t.hasOwnProperty("defaultValue")&&Vo(e,t.type,sn(t.defaultValue)),t.checked==null&&t.defaultChecked!=null&&(e.defaultChecked=!!t.defaultChecked)}function Ju(e,t,n){if(t.hasOwnProperty("value")||t.hasOwnProperty("defaultValue")){var r=t.type;if(!(r!=="submit"&&r!=="reset"||t.value!==void 0&&t.value!==null))return;t=""+e._wrapperState.initialValue,n||t===e.value||(e.value=t),e.defaultValue=t}n=e.name,n!==""&&(e.name=""),e.defaultChecked=!!e._wrapperState.initialChecked,n!==""&&(e.name=n)}function Vo(e,t,n){(t!=="number"||ai(e.ownerDocument)!==e)&&(n==null?e.defaultValue=""+e._wrapperState.initialValue:e.defaultValue!==""+n&&(e.defaultValue=""+n))}var Ir=Array.isArray;function nr(e,t,n,r){if(e=e.options,t){t={};for(var l=0;l"+t.valueOf().toString()+"",t=Ml.firstChild;e.firstChild;)e.removeChild(e.firstChild);for(;t.firstChild;)e.appendChild(t.firstChild)}});function qr(e,t){if(t){var n=e.firstChild;if(n&&n===e.lastChild&&n.nodeType===3){n.nodeValue=t;return}}e.textContent=t}var Hr={animationIterationCount:!0,aspectRatio:!0,borderImageOutset:!0,borderImageSlice:!0,borderImageWidth:!0,boxFlex:!0,boxFlexGroup:!0,boxOrdinalGroup:!0,columnCount:!0,columns:!0,flex:!0,flexGrow:!0,flexPositive:!0,flexShrink:!0,flexNegative:!0,flexOrder:!0,gridArea:!0,gridRow:!0,gridRowEnd:!0,gridRowSpan:!0,gridRowStart:!0,gridColumn:!0,gridColumnEnd:!0,gridColumnSpan:!0,gridColumnStart:!0,fontWeight:!0,lineClamp:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,tabSize:!0,widows:!0,zIndex:!0,zoom:!0,fillOpacity:!0,floodOpacity:!0,stopOpacity:!0,strokeDasharray:!0,strokeDashoffset:!0,strokeMiterlimit:!0,strokeOpacity:!0,strokeWidth:!0},Qp=["Webkit","ms","Moz","O"];Object.keys(Hr).forEach(function(e){Qp.forEach(function(t){t=t+e.charAt(0).toUpperCase()+e.substring(1),Hr[t]=Hr[e]})});function Qc(e,t,n){return t==null||typeof t=="boolean"||t===""?"":n||typeof t!="number"||t===0||Hr.hasOwnProperty(e)&&Hr[e]?(""+t).trim():t+"px"}function Kc(e,t){e=e.style;for(var n in t)if(t.hasOwnProperty(n)){var r=n.indexOf("--")===0,l=Qc(n,t[n],r);n==="float"&&(n="cssFloat"),r?e.setProperty(n,l):e[n]=l}}var Kp=de({menuitem:!0},{area:!0,base:!0,br:!0,col:!0,embed:!0,hr:!0,img:!0,input:!0,keygen:!0,link:!0,meta:!0,param:!0,source:!0,track:!0,wbr:!0});function Qo(e,t){if(t){if(Kp[e]&&(t.children!=null||t.dangerouslySetInnerHTML!=null))throw Error(R(137,e));if(t.dangerouslySetInnerHTML!=null){if(t.children!=null)throw Error(R(60));if(typeof t.dangerouslySetInnerHTML!="object"||!("__html"in t.dangerouslySetInnerHTML))throw Error(R(61))}if(t.style!=null&&typeof t.style!="object")throw Error(R(62))}}function Ko(e,t){if(e.indexOf("-")===-1)return typeof t.is=="string";switch(e){case"annotation-xml":case"color-profile":case"font-face":case"font-face-src":case"font-face-uri":case"font-face-format":case"font-face-name":case"missing-glyph":return!1;default:return!0}}var Yo=null;function Ha(e){return e=e.target||e.srcElement||window,e.correspondingUseElement&&(e=e.correspondingUseElement),e.nodeType===3?e.parentNode:e}var Xo=null,rr=null,lr=null;function bu(e){if(e=wl(e)){if(typeof Xo!="function")throw Error(R(280));var t=e.stateNode;t&&(t=Bi(t),Xo(e.stateNode,e.type,t))}}function Yc(e){rr?lr?lr.push(e):lr=[e]:rr=e}function Xc(){if(rr){var e=rr,t=lr;if(lr=rr=null,bu(e),t)for(e=0;e>>=0,e===0?32:31-(rh(e)/lh|0)|0}var jl=64,zl=4194304;function Ur(e){switch(e&-e){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return e&4194240;case 4194304:case 8388608:case 16777216:case 33554432:case 67108864:return e&130023424;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 1073741824;default:return e}}function fi(e,t){var n=e.pendingLanes;if(n===0)return 0;var r=0,l=e.suspendedLanes,i=e.pingedLanes,o=n&268435455;if(o!==0){var a=o&~l;a!==0?r=Ur(a):(i&=o,i!==0&&(r=Ur(i)))}else o=n&~l,o!==0?r=Ur(o):i!==0&&(r=Ur(i));if(r===0)return 0;if(t!==0&&t!==r&&!(t&l)&&(l=r&-r,i=t&-t,l>=i||l===16&&(i&4194240)!==0))return t;if(r&4&&(r|=n&16),t=e.entangledLanes,t!==0)for(e=e.entanglements,t&=r;0n;n++)t.push(e);return t}function yl(e,t,n){e.pendingLanes|=t,t!==536870912&&(e.suspendedLanes=0,e.pingedLanes=0),e=e.eventTimes,t=31-vt(t),e[t]=n}function uh(e,t){var n=e.pendingLanes&~t;e.pendingLanes=t,e.suspendedLanes=0,e.pingedLanes=0,e.expiredLanes&=t,e.mutableReadLanes&=t,e.entangledLanes&=t,t=e.entanglements;var r=e.eventTimes;for(e=e.expirationTimes;0=$r),us=" ",ss=!1;function mf(e,t){switch(e){case"keyup":return Fh.indexOf(t.keyCode)!==-1;case"keydown":return t.keyCode!==229;case"keypress":case"mousedown":case"focusout":return!0;default:return!1}}function vf(e){return e=e.detail,typeof e=="object"&&"data"in e?e.data:null}var Kn=!1;function Uh(e,t){switch(e){case"compositionend":return vf(t);case"keypress":return t.which!==32?null:(ss=!0,us);case"textInput":return e=t.data,e===us&&ss?null:e;default:return null}}function Ah(e,t){if(Kn)return e==="compositionend"||!Ga&&mf(e,t)?(e=pf(),Zl=Ka=Zt=null,Kn=!1,e):null;switch(e){case"paste":return null;case"keypress":if(!(t.ctrlKey||t.altKey||t.metaKey)||t.ctrlKey&&t.altKey){if(t.char&&1=t)return{node:n,offset:t-e};e=r}e:{for(;n;){if(n.nextSibling){n=n.nextSibling;break e}n=n.parentNode}n=void 0}n=ps(n)}}function Sf(e,t){return e&&t?e===t?!0:e&&e.nodeType===3?!1:t&&t.nodeType===3?Sf(e,t.parentNode):"contains"in e?e.contains(t):e.compareDocumentPosition?!!(e.compareDocumentPosition(t)&16):!1:!1}function Ef(){for(var e=window,t=ai();t instanceof e.HTMLIFrameElement;){try{var n=typeof t.contentWindow.location.href=="string"}catch{n=!1}if(n)e=t.contentWindow;else break;t=ai(e.document)}return t}function Ja(e){var t=e&&e.nodeName&&e.nodeName.toLowerCase();return t&&(t==="input"&&(e.type==="text"||e.type==="search"||e.type==="tel"||e.type==="url"||e.type==="password")||t==="textarea"||e.contentEditable==="true")}function Xh(e){var t=Ef(),n=e.focusedElem,r=e.selectionRange;if(t!==n&&n&&n.ownerDocument&&Sf(n.ownerDocument.documentElement,n)){if(r!==null&&Ja(n)){if(t=r.start,e=r.end,e===void 0&&(e=t),"selectionStart"in n)n.selectionStart=t,n.selectionEnd=Math.min(e,n.value.length);else if(e=(t=n.ownerDocument||document)&&t.defaultView||window,e.getSelection){e=e.getSelection();var l=n.textContent.length,i=Math.min(r.start,l);r=r.end===void 0?i:Math.min(r.end,l),!e.extend&&i>r&&(l=r,r=i,i=l),l=hs(n,i);var o=hs(n,r);l&&o&&(e.rangeCount!==1||e.anchorNode!==l.node||e.anchorOffset!==l.offset||e.focusNode!==o.node||e.focusOffset!==o.offset)&&(t=t.createRange(),t.setStart(l.node,l.offset),e.removeAllRanges(),i>r?(e.addRange(t),e.extend(o.node,o.offset)):(t.setEnd(o.node,o.offset),e.addRange(t)))}}for(t=[],e=n;e=e.parentNode;)e.nodeType===1&&t.push({element:e,left:e.scrollLeft,top:e.scrollTop});for(typeof n.focus=="function"&&n.focus(),n=0;n=document.documentMode,Yn=null,ea=null,Qr=null,ta=!1;function ms(e,t,n){var r=n.window===n?n.document:n.nodeType===9?n:n.ownerDocument;ta||Yn==null||Yn!==ai(r)||(r=Yn,"selectionStart"in r&&Ja(r)?r={start:r.selectionStart,end:r.selectionEnd}:(r=(r.ownerDocument&&r.ownerDocument.defaultView||window).getSelection(),r={anchorNode:r.anchorNode,anchorOffset:r.anchorOffset,focusNode:r.focusNode,focusOffset:r.focusOffset}),Qr&&ll(Qr,r)||(Qr=r,r=hi(ea,"onSelect"),0Jn||(e.current=aa[Jn],aa[Jn]=null,Jn--)}function ie(e,t){Jn++,aa[Jn]=e.current,e.current=t}var cn={},ze=dn(cn),Qe=dn(!1),Ln=cn;function sr(e,t){var n=e.type.contextTypes;if(!n)return cn;var r=e.stateNode;if(r&&r.__reactInternalMemoizedUnmaskedChildContext===t)return r.__reactInternalMemoizedMaskedChildContext;var l={},i;for(i in n)l[i]=t[i];return r&&(e=e.stateNode,e.__reactInternalMemoizedUnmaskedChildContext=t,e.__reactInternalMemoizedMaskedChildContext=l),l}function Ke(e){return e=e.childContextTypes,e!=null}function vi(){ae(Qe),ae(ze)}function xs(e,t,n){if(ze.current!==cn)throw Error(R(168));ie(ze,t),ie(Qe,n)}function Tf(e,t,n){var r=e.stateNode;if(t=t.childContextTypes,typeof r.getChildContext!="function")return n;r=r.getChildContext();for(var l in r)if(!(l in t))throw Error(R(108,$p(e)||"Unknown",l));return de({},n,r)}function yi(e){return e=(e=e.stateNode)&&e.__reactInternalMemoizedMergedChildContext||cn,Ln=ze.current,ie(ze,e),ie(Qe,Qe.current),!0}function ks(e,t,n){var r=e.stateNode;if(!r)throw Error(R(169));n?(e=Tf(e,t,Ln),r.__reactInternalMemoizedMergedChildContext=e,ae(Qe),ae(ze),ie(ze,e)):ae(Qe),ie(Qe,n)}var Lt=null,Hi=!1,xo=!1;function Df(e){Lt===null?Lt=[e]:Lt.push(e)}function om(e){Hi=!0,Df(e)}function pn(){if(!xo&&Lt!==null){xo=!0;var e=0,t=ee;try{var n=Lt;for(ee=1;e>=o,l-=o,Nt=1<<32-vt(t)+l|n<C?(H=y,y=null):H=y.sibling;var M=p(m,y,v[C],k);if(M===null){y===null&&(y=H);break}e&&y&&M.alternate===null&&t(m,y),c=i(M,c,C),z===null?L=M:z.sibling=M,z=M,y=H}if(C===v.length)return n(m,y),se&&gn(m,C),L;if(y===null){for(;CC?(H=y,y=null):H=y.sibling;var q=p(m,y,M.value,k);if(q===null){y===null&&(y=H);break}e&&y&&q.alternate===null&&t(m,y),c=i(q,c,C),z===null?L=q:z.sibling=q,z=q,y=H}if(M.done)return n(m,y),se&&gn(m,C),L;if(y===null){for(;!M.done;C++,M=v.next())M=f(m,M.value,k),M!==null&&(c=i(M,c,C),z===null?L=M:z.sibling=M,z=M);return se&&gn(m,C),L}for(y=r(m,y);!M.done;C++,M=v.next())M=w(y,m,C,M.value,k),M!==null&&(e&&M.alternate!==null&&y.delete(M.key===null?C:M.key),c=i(M,c,C),z===null?L=M:z.sibling=M,z=M);return e&&y.forEach(function(re){return t(m,re)}),se&&gn(m,C),L}function D(m,c,v,k){if(typeof v=="object"&&v!==null&&v.type===Qn&&v.key===null&&(v=v.props.children),typeof v=="object"&&v!==null){switch(v.$$typeof){case Tl:e:{for(var L=v.key,z=c;z!==null;){if(z.key===L){if(L=v.type,L===Qn){if(z.tag===7){n(m,z.sibling),c=l(z,v.props.children),c.return=m,m=c;break e}}else if(z.elementType===L||typeof L=="object"&&L!==null&&L.$$typeof===Yt&&_s(L)===z.type){n(m,z.sibling),c=l(z,v.props),c.ref=Tr(m,z,v),c.return=m,m=c;break e}n(m,z);break}else t(m,z);z=z.sibling}v.type===Qn?(c=Rn(v.props.children,m.mode,k,v.key),c.return=m,m=c):(k=ii(v.type,v.key,v.props,null,m.mode,k),k.ref=Tr(m,c,v),k.return=m,m=k)}return o(m);case Wn:e:{for(z=v.key;c!==null;){if(c.key===z)if(c.tag===4&&c.stateNode.containerInfo===v.containerInfo&&c.stateNode.implementation===v.implementation){n(m,c.sibling),c=l(c,v.children||[]),c.return=m,m=c;break e}else{n(m,c);break}else t(m,c);c=c.sibling}c=To(v,m.mode,k),c.return=m,m=c}return o(m);case Yt:return z=v._init,D(m,c,z(v._payload),k)}if(Ir(v))return x(m,c,v,k);if(Pr(v))return E(m,c,v,k);Hl(m,v)}return typeof v=="string"&&v!==""||typeof v=="number"?(v=""+v,c!==null&&c.tag===6?(n(m,c.sibling),c=l(c,v),c.return=m,m=c):(n(m,c),c=No(v,m.mode,k),c.return=m,m=c),o(m)):n(m,c)}return D}var fr=Of(!0),Ff=Of(!1),Si=dn(null),Ei=null,bn=null,eu=null;function tu(){eu=bn=Ei=null}function nu(e){var t=Si.current;ae(Si),e._currentValue=t}function ca(e,t,n){for(;e!==null;){var r=e.alternate;if((e.childLanes&t)!==t?(e.childLanes|=t,r!==null&&(r.childLanes|=t)):r!==null&&(r.childLanes&t)!==t&&(r.childLanes|=t),e===n)break;e=e.return}}function or(e,t){Ei=e,eu=bn=null,e=e.dependencies,e!==null&&e.firstContext!==null&&(e.lanes&t&&(We=!0),e.firstContext=null)}function at(e){var t=e._currentValue;if(eu!==e)if(e={context:e,memoizedValue:t,next:null},bn===null){if(Ei===null)throw Error(R(308));bn=e,Ei.dependencies={lanes:0,firstContext:e}}else bn=bn.next=e;return t}var kn=null;function ru(e){kn===null?kn=[e]:kn.push(e)}function If(e,t,n,r){var l=t.interleaved;return l===null?(n.next=n,ru(t)):(n.next=l.next,l.next=n),t.interleaved=n,Ot(e,r)}function Ot(e,t){e.lanes|=t;var n=e.alternate;for(n!==null&&(n.lanes|=t),n=e,e=e.return;e!==null;)e.childLanes|=t,n=e.alternate,n!==null&&(n.childLanes|=t),n=e,e=e.return;return n.tag===3?n.stateNode:null}var Xt=!1;function lu(e){e.updateQueue={baseState:e.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,interleaved:null,lanes:0},effects:null}}function Uf(e,t){e=e.updateQueue,t.updateQueue===e&&(t.updateQueue={baseState:e.baseState,firstBaseUpdate:e.firstBaseUpdate,lastBaseUpdate:e.lastBaseUpdate,shared:e.shared,effects:e.effects})}function Dt(e,t){return{eventTime:e,lane:t,tag:0,payload:null,callback:null,next:null}}function ln(e,t,n){var r=e.updateQueue;if(r===null)return null;if(r=r.shared,G&2){var l=r.pending;return l===null?t.next=t:(t.next=l.next,l.next=t),r.pending=t,Ot(e,n)}return l=r.interleaved,l===null?(t.next=t,ru(r)):(t.next=l.next,l.next=t),r.interleaved=t,Ot(e,n)}function bl(e,t,n){if(t=t.updateQueue,t!==null&&(t=t.shared,(n&4194240)!==0)){var r=t.lanes;r&=e.pendingLanes,n|=r,t.lanes=n,$a(e,n)}}function Rs(e,t){var n=e.updateQueue,r=e.alternate;if(r!==null&&(r=r.updateQueue,n===r)){var l=null,i=null;if(n=n.firstBaseUpdate,n!==null){do{var o={eventTime:n.eventTime,lane:n.lane,tag:n.tag,payload:n.payload,callback:n.callback,next:null};i===null?l=i=o:i=i.next=o,n=n.next}while(n!==null);i===null?l=i=t:i=i.next=t}else l=i=t;n={baseState:r.baseState,firstBaseUpdate:l,lastBaseUpdate:i,shared:r.shared,effects:r.effects},e.updateQueue=n;return}e=n.lastBaseUpdate,e===null?n.firstBaseUpdate=t:e.next=t,n.lastBaseUpdate=t}function xi(e,t,n,r){var l=e.updateQueue;Xt=!1;var i=l.firstBaseUpdate,o=l.lastBaseUpdate,a=l.shared.pending;if(a!==null){l.shared.pending=null;var u=a,s=u.next;u.next=null,o===null?i=s:o.next=s,o=u;var d=e.alternate;d!==null&&(d=d.updateQueue,a=d.lastBaseUpdate,a!==o&&(a===null?d.firstBaseUpdate=s:a.next=s,d.lastBaseUpdate=u))}if(i!==null){var f=l.baseState;o=0,d=s=u=null,a=i;do{var p=a.lane,w=a.eventTime;if((r&p)===p){d!==null&&(d=d.next={eventTime:w,lane:0,tag:a.tag,payload:a.payload,callback:a.callback,next:null});e:{var x=e,E=a;switch(p=t,w=n,E.tag){case 1:if(x=E.payload,typeof x=="function"){f=x.call(w,f,p);break e}f=x;break e;case 3:x.flags=x.flags&-65537|128;case 0:if(x=E.payload,p=typeof x=="function"?x.call(w,f,p):x,p==null)break e;f=de({},f,p);break e;case 2:Xt=!0}}a.callback!==null&&a.lane!==0&&(e.flags|=64,p=l.effects,p===null?l.effects=[a]:p.push(a))}else w={eventTime:w,lane:p,tag:a.tag,payload:a.payload,callback:a.callback,next:null},d===null?(s=d=w,u=f):d=d.next=w,o|=p;if(a=a.next,a===null){if(a=l.shared.pending,a===null)break;p=a,a=p.next,p.next=null,l.lastBaseUpdate=p,l.shared.pending=null}}while(!0);if(d===null&&(u=f),l.baseState=u,l.firstBaseUpdate=s,l.lastBaseUpdate=d,t=l.shared.interleaved,t!==null){l=t;do o|=l.lane,l=l.next;while(l!==t)}else i===null&&(l.shared.lanes=0);Dn|=o,e.lanes=o,e.memoizedState=f}}function Ls(e,t,n){if(e=t.effects,t.effects=null,e!==null)for(t=0;tn?n:4,e(!0);var r=Co.transition;Co.transition={};try{e(!1),t()}finally{ee=n,Co.transition=r}}function td(){return ut().memoizedState}function cm(e,t,n){var r=an(e);if(n={lane:r,action:n,hasEagerState:!1,eagerState:null,next:null},nd(e))rd(t,n);else if(n=If(e,t,n,r),n!==null){var l=Ae();yt(n,e,r,l),ld(n,t,r)}}function fm(e,t,n){var r=an(e),l={lane:r,action:n,hasEagerState:!1,eagerState:null,next:null};if(nd(e))rd(t,l);else{var i=e.alternate;if(e.lanes===0&&(i===null||i.lanes===0)&&(i=t.lastRenderedReducer,i!==null))try{var o=t.lastRenderedState,a=i(o,n);if(l.hasEagerState=!0,l.eagerState=a,gt(a,o)){var u=t.interleaved;u===null?(l.next=l,ru(t)):(l.next=u.next,u.next=l),t.interleaved=l;return}}catch{}finally{}n=If(e,t,l,r),n!==null&&(l=Ae(),yt(n,e,r,l),ld(n,t,r))}}function nd(e){var t=e.alternate;return e===fe||t!==null&&t===fe}function rd(e,t){Kr=Ci=!0;var n=e.pending;n===null?t.next=t:(t.next=n.next,n.next=t),e.pending=t}function ld(e,t,n){if(n&4194240){var r=t.lanes;r&=e.pendingLanes,n|=r,t.lanes=n,$a(e,n)}}var Pi={readContext:at,useCallback:De,useContext:De,useEffect:De,useImperativeHandle:De,useInsertionEffect:De,useLayoutEffect:De,useMemo:De,useReducer:De,useRef:De,useState:De,useDebugValue:De,useDeferredValue:De,useTransition:De,useMutableSource:De,useSyncExternalStore:De,useId:De,unstable_isNewReconciler:!1},dm={readContext:at,useCallback:function(e,t){return Et().memoizedState=[e,t===void 0?null:t],e},useContext:at,useEffect:Ts,useImperativeHandle:function(e,t,n){return n=n!=null?n.concat([e]):null,ti(4194308,4,Jf.bind(null,t,e),n)},useLayoutEffect:function(e,t){return ti(4194308,4,e,t)},useInsertionEffect:function(e,t){return ti(4,2,e,t)},useMemo:function(e,t){var n=Et();return t=t===void 0?null:t,e=e(),n.memoizedState=[e,t],e},useReducer:function(e,t,n){var r=Et();return t=n!==void 0?n(t):t,r.memoizedState=r.baseState=t,e={pending:null,interleaved:null,lanes:0,dispatch:null,lastRenderedReducer:e,lastRenderedState:t},r.queue=e,e=e.dispatch=cm.bind(null,fe,e),[r.memoizedState,e]},useRef:function(e){var t=Et();return e={current:e},t.memoizedState=e},useState:Ns,useDebugValue:du,useDeferredValue:function(e){return Et().memoizedState=e},useTransition:function(){var e=Ns(!1),t=e[0];return e=sm.bind(null,e[1]),Et().memoizedState=e,[t,e]},useMutableSource:function(){},useSyncExternalStore:function(e,t,n){var r=fe,l=Et();if(se){if(n===void 0)throw Error(R(407));n=n()}else{if(n=t(),Pe===null)throw Error(R(349));Tn&30||Vf(r,t,n)}l.memoizedState=n;var i={value:n,getSnapshot:t};return l.queue=i,Ts(Wf.bind(null,r,i,e),[e]),r.flags|=2048,dl(9,$f.bind(null,r,i,n,t),void 0,null),n},useId:function(){var e=Et(),t=Pe.identifierPrefix;if(se){var n=Tt,r=Nt;n=(r&~(1<<32-vt(r)-1)).toString(32)+n,t=":"+t+"R"+n,n=cl++,0<\/script>",e=e.removeChild(e.firstChild)):typeof r.is=="string"?e=o.createElement(n,{is:r.is}):(e=o.createElement(n),n==="select"&&(o=e,r.multiple?o.multiple=!0:r.size&&(o.size=r.size))):e=o.createElementNS(e,n),e[xt]=t,e[al]=r,hd(e,t,!1,!1),t.stateNode=e;e:{switch(o=Ko(n,r),n){case"dialog":oe("cancel",e),oe("close",e),l=r;break;case"iframe":case"object":case"embed":oe("load",e),l=r;break;case"video":case"audio":for(l=0;lhr&&(t.flags|=128,r=!0,Dr(i,!1),t.lanes=4194304)}else{if(!r)if(e=ki(o),e!==null){if(t.flags|=128,r=!0,n=e.updateQueue,n!==null&&(t.updateQueue=n,t.flags|=4),Dr(i,!0),i.tail===null&&i.tailMode==="hidden"&&!o.alternate&&!se)return Me(t),null}else 2*ve()-i.renderingStartTime>hr&&n!==1073741824&&(t.flags|=128,r=!0,Dr(i,!1),t.lanes=4194304);i.isBackwards?(o.sibling=t.child,t.child=o):(n=i.last,n!==null?n.sibling=o:t.child=o,i.last=o)}return i.tail!==null?(t=i.tail,i.rendering=t,i.tail=t.sibling,i.renderingStartTime=ve(),t.sibling=null,n=ce.current,ie(ce,r?n&1|2:n&1),t):(Me(t),null);case 22:case 23:return gu(),r=t.memoizedState!==null,e!==null&&e.memoizedState!==null!==r&&(t.flags|=8192),r&&t.mode&1?Ge&1073741824&&(Me(t),t.subtreeFlags&6&&(t.flags|=8192)):Me(t),null;case 24:return null;case 25:return null}throw Error(R(156,t.tag))}function Sm(e,t){switch(qa(t),t.tag){case 1:return Ke(t.type)&&vi(),e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 3:return dr(),ae(Qe),ae(ze),au(),e=t.flags,e&65536&&!(e&128)?(t.flags=e&-65537|128,t):null;case 5:return ou(t),null;case 13:if(ae(ce),e=t.memoizedState,e!==null&&e.dehydrated!==null){if(t.alternate===null)throw Error(R(340));cr()}return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 19:return ae(ce),null;case 4:return dr(),null;case 10:return nu(t.type._context),null;case 22:case 23:return gu(),null;case 24:return null;default:return null}}var $l=!1,je=!1,Em=typeof WeakSet=="function"?WeakSet:Set,O=null;function er(e,t){var n=e.ref;if(n!==null)if(typeof n=="function")try{n(null)}catch(r){me(e,t,r)}else n.current=null}function wa(e,t,n){try{n()}catch(r){me(e,t,r)}}var Hs=!1;function xm(e,t){if(na=di,e=Ef(),Ja(e)){if("selectionStart"in e)var n={start:e.selectionStart,end:e.selectionEnd};else e:{n=(n=e.ownerDocument)&&n.defaultView||window;var r=n.getSelection&&n.getSelection();if(r&&r.rangeCount!==0){n=r.anchorNode;var l=r.anchorOffset,i=r.focusNode;r=r.focusOffset;try{n.nodeType,i.nodeType}catch{n=null;break e}var o=0,a=-1,u=-1,s=0,d=0,f=e,p=null;t:for(;;){for(var w;f!==n||l!==0&&f.nodeType!==3||(a=o+l),f!==i||r!==0&&f.nodeType!==3||(u=o+r),f.nodeType===3&&(o+=f.nodeValue.length),(w=f.firstChild)!==null;)p=f,f=w;for(;;){if(f===e)break t;if(p===n&&++s===l&&(a=o),p===i&&++d===r&&(u=o),(w=f.nextSibling)!==null)break;f=p,p=f.parentNode}f=w}n=a===-1||u===-1?null:{start:a,end:u}}else n=null}n=n||{start:0,end:0}}else n=null;for(ra={focusedElem:e,selectionRange:n},di=!1,O=t;O!==null;)if(t=O,e=t.child,(t.subtreeFlags&1028)!==0&&e!==null)e.return=t,O=e;else for(;O!==null;){t=O;try{var x=t.alternate;if(t.flags&1024)switch(t.tag){case 0:case 11:case 15:break;case 1:if(x!==null){var E=x.memoizedProps,D=x.memoizedState,m=t.stateNode,c=m.getSnapshotBeforeUpdate(t.elementType===t.type?E:dt(t.type,E),D);m.__reactInternalSnapshotBeforeUpdate=c}break;case 3:var v=t.stateNode.containerInfo;v.nodeType===1?v.textContent="":v.nodeType===9&&v.documentElement&&v.removeChild(v.documentElement);break;case 5:case 6:case 4:case 17:break;default:throw Error(R(163))}}catch(k){me(t,t.return,k)}if(e=t.sibling,e!==null){e.return=t.return,O=e;break}O=t.return}return x=Hs,Hs=!1,x}function Yr(e,t,n){var r=t.updateQueue;if(r=r!==null?r.lastEffect:null,r!==null){var l=r=r.next;do{if((l.tag&e)===e){var i=l.destroy;l.destroy=void 0,i!==void 0&&wa(t,n,i)}l=l.next}while(l!==r)}}function Wi(e,t){if(t=t.updateQueue,t=t!==null?t.lastEffect:null,t!==null){var n=t=t.next;do{if((n.tag&e)===e){var r=n.create;n.destroy=r()}n=n.next}while(n!==t)}}function Sa(e){var t=e.ref;if(t!==null){var n=e.stateNode;switch(e.tag){case 5:e=n;break;default:e=n}typeof t=="function"?t(e):t.current=e}}function yd(e){var t=e.alternate;t!==null&&(e.alternate=null,yd(t)),e.child=null,e.deletions=null,e.sibling=null,e.tag===5&&(t=e.stateNode,t!==null&&(delete t[xt],delete t[al],delete t[oa],delete t[lm],delete t[im])),e.stateNode=null,e.return=null,e.dependencies=null,e.memoizedProps=null,e.memoizedState=null,e.pendingProps=null,e.stateNode=null,e.updateQueue=null}function gd(e){return e.tag===5||e.tag===3||e.tag===4}function Vs(e){e:for(;;){for(;e.sibling===null;){if(e.return===null||gd(e.return))return null;e=e.return}for(e.sibling.return=e.return,e=e.sibling;e.tag!==5&&e.tag!==6&&e.tag!==18;){if(e.flags&2||e.child===null||e.tag===4)continue e;e.child.return=e,e=e.child}if(!(e.flags&2))return e.stateNode}}function Ea(e,t,n){var r=e.tag;if(r===5||r===6)e=e.stateNode,t?n.nodeType===8?n.parentNode.insertBefore(e,t):n.insertBefore(e,t):(n.nodeType===8?(t=n.parentNode,t.insertBefore(e,n)):(t=n,t.appendChild(e)),n=n._reactRootContainer,n!=null||t.onclick!==null||(t.onclick=mi));else if(r!==4&&(e=e.child,e!==null))for(Ea(e,t,n),e=e.sibling;e!==null;)Ea(e,t,n),e=e.sibling}function xa(e,t,n){var r=e.tag;if(r===5||r===6)e=e.stateNode,t?n.insertBefore(e,t):n.appendChild(e);else if(r!==4&&(e=e.child,e!==null))for(xa(e,t,n),e=e.sibling;e!==null;)xa(e,t,n),e=e.sibling}var Le=null,pt=!1;function Qt(e,t,n){for(n=n.child;n!==null;)wd(e,t,n),n=n.sibling}function wd(e,t,n){if(kt&&typeof kt.onCommitFiberUnmount=="function")try{kt.onCommitFiberUnmount(Fi,n)}catch{}switch(n.tag){case 5:je||er(n,t);case 6:var r=Le,l=pt;Le=null,Qt(e,t,n),Le=r,pt=l,Le!==null&&(pt?(e=Le,n=n.stateNode,e.nodeType===8?e.parentNode.removeChild(n):e.removeChild(n)):Le.removeChild(n.stateNode));break;case 18:Le!==null&&(pt?(e=Le,n=n.stateNode,e.nodeType===8?Eo(e.parentNode,n):e.nodeType===1&&Eo(e,n),nl(e)):Eo(Le,n.stateNode));break;case 4:r=Le,l=pt,Le=n.stateNode.containerInfo,pt=!0,Qt(e,t,n),Le=r,pt=l;break;case 0:case 11:case 14:case 15:if(!je&&(r=n.updateQueue,r!==null&&(r=r.lastEffect,r!==null))){l=r=r.next;do{var i=l,o=i.destroy;i=i.tag,o!==void 0&&(i&2||i&4)&&wa(n,t,o),l=l.next}while(l!==r)}Qt(e,t,n);break;case 1:if(!je&&(er(n,t),r=n.stateNode,typeof r.componentWillUnmount=="function"))try{r.props=n.memoizedProps,r.state=n.memoizedState,r.componentWillUnmount()}catch(a){me(n,t,a)}Qt(e,t,n);break;case 21:Qt(e,t,n);break;case 22:n.mode&1?(je=(r=je)||n.memoizedState!==null,Qt(e,t,n),je=r):Qt(e,t,n);break;default:Qt(e,t,n)}}function $s(e){var t=e.updateQueue;if(t!==null){e.updateQueue=null;var n=e.stateNode;n===null&&(n=e.stateNode=new Em),t.forEach(function(r){var l=Dm.bind(null,e,r);n.has(r)||(n.add(r),r.then(l,l))})}}function ft(e,t){var n=t.deletions;if(n!==null)for(var r=0;rl&&(l=o),r&=~i}if(r=l,r=ve()-r,r=(120>r?120:480>r?480:1080>r?1080:1920>r?1920:3e3>r?3e3:4320>r?4320:1960*Cm(r/1960))-r,10e?16:e,qt===null)var r=!1;else{if(e=qt,qt=null,Li=0,G&6)throw Error(R(331));var l=G;for(G|=4,O=e.current;O!==null;){var i=O,o=i.child;if(O.flags&16){var a=i.deletions;if(a!==null){for(var u=0;uve()-vu?_n(e,0):mu|=n),Ye(e,t)}function Rd(e,t){t===0&&(e.mode&1?(t=zl,zl<<=1,!(zl&130023424)&&(zl=4194304)):t=1);var n=Ae();e=Ot(e,t),e!==null&&(yl(e,t,n),Ye(e,n))}function Tm(e){var t=e.memoizedState,n=0;t!==null&&(n=t.retryLane),Rd(e,n)}function Dm(e,t){var n=0;switch(e.tag){case 13:var r=e.stateNode,l=e.memoizedState;l!==null&&(n=l.retryLane);break;case 19:r=e.stateNode;break;default:throw Error(R(314))}r!==null&&r.delete(t),Rd(e,n)}var Ld;Ld=function(e,t,n){if(e!==null)if(e.memoizedProps!==t.pendingProps||Qe.current)We=!0;else{if(!(e.lanes&n)&&!(t.flags&128))return We=!1,gm(e,t,n);We=!!(e.flags&131072)}else We=!1,se&&t.flags&1048576&&Mf(t,wi,t.index);switch(t.lanes=0,t.tag){case 2:var r=t.type;ni(e,t),e=t.pendingProps;var l=sr(t,ze.current);or(t,n),l=su(null,t,r,e,l,n);var i=cu();return t.flags|=1,typeof l=="object"&&l!==null&&typeof l.render=="function"&&l.$$typeof===void 0?(t.tag=1,t.memoizedState=null,t.updateQueue=null,Ke(r)?(i=!0,yi(t)):i=!1,t.memoizedState=l.state!==null&&l.state!==void 0?l.state:null,lu(t),l.updater=$i,t.stateNode=l,l._reactInternals=t,da(t,r,e,n),t=ma(null,t,r,!0,i,n)):(t.tag=0,se&&i&&Za(t),Ue(null,t,l,n),t=t.child),t;case 16:r=t.elementType;e:{switch(ni(e,t),e=t.pendingProps,l=r._init,r=l(r._payload),t.type=r,l=t.tag=jm(r),e=dt(r,e),l){case 0:t=ha(null,t,r,e,n);break e;case 1:t=Us(null,t,r,e,n);break e;case 11:t=Fs(null,t,r,e,n);break e;case 14:t=Is(null,t,r,dt(r.type,e),n);break e}throw Error(R(306,r,""))}return t;case 0:return r=t.type,l=t.pendingProps,l=t.elementType===r?l:dt(r,l),ha(e,t,r,l,n);case 1:return r=t.type,l=t.pendingProps,l=t.elementType===r?l:dt(r,l),Us(e,t,r,l,n);case 3:e:{if(fd(t),e===null)throw Error(R(387));r=t.pendingProps,i=t.memoizedState,l=i.element,Uf(e,t),xi(t,r,null,n);var o=t.memoizedState;if(r=o.element,i.isDehydrated)if(i={element:r,isDehydrated:!1,cache:o.cache,pendingSuspenseBoundaries:o.pendingSuspenseBoundaries,transitions:o.transitions},t.updateQueue.baseState=i,t.memoizedState=i,t.flags&256){l=pr(Error(R(423)),t),t=As(e,t,r,n,l);break e}else if(r!==l){l=pr(Error(R(424)),t),t=As(e,t,r,n,l);break e}else for(Ze=rn(t.stateNode.containerInfo.firstChild),qe=t,se=!0,mt=null,n=Ff(t,null,r,n),t.child=n;n;)n.flags=n.flags&-3|4096,n=n.sibling;else{if(cr(),r===l){t=Ft(e,t,n);break e}Ue(e,t,r,n)}t=t.child}return t;case 5:return Af(t),e===null&&sa(t),r=t.type,l=t.pendingProps,i=e!==null?e.memoizedProps:null,o=l.children,la(r,l)?o=null:i!==null&&la(r,i)&&(t.flags|=32),cd(e,t),Ue(e,t,o,n),t.child;case 6:return e===null&&sa(t),null;case 13:return dd(e,t,n);case 4:return iu(t,t.stateNode.containerInfo),r=t.pendingProps,e===null?t.child=fr(t,null,r,n):Ue(e,t,r,n),t.child;case 11:return r=t.type,l=t.pendingProps,l=t.elementType===r?l:dt(r,l),Fs(e,t,r,l,n);case 7:return Ue(e,t,t.pendingProps,n),t.child;case 8:return Ue(e,t,t.pendingProps.children,n),t.child;case 12:return Ue(e,t,t.pendingProps.children,n),t.child;case 10:e:{if(r=t.type._context,l=t.pendingProps,i=t.memoizedProps,o=l.value,ie(Si,r._currentValue),r._currentValue=o,i!==null)if(gt(i.value,o)){if(i.children===l.children&&!Qe.current){t=Ft(e,t,n);break e}}else for(i=t.child,i!==null&&(i.return=t);i!==null;){var a=i.dependencies;if(a!==null){o=i.child;for(var u=a.firstContext;u!==null;){if(u.context===r){if(i.tag===1){u=Dt(-1,n&-n),u.tag=2;var s=i.updateQueue;if(s!==null){s=s.shared;var d=s.pending;d===null?u.next=u:(u.next=d.next,d.next=u),s.pending=u}}i.lanes|=n,u=i.alternate,u!==null&&(u.lanes|=n),ca(i.return,n,t),a.lanes|=n;break}u=u.next}}else if(i.tag===10)o=i.type===t.type?null:i.child;else if(i.tag===18){if(o=i.return,o===null)throw Error(R(341));o.lanes|=n,a=o.alternate,a!==null&&(a.lanes|=n),ca(o,n,t),o=i.sibling}else o=i.child;if(o!==null)o.return=i;else for(o=i;o!==null;){if(o===t){o=null;break}if(i=o.sibling,i!==null){i.return=o.return,o=i;break}o=o.return}i=o}Ue(e,t,l.children,n),t=t.child}return t;case 9:return l=t.type,r=t.pendingProps.children,or(t,n),l=at(l),r=r(l),t.flags|=1,Ue(e,t,r,n),t.child;case 14:return r=t.type,l=dt(r,t.pendingProps),l=dt(r.type,l),Is(e,t,r,l,n);case 15:return ud(e,t,t.type,t.pendingProps,n);case 17:return r=t.type,l=t.pendingProps,l=t.elementType===r?l:dt(r,l),ni(e,t),t.tag=1,Ke(r)?(e=!0,yi(t)):e=!1,or(t,n),id(t,r,l),da(t,r,l,n),ma(null,t,r,!0,e,n);case 19:return pd(e,t,n);case 22:return sd(e,t,n)}throw Error(R(156,t.tag))};function Nd(e,t){return tf(e,t)}function Mm(e,t,n,r){this.tag=e,this.key=n,this.sibling=this.child=this.return=this.stateNode=this.type=this.elementType=null,this.index=0,this.ref=null,this.pendingProps=t,this.dependencies=this.memoizedState=this.updateQueue=this.memoizedProps=null,this.mode=r,this.subtreeFlags=this.flags=0,this.deletions=null,this.childLanes=this.lanes=0,this.alternate=null}function it(e,t,n,r){return new Mm(e,t,n,r)}function Su(e){return e=e.prototype,!(!e||!e.isReactComponent)}function jm(e){if(typeof e=="function")return Su(e)?1:0;if(e!=null){if(e=e.$$typeof,e===Aa)return 11;if(e===Ba)return 14}return 2}function un(e,t){var n=e.alternate;return n===null?(n=it(e.tag,t,e.key,e.mode),n.elementType=e.elementType,n.type=e.type,n.stateNode=e.stateNode,n.alternate=e,e.alternate=n):(n.pendingProps=t,n.type=e.type,n.flags=0,n.subtreeFlags=0,n.deletions=null),n.flags=e.flags&14680064,n.childLanes=e.childLanes,n.lanes=e.lanes,n.child=e.child,n.memoizedProps=e.memoizedProps,n.memoizedState=e.memoizedState,n.updateQueue=e.updateQueue,t=e.dependencies,n.dependencies=t===null?null:{lanes:t.lanes,firstContext:t.firstContext},n.sibling=e.sibling,n.index=e.index,n.ref=e.ref,n}function ii(e,t,n,r,l,i){var o=2;if(r=e,typeof e=="function")Su(e)&&(o=1);else if(typeof e=="string")o=5;else e:switch(e){case Qn:return Rn(n.children,l,i,t);case Ua:o=8,l|=8;break;case Fo:return e=it(12,n,t,l|2),e.elementType=Fo,e.lanes=i,e;case Io:return e=it(13,n,t,l),e.elementType=Io,e.lanes=i,e;case Uo:return e=it(19,n,t,l),e.elementType=Uo,e.lanes=i,e;case Uc:return Ki(n,l,i,t);default:if(typeof e=="object"&&e!==null)switch(e.$$typeof){case Fc:o=10;break e;case Ic:o=9;break e;case Aa:o=11;break e;case Ba:o=14;break e;case Yt:o=16,r=null;break e}throw Error(R(130,e==null?e:typeof e,""))}return t=it(o,n,t,l),t.elementType=e,t.type=r,t.lanes=i,t}function Rn(e,t,n,r){return e=it(7,e,r,t),e.lanes=n,e}function Ki(e,t,n,r){return e=it(22,e,r,t),e.elementType=Uc,e.lanes=n,e.stateNode={isHidden:!1},e}function No(e,t,n){return e=it(6,e,null,t),e.lanes=n,e}function To(e,t,n){return t=it(4,e.children!==null?e.children:[],e.key,t),t.lanes=n,t.stateNode={containerInfo:e.containerInfo,pendingChildren:null,implementation:e.implementation},t}function zm(e,t,n,r,l){this.tag=t,this.containerInfo=e,this.finishedWork=this.pingCache=this.current=this.pendingChildren=null,this.timeoutHandle=-1,this.callbackNode=this.pendingContext=this.context=null,this.callbackPriority=0,this.eventTimes=so(0),this.expirationTimes=so(-1),this.entangledLanes=this.finishedLanes=this.mutableReadLanes=this.expiredLanes=this.pingedLanes=this.suspendedLanes=this.pendingLanes=0,this.entanglements=so(0),this.identifierPrefix=r,this.onRecoverableError=l,this.mutableSourceEagerHydrationData=null}function Eu(e,t,n,r,l,i,o,a,u){return e=new zm(e,t,n,a,u),t===1?(t=1,i===!0&&(t|=8)):t=0,i=it(3,null,null,t),e.current=i,i.stateNode=e,i.memoizedState={element:r,isDehydrated:n,cache:null,transitions:null,pendingSuspenseBoundaries:null},lu(i),e}function Om(e,t,n){var r=3"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(jd)}catch(e){console.error(e)}}jd(),Mc.exports=et;var Pu=Mc.exports;const Bm=Sc(Pu),Hm=wc({__proto__:null,default:Bm},[Pu]);var Zs=Pu;zo.createRoot=Zs.createRoot,zo.hydrateRoot=Zs.hydrateRoot;/** + * @remix-run/router v1.23.2 + * + * Copyright (c) Remix Software Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE.md file in the root directory of this source tree. + * + * @license MIT + */function ue(){return ue=Object.assign?Object.assign.bind():function(e){for(var t=1;t"u")throw new Error(t)}function jn(e,t){if(!e){typeof console<"u"&&console.warn(t);try{throw new Error(t)}catch{}}}function $m(){return Math.random().toString(36).substr(2,8)}function bs(e,t){return{usr:e.state,key:e.key,idx:t}}function hl(e,t,n,r){return n===void 0&&(n=null),ue({pathname:typeof e=="string"?e:e.pathname,search:"",hash:""},typeof t=="string"?hn(t):t,{state:n,key:t&&t.key||r||$m()})}function zn(e){let{pathname:t="/",search:n="",hash:r=""}=e;return n&&n!=="?"&&(t+=n.charAt(0)==="?"?n:"?"+n),r&&r!=="#"&&(t+=r.charAt(0)==="#"?r:"#"+r),t}function hn(e){let t={};if(e){let n=e.indexOf("#");n>=0&&(t.hash=e.substr(n),e=e.substr(0,n));let r=e.indexOf("?");r>=0&&(t.search=e.substr(r),e=e.substr(0,r)),e&&(t.pathname=e)}return t}function Wm(e,t,n,r){r===void 0&&(r={});let{window:l=document.defaultView,v5Compat:i=!1}=r,o=l.history,a=ge.Pop,u=null,s=d();s==null&&(s=0,o.replaceState(ue({},o.state,{idx:s}),""));function d(){return(o.state||{idx:null}).idx}function f(){a=ge.Pop;let D=d(),m=D==null?null:D-s;s=D,u&&u({action:a,location:E.location,delta:m})}function p(D,m){a=ge.Push;let c=hl(E.location,D,m);s=d()+1;let v=bs(c,s),k=E.createHref(c);try{o.pushState(v,"",k)}catch(L){if(L instanceof DOMException&&L.name==="DataCloneError")throw L;l.location.assign(k)}i&&u&&u({action:a,location:E.location,delta:1})}function w(D,m){a=ge.Replace;let c=hl(E.location,D,m);s=d();let v=bs(c,s),k=E.createHref(c);o.replaceState(v,"",k),i&&u&&u({action:a,location:E.location,delta:0})}function x(D){let m=l.location.origin!=="null"?l.location.origin:l.location.href,c=typeof D=="string"?D:zn(D);return c=c.replace(/ $/,"%20"),K(m,"No window.location.(origin|href) available to create URL for href: "+c),new URL(c,m)}let E={get action(){return a},get location(){return e(l,o)},listen(D){if(u)throw new Error("A history only accepts one active listener");return l.addEventListener(qs,f),u=D,()=>{l.removeEventListener(qs,f),u=null}},createHref(D){return t(l,D)},createURL:x,encodeLocation(D){let m=x(D);return{pathname:m.pathname,search:m.search,hash:m.hash}},push:p,replace:w,go(D){return o.go(D)}};return E}var b;(function(e){e.data="data",e.deferred="deferred",e.redirect="redirect",e.error="error"})(b||(b={}));const Qm=new Set(["lazy","caseSensitive","path","id","index","children"]);function Km(e){return e.index===!0}function Di(e,t,n,r){return n===void 0&&(n=[]),r===void 0&&(r={}),e.map((l,i)=>{let o=[...n,String(i)],a=typeof l.id=="string"?l.id:o.join("-");if(K(l.index!==!0||!l.children,"Cannot specify children on an index route"),K(!r[a],'Found a route id collision on id "'+a+`". Route id's must be globally unique within Data Router usages`),Km(l)){let u=ue({},l,t(l),{id:a});return r[a]=u,u}else{let u=ue({},l,t(l),{id:a,children:void 0});return r[a]=u,l.children&&(u.children=Di(l.children,t,o,r)),u}})}function Sn(e,t,n){return n===void 0&&(n="/"),oi(e,t,n,!1)}function oi(e,t,n,r){let l=typeof t=="string"?hn(t):t,i=It(l.pathname||"/",n);if(i==null)return null;let o=zd(e);Xm(o);let a=null;for(let u=0;a==null&&u{let u={relativePath:a===void 0?i.path||"":a,caseSensitive:i.caseSensitive===!0,childrenIndex:o,route:i};u.relativePath.startsWith("/")&&(K(u.relativePath.startsWith(r),'Absolute route path "'+u.relativePath+'" nested under path '+('"'+r+'" is not valid. An absolute child route path ')+"must start with the combined path of all its parent routes."),u.relativePath=u.relativePath.slice(r.length));let s=Mt([r,u.relativePath]),d=n.concat(u);i.children&&i.children.length>0&&(K(i.index!==!0,"Index routes must not have child routes. Please remove "+('all child routes from route path "'+s+'".')),zd(i.children,t,d,s)),!(i.path==null&&!i.index)&&t.push({path:s,score:tv(s,i.index),routesMeta:d})};return e.forEach((i,o)=>{var a;if(i.path===""||!((a=i.path)!=null&&a.includes("?")))l(i,o);else for(let u of Od(i.path))l(i,o,u)}),t}function Od(e){let t=e.split("/");if(t.length===0)return[];let[n,...r]=t,l=n.endsWith("?"),i=n.replace(/\?$/,"");if(r.length===0)return l?[i,""]:[i];let o=Od(r.join("/")),a=[];return a.push(...o.map(u=>u===""?i:[i,u].join("/"))),l&&a.push(...o),a.map(u=>e.startsWith("/")&&u===""?"/":u)}function Xm(e){e.sort((t,n)=>t.score!==n.score?n.score-t.score:nv(t.routesMeta.map(r=>r.childrenIndex),n.routesMeta.map(r=>r.childrenIndex)))}const Gm=/^:[\w-]+$/,Jm=3,Zm=2,qm=1,bm=10,ev=-2,ec=e=>e==="*";function tv(e,t){let n=e.split("/"),r=n.length;return n.some(ec)&&(r+=ev),t&&(r+=Zm),n.filter(l=>!ec(l)).reduce((l,i)=>l+(Gm.test(i)?Jm:i===""?qm:bm),r)}function nv(e,t){return e.length===t.length&&e.slice(0,-1).every((r,l)=>r===t[l])?e[e.length-1]-t[t.length-1]:0}function rv(e,t,n){n===void 0&&(n=!1);let{routesMeta:r}=e,l={},i="/",o=[];for(let a=0;a{let{paramName:p,isOptional:w}=d;if(p==="*"){let E=a[f]||"";o=i.slice(0,i.length-E.length).replace(/(.)\/+$/,"$1")}const x=a[f];return w&&!x?s[p]=void 0:s[p]=(x||"").replace(/%2F/g,"/"),s},{}),pathname:i,pathnameBase:o,pattern:e}}function lv(e,t,n){t===void 0&&(t=!1),n===void 0&&(n=!0),jn(e==="*"||!e.endsWith("*")||e.endsWith("/*"),'Route path "'+e+'" will be treated as if it were '+('"'+e.replace(/\*$/,"/*")+'" because the `*` character must ')+"always follow a `/` in the pattern. To get rid of this warning, "+('please change the route path to "'+e.replace(/\*$/,"/*")+'".'));let r=[],l="^"+e.replace(/\/*\*?$/,"").replace(/^\/*/,"/").replace(/[\\.*+^${}|()[\]]/g,"\\$&").replace(/\/:([\w-]+)(\?)?/g,(o,a,u)=>(r.push({paramName:a,isOptional:u!=null}),u?"/?([^\\/]+)?":"/([^\\/]+)"));return e.endsWith("*")?(r.push({paramName:"*"}),l+=e==="*"||e==="/*"?"(.*)$":"(?:\\/(.+)|\\/*)$"):n?l+="\\/*$":e!==""&&e!=="/"&&(l+="(?:(?=\\/|$))"),[new RegExp(l,t?void 0:"i"),r]}function iv(e){try{return e.split("/").map(t=>decodeURIComponent(t).replace(/\//g,"%2F")).join("/")}catch(t){return jn(!1,'The URL path "'+e+'" could not be decoded because it is is a malformed URL segment. This is probably due to a bad percent '+("encoding ("+t+").")),e}}function It(e,t){if(t==="/")return e;if(!e.toLowerCase().startsWith(t.toLowerCase()))return null;let n=t.endsWith("/")?t.length-1:t.length,r=e.charAt(n);return r&&r!=="/"?null:e.slice(n)||"/"}const ov=/^(?:[a-z][a-z0-9+.-]*:|\/\/)/i,av=e=>ov.test(e);function uv(e,t){t===void 0&&(t="/");let{pathname:n,search:r="",hash:l=""}=typeof e=="string"?hn(e):e,i;if(n)if(av(n))i=n;else{if(n.includes("//")){let o=n;n=n.replace(/\/\/+/g,"/"),jn(!1,"Pathnames cannot have embedded double slashes - normalizing "+(o+" -> "+n))}n.startsWith("/")?i=tc(n.substring(1),"/"):i=tc(n,t)}else i=t;return{pathname:i,search:cv(r),hash:fv(l)}}function tc(e,t){let n=t.replace(/\/+$/,"").split("/");return e.split("/").forEach(l=>{l===".."?n.length>1&&n.pop():l!=="."&&n.push(l)}),n.length>1?n.join("/"):"/"}function Do(e,t,n,r){return"Cannot include a '"+e+"' character in a manually specified "+("`to."+t+"` field ["+JSON.stringify(r)+"]. Please separate it out to the ")+("`to."+n+"` field. Alternatively you may provide the full path as ")+'a string in and the router will parse it for you.'}function Fd(e){return e.filter((t,n)=>n===0||t.route.path&&t.route.path.length>0)}function Zi(e,t){let n=Fd(e);return t?n.map((r,l)=>l===n.length-1?r.pathname:r.pathnameBase):n.map(r=>r.pathnameBase)}function qi(e,t,n,r){r===void 0&&(r=!1);let l;typeof e=="string"?l=hn(e):(l=ue({},e),K(!l.pathname||!l.pathname.includes("?"),Do("?","pathname","search",l)),K(!l.pathname||!l.pathname.includes("#"),Do("#","pathname","hash",l)),K(!l.search||!l.search.includes("#"),Do("#","search","hash",l)));let i=e===""||l.pathname==="",o=i?"/":l.pathname,a;if(o==null)a=n;else{let f=t.length-1;if(!r&&o.startsWith("..")){let p=o.split("/");for(;p[0]==="..";)p.shift(),f-=1;l.pathname=p.join("/")}a=f>=0?t[f]:"/"}let u=uv(l,a),s=o&&o!=="/"&&o.endsWith("/"),d=(i||o===".")&&n.endsWith("/");return!u.pathname.endsWith("/")&&(s||d)&&(u.pathname+="/"),u}const Mt=e=>e.join("/").replace(/\/\/+/g,"/"),sv=e=>e.replace(/\/+$/,"").replace(/^\/*/,"/"),cv=e=>!e||e==="?"?"":e.startsWith("?")?e:"?"+e,fv=e=>!e||e==="#"?"":e.startsWith("#")?e:"#"+e;class ji{constructor(t,n,r,l){l===void 0&&(l=!1),this.status=t,this.statusText=n||"",this.internal=l,r instanceof Error?(this.data=r.toString(),this.error=r):this.data=r}}function ml(e){return e!=null&&typeof e.status=="number"&&typeof e.statusText=="string"&&typeof e.internal=="boolean"&&"data"in e}const Id=["post","put","patch","delete"],dv=new Set(Id),pv=["get",...Id],hv=new Set(pv),mv=new Set([301,302,303,307,308]),vv=new Set([307,308]),Mo={state:"idle",location:void 0,formMethod:void 0,formAction:void 0,formEncType:void 0,formData:void 0,json:void 0,text:void 0},yv={state:"idle",data:void 0,formMethod:void 0,formAction:void 0,formEncType:void 0,formData:void 0,json:void 0,text:void 0},jr={state:"unblocked",proceed:void 0,reset:void 0,location:void 0},_u=/^(?:[a-z][a-z0-9+.-]*:|\/\/)/i,gv=e=>({hasErrorBoundary:!!e.hasErrorBoundary}),Ud="remix-router-transitions";function wv(e){const t=e.window?e.window:typeof window<"u"?window:void 0,n=typeof t<"u"&&typeof t.document<"u"&&typeof t.document.createElement<"u",r=!n;K(e.routes.length>0,"You must provide a non-empty routes array to createRouter");let l;if(e.mapRouteProperties)l=e.mapRouteProperties;else if(e.detectErrorBoundary){let h=e.detectErrorBoundary;l=g=>({hasErrorBoundary:h(g)})}else l=gv;let i={},o=Di(e.routes,l,void 0,i),a,u=e.basename||"/",s=e.dataStrategy||kv,d=e.patchRoutesOnNavigation,f=ue({v7_fetcherPersist:!1,v7_normalizeFormMethod:!1,v7_partialHydration:!1,v7_prependBasename:!1,v7_relativeSplatPath:!1,v7_skipActionErrorRevalidation:!1},e.future),p=null,w=new Set,x=null,E=null,D=null,m=e.hydrationData!=null,c=Sn(o,e.history.location,u),v=!1,k=null;if(c==null&&!d){let h=Ve(404,{pathname:e.history.location.pathname}),{matches:g,route:S}=dc(o);c=g,k={[S.id]:h}}c&&!e.hydrationData&&Pl(c,o,e.history.location.pathname).active&&(c=null);let L;if(c)if(c.some(h=>h.route.lazy))L=!1;else if(!c.some(h=>h.route.loader))L=!0;else if(f.v7_partialHydration){let h=e.hydrationData?e.hydrationData.loaderData:null,g=e.hydrationData?e.hydrationData.errors:null;if(g){let S=c.findIndex(P=>g[P.route.id]!==void 0);L=c.slice(0,S+1).every(P=>!La(P.route,h,g))}else L=c.every(S=>!La(S.route,h,g))}else L=e.hydrationData!=null;else if(L=!1,c=[],f.v7_partialHydration){let h=Pl(null,o,e.history.location.pathname);h.active&&h.matches&&(v=!0,c=h.matches)}let z,y={historyAction:e.history.action,location:e.history.location,matches:c,initialized:L,navigation:Mo,restoreScrollPosition:e.hydrationData!=null?!1:null,preventScrollReset:!1,revalidation:"idle",loaderData:e.hydrationData&&e.hydrationData.loaderData||{},actionData:e.hydrationData&&e.hydrationData.actionData||null,errors:e.hydrationData&&e.hydrationData.errors||k,fetchers:new Map,blockers:new Map},C=ge.Pop,H=!1,M,q=!1,re=new Map,Se=null,_e=!1,st=!1,Ht=[],Vt=new Set,N=new Map,V=0,W=-1,te=new Map,ne=new Set,ct=new Map,Xe=new Map,Oe=new Set,Fe=new Map,nt=new Map,xl;function Zd(){if(p=e.history.listen(h=>{let{action:g,location:S,delta:P}=h;if(xl){xl(),xl=void 0;return}jn(nt.size===0||P!=null,"You are trying to use a blocker on a POP navigation to a location that was not created by @remix-run/router. This will fail silently in production. This can happen if you are navigating outside the router via `window.history.pushState`/`window.location.hash` instead of using router navigation APIs. This can also happen if you are using createHashRouter and the user manually changes the URL.");let j=Au({currentLocation:y.location,nextLocation:S,historyAction:g});if(j&&P!=null){let B=new Promise($=>{xl=$});e.history.go(P*-1),Cl(j,{state:"blocked",location:S,proceed(){Cl(j,{state:"proceeding",proceed:void 0,reset:void 0,location:S}),B.then(()=>e.history.go(P))},reset(){let $=new Map(y.blockers);$.set(j,jr),Ie({blockers:$})}});return}return mn(g,S)}),n){Iv(t,re);let h=()=>Uv(t,re);t.addEventListener("pagehide",h),Se=()=>t.removeEventListener("pagehide",h)}return y.initialized||mn(ge.Pop,y.location,{initialHydration:!0}),z}function qd(){p&&p(),Se&&Se(),w.clear(),M&&M.abort(),y.fetchers.forEach((h,g)=>kl(g)),y.blockers.forEach((h,g)=>Uu(g))}function bd(h){return w.add(h),()=>w.delete(h)}function Ie(h,g){g===void 0&&(g={}),y=ue({},y,h);let S=[],P=[];f.v7_fetcherPersist&&y.fetchers.forEach((j,B)=>{j.state==="idle"&&(Oe.has(B)?P.push(B):S.push(B))}),Oe.forEach(j=>{!y.fetchers.has(j)&&!N.has(j)&&P.push(j)}),[...w].forEach(j=>j(y,{deletedFetchers:P,viewTransitionOpts:g.viewTransitionOpts,flushSync:g.flushSync===!0})),f.v7_fetcherPersist?(S.forEach(j=>y.fetchers.delete(j)),P.forEach(j=>kl(j))):P.forEach(j=>Oe.delete(j))}function Un(h,g,S){var P,j;let{flushSync:B}=S===void 0?{}:S,$=y.actionData!=null&&y.navigation.formMethod!=null&&ht(y.navigation.formMethod)&&y.navigation.state==="loading"&&((P=h.state)==null?void 0:P._isRedirect)!==!0,I;g.actionData?Object.keys(g.actionData).length>0?I=g.actionData:I=null:$?I=y.actionData:I=null;let U=g.loaderData?cc(y.loaderData,g.loaderData,g.matches||[],g.errors):y.loaderData,F=y.blockers;F.size>0&&(F=new Map(F),F.forEach((X,Re)=>F.set(Re,jr)));let A=H===!0||y.navigation.formMethod!=null&&ht(y.navigation.formMethod)&&((j=h.state)==null?void 0:j._isRedirect)!==!0;a&&(o=a,a=void 0),_e||C===ge.Pop||(C===ge.Push?e.history.push(h,h.state):C===ge.Replace&&e.history.replace(h,h.state));let Q;if(C===ge.Pop){let X=re.get(y.location.pathname);X&&X.has(h.pathname)?Q={currentLocation:y.location,nextLocation:h}:re.has(h.pathname)&&(Q={currentLocation:h,nextLocation:y.location})}else if(q){let X=re.get(y.location.pathname);X?X.add(h.pathname):(X=new Set([h.pathname]),re.set(y.location.pathname,X)),Q={currentLocation:y.location,nextLocation:h}}Ie(ue({},g,{actionData:I,loaderData:U,historyAction:C,location:h,initialized:!0,navigation:Mo,revalidation:"idle",restoreScrollPosition:Hu(h,g.matches||y.matches),preventScrollReset:A,blockers:F}),{viewTransitionOpts:Q,flushSync:B===!0}),C=ge.Pop,H=!1,q=!1,_e=!1,st=!1,Ht=[]}async function Du(h,g){if(typeof h=="number"){e.history.go(h);return}let S=Ra(y.location,y.matches,u,f.v7_prependBasename,h,f.v7_relativeSplatPath,g==null?void 0:g.fromRouteId,g==null?void 0:g.relative),{path:P,submission:j,error:B}=nc(f.v7_normalizeFormMethod,!1,S,g),$=y.location,I=hl(y.location,P,g&&g.state);I=ue({},I,e.history.encodeLocation(I));let U=g&&g.replace!=null?g.replace:void 0,F=ge.Push;U===!0?F=ge.Replace:U===!1||j!=null&&ht(j.formMethod)&&j.formAction===y.location.pathname+y.location.search&&(F=ge.Replace);let A=g&&"preventScrollReset"in g?g.preventScrollReset===!0:void 0,Q=(g&&g.flushSync)===!0,X=Au({currentLocation:$,nextLocation:I,historyAction:F});if(X){Cl(X,{state:"blocked",location:I,proceed(){Cl(X,{state:"proceeding",proceed:void 0,reset:void 0,location:I}),Du(h,g)},reset(){let Re=new Map(y.blockers);Re.set(X,jr),Ie({blockers:Re})}});return}return await mn(F,I,{submission:j,pendingError:B,preventScrollReset:A,replace:g&&g.replace,enableViewTransition:g&&g.viewTransition,flushSync:Q})}function ep(){if(eo(),Ie({revalidation:"loading"}),y.navigation.state!=="submitting"){if(y.navigation.state==="idle"){mn(y.historyAction,y.location,{startUninterruptedRevalidation:!0});return}mn(C||y.historyAction,y.navigation.location,{overrideNavigation:y.navigation,enableViewTransition:q===!0})}}async function mn(h,g,S){M&&M.abort(),M=null,C=h,_e=(S&&S.startUninterruptedRevalidation)===!0,cp(y.location,y.matches),H=(S&&S.preventScrollReset)===!0,q=(S&&S.enableViewTransition)===!0;let P=a||o,j=S&&S.overrideNavigation,B=S!=null&&S.initialHydration&&y.matches&&y.matches.length>0&&!v?y.matches:Sn(P,g,u),$=(S&&S.flushSync)===!0;if(B&&y.initialized&&!st&&Nv(y.location,g)&&!(S&&S.submission&&ht(S.submission.formMethod))){Un(g,{matches:B},{flushSync:$});return}let I=Pl(B,P,g.pathname);if(I.active&&I.matches&&(B=I.matches),!B){let{error:le,notFoundMatches:Z,route:pe}=to(g.pathname);Un(g,{matches:Z,loaderData:{},errors:{[pe.id]:le}},{flushSync:$});return}M=new AbortController;let U=$n(e.history,g,M.signal,S&&S.submission),F;if(S&&S.pendingError)F=[En(B).route.id,{type:b.error,error:S.pendingError}];else if(S&&S.submission&&ht(S.submission.formMethod)){let le=await tp(U,g,S.submission,B,I.active,{replace:S.replace,flushSync:$});if(le.shortCircuited)return;if(le.pendingActionResult){let[Z,pe]=le.pendingActionResult;if(Je(pe)&&ml(pe.error)&&pe.error.status===404){M=null,Un(g,{matches:le.matches,loaderData:{},errors:{[Z]:pe.error}});return}}B=le.matches||B,F=le.pendingActionResult,j=jo(g,S.submission),$=!1,I.active=!1,U=$n(e.history,U.url,U.signal)}let{shortCircuited:A,matches:Q,loaderData:X,errors:Re}=await np(U,g,B,I.active,j,S&&S.submission,S&&S.fetcherSubmission,S&&S.replace,S&&S.initialHydration===!0,$,F);A||(M=null,Un(g,ue({matches:Q||B},fc(F),{loaderData:X,errors:Re})))}async function tp(h,g,S,P,j,B){B===void 0&&(B={}),eo();let $=Ov(g,S);if(Ie({navigation:$},{flushSync:B.flushSync===!0}),j){let F=await _l(P,g.pathname,h.signal);if(F.type==="aborted")return{shortCircuited:!0};if(F.type==="error"){let A=En(F.partialMatches).route.id;return{matches:F.partialMatches,pendingActionResult:[A,{type:b.error,error:F.error}]}}else if(F.matches)P=F.matches;else{let{notFoundMatches:A,error:Q,route:X}=to(g.pathname);return{matches:A,pendingActionResult:[X.id,{type:b.error,error:Q}]}}}let I,U=Br(P,g);if(!U.route.action&&!U.route.lazy)I={type:b.error,error:Ve(405,{method:h.method,pathname:g.pathname,routeId:U.route.id})};else if(I=(await Er("action",y,h,[U],P,null))[U.route.id],h.signal.aborted)return{shortCircuited:!0};if(Pn(I)){let F;return B&&B.replace!=null?F=B.replace:F=ac(I.response.headers.get("Location"),new URL(h.url),u,e.history)===y.location.pathname+y.location.search,await vn(h,I,!0,{submission:S,replace:F}),{shortCircuited:!0}}if(bt(I))throw Ve(400,{type:"defer-action"});if(Je(I)){let F=En(P,U.route.id);return(B&&B.replace)!==!0&&(C=ge.Push),{matches:P,pendingActionResult:[F.route.id,I]}}return{matches:P,pendingActionResult:[U.route.id,I]}}async function np(h,g,S,P,j,B,$,I,U,F,A){let Q=j||jo(g,B),X=B||$||hc(Q),Re=!_e&&(!f.v7_partialHydration||!U);if(P){if(Re){let he=Mu(A);Ie(ue({navigation:Q},he!==void 0?{actionData:he}:{}),{flushSync:F})}let J=await _l(S,g.pathname,h.signal);if(J.type==="aborted")return{shortCircuited:!0};if(J.type==="error"){let he=En(J.partialMatches).route.id;return{matches:J.partialMatches,loaderData:{},errors:{[he]:J.error}}}else if(J.matches)S=J.matches;else{let{error:he,notFoundMatches:Bn,route:Cr}=to(g.pathname);return{matches:Bn,loaderData:{},errors:{[Cr.id]:he}}}}let le=a||o,[Z,pe]=lc(e.history,y,S,X,g,f.v7_partialHydration&&U===!0,f.v7_skipActionErrorRevalidation,st,Ht,Vt,Oe,ct,ne,le,u,A);if(no(J=>!(S&&S.some(he=>he.route.id===J))||Z&&Z.some(he=>he.route.id===J)),W=++V,Z.length===0&&pe.length===0){let J=Fu();return Un(g,ue({matches:S,loaderData:{},errors:A&&Je(A[1])?{[A[0]]:A[1].error}:null},fc(A),J?{fetchers:new Map(y.fetchers)}:{}),{flushSync:F}),{shortCircuited:!0}}if(Re){let J={};if(!P){J.navigation=Q;let he=Mu(A);he!==void 0&&(J.actionData=he)}pe.length>0&&(J.fetchers=rp(pe)),Ie(J,{flushSync:F})}pe.forEach(J=>{Wt(J.key),J.controller&&N.set(J.key,J.controller)});let An=()=>pe.forEach(J=>Wt(J.key));M&&M.signal.addEventListener("abort",An);let{loaderResults:xr,fetcherResults:_t}=await ju(y,S,Z,pe,h);if(h.signal.aborted)return{shortCircuited:!0};M&&M.signal.removeEventListener("abort",An),pe.forEach(J=>N.delete(J.key));let wt=Kl(xr);if(wt)return await vn(h,wt.result,!0,{replace:I}),{shortCircuited:!0};if(wt=Kl(_t),wt)return ne.add(wt.key),await vn(h,wt.result,!0,{replace:I}),{shortCircuited:!0};let{loaderData:ro,errors:kr}=sc(y,S,xr,A,pe,_t,Fe);Fe.forEach((J,he)=>{J.subscribe(Bn=>{(Bn||J.done)&&Fe.delete(he)})}),f.v7_partialHydration&&U&&y.errors&&(kr=ue({},y.errors,kr));let yn=Fu(),Rl=Iu(W),Ll=yn||Rl||pe.length>0;return ue({matches:S,loaderData:ro,errors:kr},Ll?{fetchers:new Map(y.fetchers)}:{})}function Mu(h){if(h&&!Je(h[1]))return{[h[0]]:h[1].data};if(y.actionData)return Object.keys(y.actionData).length===0?null:y.actionData}function rp(h){return h.forEach(g=>{let S=y.fetchers.get(g.key),P=zr(void 0,S?S.data:void 0);y.fetchers.set(g.key,P)}),new Map(y.fetchers)}function lp(h,g,S,P){if(r)throw new Error("router.fetch() was called during the server render, but it shouldn't be. You are likely calling a useFetcher() method in the body of your component. Try moving it to a useEffect or a callback.");Wt(h);let j=(P&&P.flushSync)===!0,B=a||o,$=Ra(y.location,y.matches,u,f.v7_prependBasename,S,f.v7_relativeSplatPath,g,P==null?void 0:P.relative),I=Sn(B,$,u),U=Pl(I,B,$);if(U.active&&U.matches&&(I=U.matches),!I){Pt(h,g,Ve(404,{pathname:$}),{flushSync:j});return}let{path:F,submission:A,error:Q}=nc(f.v7_normalizeFormMethod,!0,$,P);if(Q){Pt(h,g,Q,{flushSync:j});return}let X=Br(I,F),Re=(P&&P.preventScrollReset)===!0;if(A&&ht(A.formMethod)){ip(h,g,F,X,I,U.active,j,Re,A);return}ct.set(h,{routeId:g,path:F}),op(h,g,F,X,I,U.active,j,Re,A)}async function ip(h,g,S,P,j,B,$,I,U){eo(),ct.delete(h);function F(ye){if(!ye.route.action&&!ye.route.lazy){let Hn=Ve(405,{method:U.formMethod,pathname:S,routeId:g});return Pt(h,g,Hn,{flushSync:$}),!0}return!1}if(!B&&F(P))return;let A=y.fetchers.get(h);$t(h,Fv(U,A),{flushSync:$});let Q=new AbortController,X=$n(e.history,S,Q.signal,U);if(B){let ye=await _l(j,new URL(X.url).pathname,X.signal,h);if(ye.type==="aborted")return;if(ye.type==="error"){Pt(h,g,ye.error,{flushSync:$});return}else if(ye.matches){if(j=ye.matches,P=Br(j,S),F(P))return}else{Pt(h,g,Ve(404,{pathname:S}),{flushSync:$});return}}N.set(h,Q);let Re=V,Z=(await Er("action",y,X,[P],j,h))[P.route.id];if(X.signal.aborted){N.get(h)===Q&&N.delete(h);return}if(f.v7_fetcherPersist&&Oe.has(h)){if(Pn(Z)||Je(Z)){$t(h,Kt(void 0));return}}else{if(Pn(Z))if(N.delete(h),W>Re){$t(h,Kt(void 0));return}else return ne.add(h),$t(h,zr(U)),vn(X,Z,!1,{fetcherSubmission:U,preventScrollReset:I});if(Je(Z)){Pt(h,g,Z.error);return}}if(bt(Z))throw Ve(400,{type:"defer-action"});let pe=y.navigation.location||y.location,An=$n(e.history,pe,Q.signal),xr=a||o,_t=y.navigation.state!=="idle"?Sn(xr,y.navigation.location,u):y.matches;K(_t,"Didn't find any matches after fetcher action");let wt=++V;te.set(h,wt);let ro=zr(U,Z.data);y.fetchers.set(h,ro);let[kr,yn]=lc(e.history,y,_t,U,pe,!1,f.v7_skipActionErrorRevalidation,st,Ht,Vt,Oe,ct,ne,xr,u,[P.route.id,Z]);yn.filter(ye=>ye.key!==h).forEach(ye=>{let Hn=ye.key,Vu=y.fetchers.get(Hn),pp=zr(void 0,Vu?Vu.data:void 0);y.fetchers.set(Hn,pp),Wt(Hn),ye.controller&&N.set(Hn,ye.controller)}),Ie({fetchers:new Map(y.fetchers)});let Rl=()=>yn.forEach(ye=>Wt(ye.key));Q.signal.addEventListener("abort",Rl);let{loaderResults:Ll,fetcherResults:J}=await ju(y,_t,kr,yn,An);if(Q.signal.aborted)return;Q.signal.removeEventListener("abort",Rl),te.delete(h),N.delete(h),yn.forEach(ye=>N.delete(ye.key));let he=Kl(Ll);if(he)return vn(An,he.result,!1,{preventScrollReset:I});if(he=Kl(J),he)return ne.add(he.key),vn(An,he.result,!1,{preventScrollReset:I});let{loaderData:Bn,errors:Cr}=sc(y,_t,Ll,void 0,yn,J,Fe);if(y.fetchers.has(h)){let ye=Kt(Z.data);y.fetchers.set(h,ye)}Iu(wt),y.navigation.state==="loading"&&wt>W?(K(C,"Expected pending action"),M&&M.abort(),Un(y.navigation.location,{matches:_t,loaderData:Bn,errors:Cr,fetchers:new Map(y.fetchers)})):(Ie({errors:Cr,loaderData:cc(y.loaderData,Bn,_t,Cr),fetchers:new Map(y.fetchers)}),st=!1)}async function op(h,g,S,P,j,B,$,I,U){let F=y.fetchers.get(h);$t(h,zr(U,F?F.data:void 0),{flushSync:$});let A=new AbortController,Q=$n(e.history,S,A.signal);if(B){let Z=await _l(j,new URL(Q.url).pathname,Q.signal,h);if(Z.type==="aborted")return;if(Z.type==="error"){Pt(h,g,Z.error,{flushSync:$});return}else if(Z.matches)j=Z.matches,P=Br(j,S);else{Pt(h,g,Ve(404,{pathname:S}),{flushSync:$});return}}N.set(h,A);let X=V,le=(await Er("loader",y,Q,[P],j,h))[P.route.id];if(bt(le)&&(le=await Ru(le,Q.signal,!0)||le),N.get(h)===A&&N.delete(h),!Q.signal.aborted){if(Oe.has(h)){$t(h,Kt(void 0));return}if(Pn(le))if(W>X){$t(h,Kt(void 0));return}else{ne.add(h),await vn(Q,le,!1,{preventScrollReset:I});return}if(Je(le)){Pt(h,g,le.error);return}K(!bt(le),"Unhandled fetcher deferred data"),$t(h,Kt(le.data))}}async function vn(h,g,S,P){let{submission:j,fetcherSubmission:B,preventScrollReset:$,replace:I}=P===void 0?{}:P;g.response.headers.has("X-Remix-Revalidate")&&(st=!0);let U=g.response.headers.get("Location");K(U,"Expected a Location header on the redirect Response"),U=ac(U,new URL(h.url),u,e.history);let F=hl(y.location,U,{_isRedirect:!0});if(n){let Z=!1;if(g.response.headers.has("X-Remix-Reload-Document"))Z=!0;else if(_u.test(U)){const pe=e.history.createURL(U);Z=pe.origin!==t.location.origin||It(pe.pathname,u)==null}if(Z){I?t.location.replace(U):t.location.assign(U);return}}M=null;let A=I===!0||g.response.headers.has("X-Remix-Replace")?ge.Replace:ge.Push,{formMethod:Q,formAction:X,formEncType:Re}=y.navigation;!j&&!B&&Q&&X&&Re&&(j=hc(y.navigation));let le=j||B;if(vv.has(g.response.status)&&le&&ht(le.formMethod))await mn(A,F,{submission:ue({},le,{formAction:U}),preventScrollReset:$||H,enableViewTransition:S?q:void 0});else{let Z=jo(F,j);await mn(A,F,{overrideNavigation:Z,fetcherSubmission:B,preventScrollReset:$||H,enableViewTransition:S?q:void 0})}}async function Er(h,g,S,P,j,B){let $,I={};try{$=await Cv(s,h,g,S,P,j,B,i,l)}catch(U){return P.forEach(F=>{I[F.route.id]={type:b.error,error:U}}),I}for(let[U,F]of Object.entries($))if(Tv(F)){let A=F.result;I[U]={type:b.redirect,response:Rv(A,S,U,j,u,f.v7_relativeSplatPath)}}else I[U]=await _v(F);return I}async function ju(h,g,S,P,j){let B=h.matches,$=Er("loader",h,j,S,g,null),I=Promise.all(P.map(async A=>{if(A.matches&&A.match&&A.controller){let X=(await Er("loader",h,$n(e.history,A.path,A.controller.signal),[A.match],A.matches,A.key))[A.match.route.id];return{[A.key]:X}}else return Promise.resolve({[A.key]:{type:b.error,error:Ve(404,{pathname:A.path})}})})),U=await $,F=(await I).reduce((A,Q)=>Object.assign(A,Q),{});return await Promise.all([jv(g,U,j.signal,B,h.loaderData),zv(g,F,P)]),{loaderResults:U,fetcherResults:F}}function eo(){st=!0,Ht.push(...no()),ct.forEach((h,g)=>{N.has(g)&&Vt.add(g),Wt(g)})}function $t(h,g,S){S===void 0&&(S={}),y.fetchers.set(h,g),Ie({fetchers:new Map(y.fetchers)},{flushSync:(S&&S.flushSync)===!0})}function Pt(h,g,S,P){P===void 0&&(P={});let j=En(y.matches,g);kl(h),Ie({errors:{[j.route.id]:S},fetchers:new Map(y.fetchers)},{flushSync:(P&&P.flushSync)===!0})}function zu(h){return Xe.set(h,(Xe.get(h)||0)+1),Oe.has(h)&&Oe.delete(h),y.fetchers.get(h)||yv}function kl(h){let g=y.fetchers.get(h);N.has(h)&&!(g&&g.state==="loading"&&te.has(h))&&Wt(h),ct.delete(h),te.delete(h),ne.delete(h),f.v7_fetcherPersist&&Oe.delete(h),Vt.delete(h),y.fetchers.delete(h)}function ap(h){let g=(Xe.get(h)||0)-1;g<=0?(Xe.delete(h),Oe.add(h),f.v7_fetcherPersist||kl(h)):Xe.set(h,g),Ie({fetchers:new Map(y.fetchers)})}function Wt(h){let g=N.get(h);g&&(g.abort(),N.delete(h))}function Ou(h){for(let g of h){let S=zu(g),P=Kt(S.data);y.fetchers.set(g,P)}}function Fu(){let h=[],g=!1;for(let S of ne){let P=y.fetchers.get(S);K(P,"Expected fetcher: "+S),P.state==="loading"&&(ne.delete(S),h.push(S),g=!0)}return Ou(h),g}function Iu(h){let g=[];for(let[S,P]of te)if(P0}function up(h,g){let S=y.blockers.get(h)||jr;return nt.get(h)!==g&&nt.set(h,g),S}function Uu(h){y.blockers.delete(h),nt.delete(h)}function Cl(h,g){let S=y.blockers.get(h)||jr;K(S.state==="unblocked"&&g.state==="blocked"||S.state==="blocked"&&g.state==="blocked"||S.state==="blocked"&&g.state==="proceeding"||S.state==="blocked"&&g.state==="unblocked"||S.state==="proceeding"&&g.state==="unblocked","Invalid blocker state transition: "+S.state+" -> "+g.state);let P=new Map(y.blockers);P.set(h,g),Ie({blockers:P})}function Au(h){let{currentLocation:g,nextLocation:S,historyAction:P}=h;if(nt.size===0)return;nt.size>1&&jn(!1,"A router only supports one blocker at a time");let j=Array.from(nt.entries()),[B,$]=j[j.length-1],I=y.blockers.get(B);if(!(I&&I.state==="proceeding")&&$({currentLocation:g,nextLocation:S,historyAction:P}))return B}function to(h){let g=Ve(404,{pathname:h}),S=a||o,{matches:P,route:j}=dc(S);return no(),{notFoundMatches:P,route:j,error:g}}function no(h){let g=[];return Fe.forEach((S,P)=>{(!h||h(P))&&(S.cancel(),g.push(P),Fe.delete(P))}),g}function sp(h,g,S){if(x=h,D=g,E=S||null,!m&&y.navigation===Mo){m=!0;let P=Hu(y.location,y.matches);P!=null&&Ie({restoreScrollPosition:P})}return()=>{x=null,D=null,E=null}}function Bu(h,g){return E&&E(h,g.map(P=>Ym(P,y.loaderData)))||h.key}function cp(h,g){if(x&&D){let S=Bu(h,g);x[S]=D()}}function Hu(h,g){if(x){let S=Bu(h,g),P=x[S];if(typeof P=="number")return P}return null}function Pl(h,g,S){if(d)if(h){if(Object.keys(h[0].params).length>0)return{active:!0,matches:oi(g,S,u,!0)}}else return{active:!0,matches:oi(g,S,u,!0)||[]};return{active:!1,matches:null}}async function _l(h,g,S,P){if(!d)return{type:"success",matches:h};let j=h;for(;;){let B=a==null,$=a||o,I=i;try{await d({signal:S,path:g,matches:j,fetcherKey:P,patch:(A,Q)=>{S.aborted||oc(A,Q,$,I,l)}})}catch(A){return{type:"error",error:A,partialMatches:j}}finally{B&&!S.aborted&&(o=[...o])}if(S.aborted)return{type:"aborted"};let U=Sn($,g,u);if(U)return{type:"success",matches:U};let F=oi($,g,u,!0);if(!F||j.length===F.length&&j.every((A,Q)=>A.route.id===F[Q].route.id))return{type:"success",matches:null};j=F}}function fp(h){i={},a=Di(h,l,void 0,i)}function dp(h,g){let S=a==null;oc(h,g,a||o,i,l),S&&(o=[...o],Ie({}))}return z={get basename(){return u},get future(){return f},get state(){return y},get routes(){return o},get window(){return t},initialize:Zd,subscribe:bd,enableScrollRestoration:sp,navigate:Du,fetch:lp,revalidate:ep,createHref:h=>e.history.createHref(h),encodeLocation:h=>e.history.encodeLocation(h),getFetcher:zu,deleteFetcher:ap,dispose:qd,getBlocker:up,deleteBlocker:Uu,patchRoutes:dp,_internalFetchControllers:N,_internalActiveDeferreds:Fe,_internalSetRoutes:fp},z}function Sv(e){return e!=null&&("formData"in e&&e.formData!=null||"body"in e&&e.body!==void 0)}function Ra(e,t,n,r,l,i,o,a){let u,s;if(o){u=[];for(let f of t)if(u.push(f),f.route.id===o){s=f;break}}else u=t,s=t[t.length-1];let d=qi(l||".",Zi(u,i),It(e.pathname,n)||e.pathname,a==="path");if(l==null&&(d.search=e.search,d.hash=e.hash),(l==null||l===""||l===".")&&s){let f=Lu(d.search);if(s.route.index&&!f)d.search=d.search?d.search.replace(/^\?/,"?index&"):"?index";else if(!s.route.index&&f){let p=new URLSearchParams(d.search),w=p.getAll("index");p.delete("index"),w.filter(E=>E).forEach(E=>p.append("index",E));let x=p.toString();d.search=x?"?"+x:""}}return r&&n!=="/"&&(d.pathname=d.pathname==="/"?n:Mt([n,d.pathname])),zn(d)}function nc(e,t,n,r){if(!r||!Sv(r))return{path:n};if(r.formMethod&&!Mv(r.formMethod))return{path:n,error:Ve(405,{method:r.formMethod})};let l=()=>({path:n,error:Ve(400,{type:"invalid-body"})}),i=r.formMethod||"get",o=e?i.toUpperCase():i.toLowerCase(),a=Hd(n);if(r.body!==void 0){if(r.formEncType==="text/plain"){if(!ht(o))return l();let p=typeof r.body=="string"?r.body:r.body instanceof FormData||r.body instanceof URLSearchParams?Array.from(r.body.entries()).reduce((w,x)=>{let[E,D]=x;return""+w+E+"="+D+` +`},""):String(r.body);return{path:n,submission:{formMethod:o,formAction:a,formEncType:r.formEncType,formData:void 0,json:void 0,text:p}}}else if(r.formEncType==="application/json"){if(!ht(o))return l();try{let p=typeof r.body=="string"?JSON.parse(r.body):r.body;return{path:n,submission:{formMethod:o,formAction:a,formEncType:r.formEncType,formData:void 0,json:p,text:void 0}}}catch{return l()}}}K(typeof FormData=="function","FormData is not available in this environment");let u,s;if(r.formData)u=Na(r.formData),s=r.formData;else if(r.body instanceof FormData)u=Na(r.body),s=r.body;else if(r.body instanceof URLSearchParams)u=r.body,s=uc(u);else if(r.body==null)u=new URLSearchParams,s=new FormData;else try{u=new URLSearchParams(r.body),s=uc(u)}catch{return l()}let d={formMethod:o,formAction:a,formEncType:r&&r.formEncType||"application/x-www-form-urlencoded",formData:s,json:void 0,text:void 0};if(ht(d.formMethod))return{path:n,submission:d};let f=hn(n);return t&&f.search&&Lu(f.search)&&u.append("index",""),f.search="?"+u,{path:zn(f),submission:d}}function rc(e,t,n){n===void 0&&(n=!1);let r=e.findIndex(l=>l.route.id===t);return r>=0?e.slice(0,n?r+1:r):e}function lc(e,t,n,r,l,i,o,a,u,s,d,f,p,w,x,E){let D=E?Je(E[1])?E[1].error:E[1].data:void 0,m=e.createURL(t.location),c=e.createURL(l),v=n;i&&t.errors?v=rc(n,Object.keys(t.errors)[0],!0):E&&Je(E[1])&&(v=rc(n,E[0]));let k=E?E[1].statusCode:void 0,L=o&&k&&k>=400,z=v.filter((C,H)=>{let{route:M}=C;if(M.lazy)return!0;if(M.loader==null)return!1;if(i)return La(M,t.loaderData,t.errors);if(Ev(t.loaderData,t.matches[H],C)||u.some(Se=>Se===C.route.id))return!0;let q=t.matches[H],re=C;return ic(C,ue({currentUrl:m,currentParams:q.params,nextUrl:c,nextParams:re.params},r,{actionResult:D,actionStatus:k,defaultShouldRevalidate:L?!1:a||m.pathname+m.search===c.pathname+c.search||m.search!==c.search||Ad(q,re)}))}),y=[];return f.forEach((C,H)=>{if(i||!n.some(_e=>_e.route.id===C.routeId)||d.has(H))return;let M=Sn(w,C.path,x);if(!M){y.push({key:H,routeId:C.routeId,path:C.path,matches:null,match:null,controller:null});return}let q=t.fetchers.get(H),re=Br(M,C.path),Se=!1;p.has(H)?Se=!1:s.has(H)?(s.delete(H),Se=!0):q&&q.state!=="idle"&&q.data===void 0?Se=a:Se=ic(re,ue({currentUrl:m,currentParams:t.matches[t.matches.length-1].params,nextUrl:c,nextParams:n[n.length-1].params},r,{actionResult:D,actionStatus:k,defaultShouldRevalidate:L?!1:a})),Se&&y.push({key:H,routeId:C.routeId,path:C.path,matches:M,match:re,controller:new AbortController})}),[z,y]}function La(e,t,n){if(e.lazy)return!0;if(!e.loader)return!1;let r=t!=null&&t[e.id]!==void 0,l=n!=null&&n[e.id]!==void 0;return!r&&l?!1:typeof e.loader=="function"&&e.loader.hydrate===!0?!0:!r&&!l}function Ev(e,t,n){let r=!t||n.route.id!==t.route.id,l=e[n.route.id]===void 0;return r||l}function Ad(e,t){let n=e.route.path;return e.pathname!==t.pathname||n!=null&&n.endsWith("*")&&e.params["*"]!==t.params["*"]}function ic(e,t){if(e.route.shouldRevalidate){let n=e.route.shouldRevalidate(t);if(typeof n=="boolean")return n}return t.defaultShouldRevalidate}function oc(e,t,n,r,l){var i;let o;if(e){let s=r[e];K(s,"No route found to patch children into: routeId = "+e),s.children||(s.children=[]),o=s.children}else o=n;let a=t.filter(s=>!o.some(d=>Bd(s,d))),u=Di(a,l,[e||"_","patch",String(((i=o)==null?void 0:i.length)||"0")],r);o.push(...u)}function Bd(e,t){return"id"in e&&"id"in t&&e.id===t.id?!0:e.index===t.index&&e.path===t.path&&e.caseSensitive===t.caseSensitive?(!e.children||e.children.length===0)&&(!t.children||t.children.length===0)?!0:e.children.every((n,r)=>{var l;return(l=t.children)==null?void 0:l.some(i=>Bd(n,i))}):!1}async function xv(e,t,n){if(!e.lazy)return;let r=await e.lazy();if(!e.lazy)return;let l=n[e.id];K(l,"No route found in manifest");let i={};for(let o in r){let u=l[o]!==void 0&&o!=="hasErrorBoundary";jn(!u,'Route "'+l.id+'" has a static property "'+o+'" defined but its lazy function is also returning a value for this property. '+('The lazy route property "'+o+'" will be ignored.')),!u&&!Qm.has(o)&&(i[o]=r[o])}Object.assign(l,i),Object.assign(l,ue({},t(l),{lazy:void 0}))}async function kv(e){let{matches:t}=e,n=t.filter(l=>l.shouldLoad);return(await Promise.all(n.map(l=>l.resolve()))).reduce((l,i,o)=>Object.assign(l,{[n[o].route.id]:i}),{})}async function Cv(e,t,n,r,l,i,o,a,u,s){let d=i.map(w=>w.route.lazy?xv(w.route,u,a):void 0),f=i.map((w,x)=>{let E=d[x],D=l.some(c=>c.route.id===w.route.id);return ue({},w,{shouldLoad:D,resolve:async c=>(c&&r.method==="GET"&&(w.route.lazy||w.route.loader)&&(D=!0),D?Pv(t,r,w,E,c,s):Promise.resolve({type:b.data,result:void 0}))})}),p=await e({matches:f,request:r,params:i[0].params,fetcherKey:o,context:s});try{await Promise.all(d)}catch{}return p}async function Pv(e,t,n,r,l,i){let o,a,u=s=>{let d,f=new Promise((x,E)=>d=E);a=()=>d(),t.signal.addEventListener("abort",a);let p=x=>typeof s!="function"?Promise.reject(new Error("You cannot call the handler for a route which defines a boolean "+('"'+e+'" [routeId: '+n.route.id+"]"))):s({request:t,params:n.params,context:i},...x!==void 0?[x]:[]),w=(async()=>{try{return{type:"data",result:await(l?l(E=>p(E)):p())}}catch(x){return{type:"error",result:x}}})();return Promise.race([w,f])};try{let s=n.route[e];if(r)if(s){let d,[f]=await Promise.all([u(s).catch(p=>{d=p}),r]);if(d!==void 0)throw d;o=f}else if(await r,s=n.route[e],s)o=await u(s);else if(e==="action"){let d=new URL(t.url),f=d.pathname+d.search;throw Ve(405,{method:t.method,pathname:f,routeId:n.route.id})}else return{type:b.data,result:void 0};else if(s)o=await u(s);else{let d=new URL(t.url),f=d.pathname+d.search;throw Ve(404,{pathname:f})}K(o.result!==void 0,"You defined "+(e==="action"?"an action":"a loader")+" for route "+('"'+n.route.id+"\" but didn't return anything from your `"+e+"` ")+"function. Please return a value or `null`.")}catch(s){return{type:b.error,result:s}}finally{a&&t.signal.removeEventListener("abort",a)}return o}async function _v(e){let{result:t,type:n}=e;if(Vd(t)){let f;try{let p=t.headers.get("Content-Type");p&&/\bapplication\/json\b/.test(p)?t.body==null?f=null:f=await t.json():f=await t.text()}catch(p){return{type:b.error,error:p}}return n===b.error?{type:b.error,error:new ji(t.status,t.statusText,f),statusCode:t.status,headers:t.headers}:{type:b.data,data:f,statusCode:t.status,headers:t.headers}}if(n===b.error){if(pc(t)){var r,l;if(t.data instanceof Error){var i,o;return{type:b.error,error:t.data,statusCode:(i=t.init)==null?void 0:i.status,headers:(o=t.init)!=null&&o.headers?new Headers(t.init.headers):void 0}}return{type:b.error,error:new ji(((r=t.init)==null?void 0:r.status)||500,void 0,t.data),statusCode:ml(t)?t.status:void 0,headers:(l=t.init)!=null&&l.headers?new Headers(t.init.headers):void 0}}return{type:b.error,error:t,statusCode:ml(t)?t.status:void 0}}if(Dv(t)){var a,u;return{type:b.deferred,deferredData:t,statusCode:(a=t.init)==null?void 0:a.status,headers:((u=t.init)==null?void 0:u.headers)&&new Headers(t.init.headers)}}if(pc(t)){var s,d;return{type:b.data,data:t.data,statusCode:(s=t.init)==null?void 0:s.status,headers:(d=t.init)!=null&&d.headers?new Headers(t.init.headers):void 0}}return{type:b.data,data:t}}function Rv(e,t,n,r,l,i){let o=e.headers.get("Location");if(K(o,"Redirects returned/thrown from loaders/actions must have a Location header"),!_u.test(o)){let a=r.slice(0,r.findIndex(u=>u.route.id===n)+1);o=Ra(new URL(t.url),a,l,!0,o,i),e.headers.set("Location",o)}return e}function ac(e,t,n,r){let l=["about:","blob:","chrome:","chrome-untrusted:","content:","data:","devtools:","file:","filesystem:","javascript:"];if(_u.test(e)){let i=e,o=i.startsWith("//")?new URL(t.protocol+i):new URL(i);if(l.includes(o.protocol))throw new Error("Invalid redirect location");let a=It(o.pathname,n)!=null;if(o.origin===t.origin&&a)return o.pathname+o.search+o.hash}try{let i=r.createURL(e);if(l.includes(i.protocol))throw new Error("Invalid redirect location")}catch{}return e}function $n(e,t,n,r){let l=e.createURL(Hd(t)).toString(),i={signal:n};if(r&&ht(r.formMethod)){let{formMethod:o,formEncType:a}=r;i.method=o.toUpperCase(),a==="application/json"?(i.headers=new Headers({"Content-Type":a}),i.body=JSON.stringify(r.json)):a==="text/plain"?i.body=r.text:a==="application/x-www-form-urlencoded"&&r.formData?i.body=Na(r.formData):i.body=r.formData}return new Request(l,i)}function Na(e){let t=new URLSearchParams;for(let[n,r]of e.entries())t.append(n,typeof r=="string"?r:r.name);return t}function uc(e){let t=new FormData;for(let[n,r]of e.entries())t.append(n,r);return t}function Lv(e,t,n,r,l){let i={},o=null,a,u=!1,s={},d=n&&Je(n[1])?n[1].error:void 0;return e.forEach(f=>{if(!(f.route.id in t))return;let p=f.route.id,w=t[p];if(K(!Pn(w),"Cannot handle redirect results in processLoaderData"),Je(w)){let x=w.error;d!==void 0&&(x=d,d=void 0),o=o||{};{let E=En(e,p);o[E.route.id]==null&&(o[E.route.id]=x)}i[p]=void 0,u||(u=!0,a=ml(w.error)?w.error.status:500),w.headers&&(s[p]=w.headers)}else bt(w)?(r.set(p,w.deferredData),i[p]=w.deferredData.data,w.statusCode!=null&&w.statusCode!==200&&!u&&(a=w.statusCode),w.headers&&(s[p]=w.headers)):(i[p]=w.data,w.statusCode&&w.statusCode!==200&&!u&&(a=w.statusCode),w.headers&&(s[p]=w.headers))}),d!==void 0&&n&&(o={[n[0]]:d},i[n[0]]=void 0),{loaderData:i,errors:o,statusCode:a||200,loaderHeaders:s}}function sc(e,t,n,r,l,i,o){let{loaderData:a,errors:u}=Lv(t,n,r,o);return l.forEach(s=>{let{key:d,match:f,controller:p}=s,w=i[d];if(K(w,"Did not find corresponding fetcher result"),!(p&&p.signal.aborted))if(Je(w)){let x=En(e.matches,f==null?void 0:f.route.id);u&&u[x.route.id]||(u=ue({},u,{[x.route.id]:w.error})),e.fetchers.delete(d)}else if(Pn(w))K(!1,"Unhandled fetcher revalidation redirect");else if(bt(w))K(!1,"Unhandled fetcher deferred data");else{let x=Kt(w.data);e.fetchers.set(d,x)}}),{loaderData:a,errors:u}}function cc(e,t,n,r){let l=ue({},t);for(let i of n){let o=i.route.id;if(t.hasOwnProperty(o)?t[o]!==void 0&&(l[o]=t[o]):e[o]!==void 0&&i.route.loader&&(l[o]=e[o]),r&&r.hasOwnProperty(o))break}return l}function fc(e){return e?Je(e[1])?{actionData:{}}:{actionData:{[e[0]]:e[1].data}}:{}}function En(e,t){return(t?e.slice(0,e.findIndex(r=>r.route.id===t)+1):[...e]).reverse().find(r=>r.route.hasErrorBoundary===!0)||e[0]}function dc(e){let t=e.length===1?e[0]:e.find(n=>n.index||!n.path||n.path==="/")||{id:"__shim-error-route__"};return{matches:[{params:{},pathname:"",pathnameBase:"",route:t}],route:t}}function Ve(e,t){let{pathname:n,routeId:r,method:l,type:i,message:o}=t===void 0?{}:t,a="Unknown Server Error",u="Unknown @remix-run/router error";return e===400?(a="Bad Request",l&&n&&r?u="You made a "+l+' request to "'+n+'" but '+('did not provide a `loader` for route "'+r+'", ')+"so there is no way to handle the request.":i==="defer-action"?u="defer() is not supported in actions":i==="invalid-body"&&(u="Unable to encode submission body")):e===403?(a="Forbidden",u='Route "'+r+'" does not match URL "'+n+'"'):e===404?(a="Not Found",u='No route matches URL "'+n+'"'):e===405&&(a="Method Not Allowed",l&&n&&r?u="You made a "+l.toUpperCase()+' request to "'+n+'" but '+('did not provide an `action` for route "'+r+'", ')+"so there is no way to handle the request.":l&&(u='Invalid request method "'+l.toUpperCase()+'"')),new ji(e||500,a,new Error(u),!0)}function Kl(e){let t=Object.entries(e);for(let n=t.length-1;n>=0;n--){let[r,l]=t[n];if(Pn(l))return{key:r,result:l}}}function Hd(e){let t=typeof e=="string"?hn(e):e;return zn(ue({},t,{hash:""}))}function Nv(e,t){return e.pathname!==t.pathname||e.search!==t.search?!1:e.hash===""?t.hash!=="":e.hash===t.hash?!0:t.hash!==""}function Tv(e){return Vd(e.result)&&mv.has(e.result.status)}function bt(e){return e.type===b.deferred}function Je(e){return e.type===b.error}function Pn(e){return(e&&e.type)===b.redirect}function pc(e){return typeof e=="object"&&e!=null&&"type"in e&&"data"in e&&"init"in e&&e.type==="DataWithResponseInit"}function Dv(e){let t=e;return t&&typeof t=="object"&&typeof t.data=="object"&&typeof t.subscribe=="function"&&typeof t.cancel=="function"&&typeof t.resolveData=="function"}function Vd(e){return e!=null&&typeof e.status=="number"&&typeof e.statusText=="string"&&typeof e.headers=="object"&&typeof e.body<"u"}function Mv(e){return hv.has(e.toLowerCase())}function ht(e){return dv.has(e.toLowerCase())}async function jv(e,t,n,r,l){let i=Object.entries(t);for(let o=0;o(p==null?void 0:p.route.id)===a);if(!s)continue;let d=r.find(p=>p.route.id===s.route.id),f=d!=null&&!Ad(d,s)&&(l&&l[s.route.id])!==void 0;bt(u)&&f&&await Ru(u,n,!1).then(p=>{p&&(t[a]=p)})}}async function zv(e,t,n){for(let r=0;r(s==null?void 0:s.route.id)===i)&&bt(a)&&(K(o,"Expected an AbortController for revalidating fetcher deferred result"),await Ru(a,o.signal,!0).then(s=>{s&&(t[l]=s)}))}}async function Ru(e,t,n){if(n===void 0&&(n=!1),!await e.deferredData.resolveData(t)){if(n)try{return{type:b.data,data:e.deferredData.unwrappedData}}catch(l){return{type:b.error,error:l}}return{type:b.data,data:e.deferredData.data}}}function Lu(e){return new URLSearchParams(e).getAll("index").some(t=>t==="")}function Br(e,t){let n=typeof t=="string"?hn(t).search:t.search;if(e[e.length-1].route.index&&Lu(n||""))return e[e.length-1];let r=Fd(e);return r[r.length-1]}function hc(e){let{formMethod:t,formAction:n,formEncType:r,text:l,formData:i,json:o}=e;if(!(!t||!n||!r)){if(l!=null)return{formMethod:t,formAction:n,formEncType:r,formData:void 0,json:void 0,text:l};if(i!=null)return{formMethod:t,formAction:n,formEncType:r,formData:i,json:void 0,text:void 0};if(o!==void 0)return{formMethod:t,formAction:n,formEncType:r,formData:void 0,json:o,text:void 0}}}function jo(e,t){return t?{state:"loading",location:e,formMethod:t.formMethod,formAction:t.formAction,formEncType:t.formEncType,formData:t.formData,json:t.json,text:t.text}:{state:"loading",location:e,formMethod:void 0,formAction:void 0,formEncType:void 0,formData:void 0,json:void 0,text:void 0}}function Ov(e,t){return{state:"submitting",location:e,formMethod:t.formMethod,formAction:t.formAction,formEncType:t.formEncType,formData:t.formData,json:t.json,text:t.text}}function zr(e,t){return e?{state:"loading",formMethod:e.formMethod,formAction:e.formAction,formEncType:e.formEncType,formData:e.formData,json:e.json,text:e.text,data:t}:{state:"loading",formMethod:void 0,formAction:void 0,formEncType:void 0,formData:void 0,json:void 0,text:void 0,data:t}}function Fv(e,t){return{state:"submitting",formMethod:e.formMethod,formAction:e.formAction,formEncType:e.formEncType,formData:e.formData,json:e.json,text:e.text,data:t?t.data:void 0}}function Kt(e){return{state:"idle",formMethod:void 0,formAction:void 0,formEncType:void 0,formData:void 0,json:void 0,text:void 0,data:e}}function Iv(e,t){try{let n=e.sessionStorage.getItem(Ud);if(n){let r=JSON.parse(n);for(let[l,i]of Object.entries(r||{}))i&&Array.isArray(i)&&t.set(l,new Set(i||[]))}}catch{}}function Uv(e,t){if(t.size>0){let n={};for(let[r,l]of t)n[r]=[...l];try{e.sessionStorage.setItem(Ud,JSON.stringify(n))}catch(r){jn(!1,"Failed to save applied view transitions in sessionStorage ("+r+").")}}}/** + * React Router v6.30.3 + * + * Copyright (c) Remix Software Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE.md file in the root directory of this source tree. + * + * @license MIT + */function zi(){return zi=Object.assign?Object.assign.bind():function(e){for(var t=1;t{a.current=!0}),_.useCallback(function(s,d){if(d===void 0&&(d={}),!a.current)return;if(typeof s=="number"){r.go(s);return}let f=qi(s,JSON.parse(o),i,d.relative==="path");e==null&&t!=="/"&&(f.pathname=f.pathname==="/"?t:Mt([t,f.pathname])),(d.replace?r.replace:r.push)(f,d.state,d)},[t,r,o,i,e])}const Hv=_.createContext(null);function Vv(e){let t=_.useContext(Bt).outlet;return t&&_.createElement(Hv.Provider,{value:e},t)}function bi(e,t){let{relative:n}=t===void 0?{}:t,{future:r}=_.useContext(At),{matches:l}=_.useContext(Bt),{pathname:i}=Sr(),o=JSON.stringify(Zi(l,r.v7_relativeSplatPath));return _.useMemo(()=>qi(e,JSON.parse(o),i,n==="path"),[e,o,i,n])}function $v(e,t,n,r){wr()||K(!1);let{navigator:l}=_.useContext(At),{matches:i}=_.useContext(Bt),o=i[i.length-1],a=o?o.params:{};o&&o.pathname;let u=o?o.pathnameBase:"/";o&&o.route;let s=Sr(),d;d=s;let f=d.pathname||"/",p=f;if(u!=="/"){let E=u.replace(/^\//,"").split("/");p="/"+f.replace(/^\//,"").split("/").slice(E.length).join("/")}let w=Sn(e,{pathname:p});return Xv(w&&w.map(E=>Object.assign({},E,{params:Object.assign({},a,E.params),pathname:Mt([u,l.encodeLocation?l.encodeLocation(E.pathname).pathname:E.pathname]),pathnameBase:E.pathnameBase==="/"?u:Mt([u,l.encodeLocation?l.encodeLocation(E.pathnameBase).pathname:E.pathnameBase])})),i,n,r)}function Wv(){let e=qv(),t=ml(e)?e.status+" "+e.statusText:e instanceof Error?e.message:JSON.stringify(e),n=e instanceof Error?e.stack:null,l={padding:"0.5rem",backgroundColor:"rgba(200,200,200, 0.5)"};return _.createElement(_.Fragment,null,_.createElement("h2",null,"Unexpected Application Error!"),_.createElement("h3",{style:{fontStyle:"italic"}},t),n?_.createElement("pre",{style:l},n):null,null)}const Qv=_.createElement(Wv,null);class Kv extends _.Component{constructor(t){super(t),this.state={location:t.location,revalidation:t.revalidation,error:t.error}}static getDerivedStateFromError(t){return{error:t}}static getDerivedStateFromProps(t,n){return n.location!==t.location||n.revalidation!=="idle"&&t.revalidation==="idle"?{error:t.error,location:t.location,revalidation:t.revalidation}:{error:t.error!==void 0?t.error:n.error,location:n.location,revalidation:t.revalidation||n.revalidation}}componentDidCatch(t,n){console.error("React Router caught the following error during render",t,n)}render(){return this.state.error!==void 0?_.createElement(Bt.Provider,{value:this.props.routeContext},_.createElement($d.Provider,{value:this.state.error,children:this.props.component})):this.props.children}}function Yv(e){let{routeContext:t,match:n,children:r}=e,l=_.useContext(El);return l&&l.static&&l.staticContext&&(n.route.errorElement||n.route.ErrorBoundary)&&(l.staticContext._deepestRenderedBoundaryId=n.route.id),_.createElement(Bt.Provider,{value:t},r)}function Xv(e,t,n,r){var l;if(t===void 0&&(t=[]),n===void 0&&(n=null),r===void 0&&(r=null),e==null){var i;if(!n)return null;if(n.errors)e=n.matches;else if((i=r)!=null&&i.v7_partialHydration&&t.length===0&&!n.initialized&&n.matches.length>0)e=n.matches;else return null}let o=e,a=(l=n)==null?void 0:l.errors;if(a!=null){let d=o.findIndex(f=>f.route.id&&(a==null?void 0:a[f.route.id])!==void 0);d>=0||K(!1),o=o.slice(0,Math.min(o.length,d+1))}let u=!1,s=-1;if(n&&r&&r.v7_partialHydration)for(let d=0;d=0?o=o.slice(0,s+1):o=[o[0]];break}}}return o.reduceRight((d,f,p)=>{let w,x=!1,E=null,D=null;n&&(w=a&&f.route.id?a[f.route.id]:void 0,E=f.route.errorElement||Qv,u&&(s<0&&p===0?(ey("route-fallback"),x=!0,D=null):s===p&&(x=!0,D=f.route.hydrateFallbackElement||null)));let m=t.concat(o.slice(0,p+1)),c=()=>{let v;return w?v=E:x?v=D:f.route.Component?v=_.createElement(f.route.Component,null):f.route.element?v=f.route.element:v=d,_.createElement(Yv,{match:f,routeContext:{outlet:d,matches:m,isDataRoute:n!=null},children:v})};return n&&(f.route.ErrorBoundary||f.route.errorElement||p===0)?_.createElement(Kv,{location:n.location,revalidation:n.revalidation,component:E,error:w,children:c(),routeContext:{outlet:null,matches:m,isDataRoute:!0}}):c()},null)}var Kd=function(e){return e.UseBlocker="useBlocker",e.UseRevalidator="useRevalidator",e.UseNavigateStable="useNavigate",e}(Kd||{}),Yd=function(e){return e.UseBlocker="useBlocker",e.UseLoaderData="useLoaderData",e.UseActionData="useActionData",e.UseRouteError="useRouteError",e.UseNavigation="useNavigation",e.UseRouteLoaderData="useRouteLoaderData",e.UseMatches="useMatches",e.UseRevalidator="useRevalidator",e.UseNavigateStable="useNavigate",e.UseRouteId="useRouteId",e}(Yd||{});function Gv(e){let t=_.useContext(El);return t||K(!1),t}function Jv(e){let t=_.useContext(Nu);return t||K(!1),t}function Zv(e){let t=_.useContext(Bt);return t||K(!1),t}function Xd(e){let t=Zv(),n=t.matches[t.matches.length-1];return n.route.id||K(!1),n.route.id}function qv(){var e;let t=_.useContext($d),n=Jv(Yd.UseRouteError),r=Xd();return t!==void 0?t:(e=n.errors)==null?void 0:e[r]}function bv(){let{router:e}=Gv(Kd.UseNavigateStable),t=Xd(),n=_.useRef(!1);return Wd(()=>{n.current=!0}),_.useCallback(function(l,i){i===void 0&&(i={}),n.current&&(typeof l=="number"?e.navigate(l):e.navigate(l,zi({fromRouteId:t},i)))},[e,t])}const mc={};function ey(e,t,n){mc[e]||(mc[e]=!0)}function ty(e,t){e==null||e.v7_startTransition,(e==null?void 0:e.v7_relativeSplatPath)===void 0&&(!t||t.v7_relativeSplatPath),t&&(t.v7_fetcherPersist,t.v7_normalizeFormMethod,t.v7_partialHydration,t.v7_skipActionErrorRevalidation)}function ny(e){let{to:t,replace:n,state:r,relative:l}=e;wr()||K(!1);let{future:i,static:o}=_.useContext(At),{matches:a}=_.useContext(Bt),{pathname:u}=Sr(),s=Qd(),d=qi(t,Zi(a,i.v7_relativeSplatPath),u,l==="path"),f=JSON.stringify(d);return _.useEffect(()=>s(JSON.parse(f),{replace:n,state:r,relative:l}),[s,f,l,n,r]),null}function ry(e){return Vv(e.context)}function ly(e){let{basename:t="/",children:n=null,location:r,navigationType:l=ge.Pop,navigator:i,static:o=!1,future:a}=e;wr()&&K(!1);let u=t.replace(/^\/*/,"/"),s=_.useMemo(()=>({basename:u,navigator:i,static:o,future:zi({v7_relativeSplatPath:!1},a)}),[u,a,i,o]);typeof r=="string"&&(r=hn(r));let{pathname:d="/",search:f="",hash:p="",state:w=null,key:x="default"}=r,E=_.useMemo(()=>{let D=It(d,u);return D==null?null:{location:{pathname:D,search:f,hash:p,state:w,key:x},navigationType:l}},[u,d,f,p,w,x,l]);return E==null?null:_.createElement(At.Provider,{value:s},_.createElement(Tu.Provider,{children:n,value:E}))}new Promise(()=>{});function iy(e){let t={hasErrorBoundary:e.ErrorBoundary!=null||e.errorElement!=null};return e.Component&&Object.assign(t,{element:_.createElement(e.Component),Component:void 0}),e.HydrateFallback&&Object.assign(t,{hydrateFallbackElement:_.createElement(e.HydrateFallback),HydrateFallback:void 0}),e.ErrorBoundary&&Object.assign(t,{errorElement:_.createElement(e.ErrorBoundary),ErrorBoundary:void 0}),t}/** + * React Router DOM v6.30.3 + * + * Copyright (c) Remix Software Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE.md file in the root directory of this source tree. + * + * @license MIT + */function mr(){return mr=Object.assign?Object.assign.bind():function(e){for(var t=1;t=0)&&(n[l]=e[l]);return n}function oy(e){return!!(e.metaKey||e.altKey||e.ctrlKey||e.shiftKey)}function ay(e,t){return e.button===0&&(!t||t==="_self")&&!oy(e)}const uy=["onClick","relative","reloadDocument","replace","state","target","to","preventScrollReset","viewTransition"],sy=["aria-current","caseSensitive","className","end","style","to","viewTransition","children"],cy="6";try{window.__reactRouterVersion=cy}catch{}function fy(e,t){return wv({basename:void 0,future:mr({},void 0,{v7_prependBasename:!0}),history:Vm({window:void 0}),hydrationData:dy(),routes:e,mapRouteProperties:iy,dataStrategy:void 0,patchRoutesOnNavigation:void 0,window:void 0}).initialize()}function dy(){var e;let t=(e=window)==null?void 0:e.__staticRouterHydrationData;return t&&t.errors&&(t=mr({},t,{errors:py(t.errors)})),t}function py(e){if(!e)return null;let t=Object.entries(e),n={};for(let[r,l]of t)if(l&&l.__type==="RouteErrorResponse")n[r]=new ji(l.status,l.statusText,l.data,l.internal===!0);else if(l&&l.__type==="Error"){if(l.__subType){let i=window[l.__subType];if(typeof i=="function")try{let o=new i(l.message);o.stack="",n[r]=o}catch{}}if(n[r]==null){let i=new Error(l.message);i.stack="",n[r]=i}}else n[r]=l;return n}const Jd=_.createContext({isTransitioning:!1}),hy=_.createContext(new Map),my="startTransition",vc=Np[my],vy="flushSync",yc=Hm[vy];function yy(e){vc?vc(e):e()}function Or(e){yc?yc(e):e()}class gy{constructor(){this.status="pending",this.promise=new Promise((t,n)=>{this.resolve=r=>{this.status==="pending"&&(this.status="resolved",t(r))},this.reject=r=>{this.status==="pending"&&(this.status="rejected",n(r))}})}}function wy(e){let{fallbackElement:t,router:n,future:r}=e,[l,i]=_.useState(n.state),[o,a]=_.useState(),[u,s]=_.useState({isTransitioning:!1}),[d,f]=_.useState(),[p,w]=_.useState(),[x,E]=_.useState(),D=_.useRef(new Map),{v7_startTransition:m}=r||{},c=_.useCallback(C=>{m?yy(C):C()},[m]),v=_.useCallback((C,H)=>{let{deletedFetchers:M,flushSync:q,viewTransitionOpts:re}=H;C.fetchers.forEach((_e,st)=>{_e.data!==void 0&&D.current.set(st,_e.data)}),M.forEach(_e=>D.current.delete(_e));let Se=n.window==null||n.window.document==null||typeof n.window.document.startViewTransition!="function";if(!re||Se){q?Or(()=>i(C)):c(()=>i(C));return}if(q){Or(()=>{p&&(d&&d.resolve(),p.skipTransition()),s({isTransitioning:!0,flushSync:!0,currentLocation:re.currentLocation,nextLocation:re.nextLocation})});let _e=n.window.document.startViewTransition(()=>{Or(()=>i(C))});_e.finished.finally(()=>{Or(()=>{f(void 0),w(void 0),a(void 0),s({isTransitioning:!1})})}),Or(()=>w(_e));return}p?(d&&d.resolve(),p.skipTransition(),E({state:C,currentLocation:re.currentLocation,nextLocation:re.nextLocation})):(a(C),s({isTransitioning:!0,flushSync:!1,currentLocation:re.currentLocation,nextLocation:re.nextLocation}))},[n.window,p,d,D,c]);_.useLayoutEffect(()=>n.subscribe(v),[n,v]),_.useEffect(()=>{u.isTransitioning&&!u.flushSync&&f(new gy)},[u]),_.useEffect(()=>{if(d&&o&&n.window){let C=o,H=d.promise,M=n.window.document.startViewTransition(async()=>{c(()=>i(C)),await H});M.finished.finally(()=>{f(void 0),w(void 0),a(void 0),s({isTransitioning:!1})}),w(M)}},[c,o,d,n.window]),_.useEffect(()=>{d&&o&&l.location.key===o.location.key&&d.resolve()},[d,p,l.location,o]),_.useEffect(()=>{!u.isTransitioning&&x&&(a(x.state),s({isTransitioning:!0,flushSync:!1,currentLocation:x.currentLocation,nextLocation:x.nextLocation}),E(void 0))},[u.isTransitioning,x]),_.useEffect(()=>{},[]);let k=_.useMemo(()=>({createHref:n.createHref,encodeLocation:n.encodeLocation,go:C=>n.navigate(C),push:(C,H,M)=>n.navigate(C,{state:H,preventScrollReset:M==null?void 0:M.preventScrollReset}),replace:(C,H,M)=>n.navigate(C,{replace:!0,state:H,preventScrollReset:M==null?void 0:M.preventScrollReset})}),[n]),L=n.basename||"/",z=_.useMemo(()=>({router:n,navigator:k,static:!1,basename:L}),[n,k,L]),y=_.useMemo(()=>({v7_relativeSplatPath:n.future.v7_relativeSplatPath}),[n.future.v7_relativeSplatPath]);return _.useEffect(()=>ty(r,n.future),[r,n.future]),_.createElement(_.Fragment,null,_.createElement(El.Provider,{value:z},_.createElement(Nu.Provider,{value:l},_.createElement(hy.Provider,{value:D.current},_.createElement(Jd.Provider,{value:u},_.createElement(ly,{basename:L,location:l.location,navigationType:l.historyAction,navigator:k,future:y},l.initialized||n.future.v7_partialHydration?_.createElement(Sy,{routes:n.routes,future:n.future,state:l}):t))))),null)}const Sy=_.memo(Ey);function Ey(e){let{routes:t,future:n,state:r}=e;return $v(t,void 0,r,n)}const xy=typeof window<"u"&&typeof window.document<"u"&&typeof window.document.createElement<"u",ky=/^(?:[a-z][a-z0-9+.-]*:|\/\/)/i,Cy=_.forwardRef(function(t,n){let{onClick:r,relative:l,reloadDocument:i,replace:o,state:a,target:u,to:s,preventScrollReset:d,viewTransition:f}=t,p=Gd(t,uy),{basename:w}=_.useContext(At),x,E=!1;if(typeof s=="string"&&ky.test(s)&&(x=s,xy))try{let v=new URL(window.location.href),k=s.startsWith("//")?new URL(v.protocol+s):new URL(s),L=It(k.pathname,w);k.origin===v.origin&&L!=null?s=L+k.search+k.hash:E=!0}catch{}let D=Av(s,{relative:l}),m=Ry(s,{replace:o,state:a,target:u,preventScrollReset:d,relative:l,viewTransition:f});function c(v){r&&r(v),v.defaultPrevented||m(v)}return _.createElement("a",mr({},p,{href:x||D,onClick:E||i?r:c,ref:n,target:u}))}),Py=_.forwardRef(function(t,n){let{"aria-current":r="page",caseSensitive:l=!1,className:i="",end:o=!1,style:a,to:u,viewTransition:s,children:d}=t,f=Gd(t,sy),p=bi(u,{relative:f.relative}),w=Sr(),x=_.useContext(Nu),{navigator:E,basename:D}=_.useContext(At),m=x!=null&&Ly(p)&&s===!0,c=E.encodeLocation?E.encodeLocation(p).pathname:p.pathname,v=w.pathname,k=x&&x.navigation&&x.navigation.location?x.navigation.location.pathname:null;l||(v=v.toLowerCase(),k=k?k.toLowerCase():null,c=c.toLowerCase()),k&&D&&(k=It(k,D)||k);const L=c!=="/"&&c.endsWith("/")?c.length-1:c.length;let z=v===c||!o&&v.startsWith(c)&&v.charAt(L)==="/",y=k!=null&&(k===c||!o&&k.startsWith(c)&&k.charAt(c.length)==="/"),C={isActive:z,isPending:y,isTransitioning:m},H=z?r:void 0,M;typeof i=="function"?M=i(C):M=[i,z?"active":null,y?"pending":null,m?"transitioning":null].filter(Boolean).join(" ");let q=typeof a=="function"?a(C):a;return _.createElement(Cy,mr({},f,{"aria-current":H,className:M,ref:n,style:q,to:u,viewTransition:s}),typeof d=="function"?d(C):d)});var Ta;(function(e){e.UseScrollRestoration="useScrollRestoration",e.UseSubmit="useSubmit",e.UseSubmitFetcher="useSubmitFetcher",e.UseFetcher="useFetcher",e.useViewTransitionState="useViewTransitionState"})(Ta||(Ta={}));var gc;(function(e){e.UseFetcher="useFetcher",e.UseFetchers="useFetchers",e.UseScrollRestoration="useScrollRestoration"})(gc||(gc={}));function _y(e){let t=_.useContext(El);return t||K(!1),t}function Ry(e,t){let{target:n,replace:r,state:l,preventScrollReset:i,relative:o,viewTransition:a}=t===void 0?{}:t,u=Qd(),s=Sr(),d=bi(e,{relative:o});return _.useCallback(f=>{if(ay(f,n)){f.preventDefault();let p=r!==void 0?r:zn(s)===zn(d);u(e,{replace:p,state:l,preventScrollReset:i,relative:o,viewTransition:a})}},[s,u,d,r,l,n,e,i,o,a])}function Ly(e,t){t===void 0&&(t={});let n=_.useContext(Jd);n==null&&K(!1);let{basename:r}=_y(Ta.useViewTransitionState),l=bi(e,{relative:t.relative});if(!n.isTransitioning)return!1;let i=It(n.currentLocation.pathname,r)||n.currentLocation.pathname,o=It(n.nextLocation.pathname,r)||n.nextLocation.pathname;return Mi(l.pathname,o)!=null||Mi(l.pathname,i)!=null}function In(e){return T.jsxs("section",{className:"page",children:[T.jsx("h1",{children:e.title}),T.jsx("p",{children:e.subtitle}),e.children]})}function Yl(e){return T.jsxs("div",{className:"card",children:[T.jsx("h3",{children:e.title}),e.children]})}async function Jr(e){var l;const t=window.localStorage.getItem("osham-admin-secret")||"",n=await fetch(e,{headers:t?{"x-osham-admin-secret":t}:{}}),r=await n.json();if(!n.ok||r.ok===!1)throw new Error(((l=r==null?void 0:r.error)==null?void 0:l.message)||`Request failed: ${n.status}`);return r.data}async function Ny(e,t){var i;const n=window.localStorage.getItem("osham-admin-secret")||"",r=await fetch(e,{method:"POST",headers:{"content-type":"application/json",...n?{"x-osham-admin-secret":n}:{}},body:JSON.stringify(t)}),l=await r.json();if(!r.ok||l.ok===!1)throw new Error(((i=l==null?void 0:l.error)==null?void 0:i.message)||`Request failed: ${r.status}`);return l.data}function Ty(){var u;const[e,t]=Ce.useState(null),[n,r]=Ce.useState(null),[l,i]=Ce.useState(null),[o,a]=Ce.useState(null);return Ce.useEffect(()=>{Promise.all([Jr("/__osham/admin/health"),Jr("/__osham/admin/metrics/summary"),Jr("/__osham/admin/startup-summary")]).then(([s,d,f])=>{t(s),r(d),i(f),a(null)}).catch(s=>a(s.message))},[]),T.jsxs(In,{title:"Dashboard",subtitle:"High-level operational overview for Osham.",children:[o?T.jsx("div",{className:"code-block",children:o}):null,T.jsxs("div",{className:"card-grid",children:[T.jsxs(Yl,{title:"Service Health",children:[T.jsxs("p",{children:["Status: ",(e==null?void 0:e.status)||"loading..."]}),T.jsxs("p",{children:["Cache: ",(e==null?void 0:e.cache.backend)||"-"]})]}),T.jsxs(Yl,{title:"Revision",children:[T.jsxs("p",{children:["Revision: ",(e==null?void 0:e.config.revision)||"loading..."]}),T.jsxs("p",{children:["Applied: ",(e==null?void 0:e.config.lastAppliedAt)||"-"]})]}),T.jsxs(Yl,{title:"Metrics Summary",children:[T.jsxs("p",{children:["Requests: ",(n==null?void 0:n.requests)??"-"]}),T.jsxs("p",{children:["Hit ratio: ",n?`${(n.hitRatio*100).toFixed(1)}%`:"-"]})]}),T.jsxs(Yl,{title:"Namespaces",children:[T.jsxs("p",{children:["Count: ",(l==null?void 0:l.namespaceCount)??"-"]}),T.jsxs("p",{children:["Metrics enabled: ",String(((u=l==null?void 0:l.features)==null?void 0:u.metrics)??!1)]})]})]})]})}function Dy(){return T.jsx(In,{title:"Config",subtitle:"Landing page for global settings and namespace editors.",children:T.jsxs("div",{className:"code-block",children:[T.jsx("strong",{children:"Planned next:"}),T.jsxs("ul",{children:[T.jsxs("li",{children:["load current config from ",T.jsx("code",{children:"/__osham/admin/config"})]}),T.jsx("li",{children:"draft editing for global config + namespaces"}),T.jsx("li",{children:"validate, save, and reload actions"})]})]})})}function My(){return T.jsx(In,{title:"Metrics",subtitle:"Namespace and aggregate cache metrics.",children:T.jsxs("div",{className:"code-block",children:["Hook into ",T.jsx("code",{children:"/__osham/admin/metrics/summary"})," and ",T.jsx("code",{children:"/__osham/admin/metrics/namespaces"}),"."]})})}function jy(){const[e,t]=Ce.useState(null),[n,r]=Ce.useState(null);return Ce.useEffect(()=>{Jr("/__osham/admin/health").then(t).catch(l=>r(l.message))},[]),T.jsxs(In,{title:"Health",subtitle:"Operational health and startup visibility.",children:[n?T.jsx("div",{className:"code-block",children:n}):null,T.jsx("div",{className:"code-block",children:T.jsx("pre",{children:JSON.stringify(e,null,2)})})]})}function zy(){const[e,t]=Ce.useState("**"),[n,r]=Ce.useState(!0),[l,i]=Ce.useState(null),[o,a]=Ce.useState(null);async function u(){try{const s=await Ny("/__osham/admin/purge",{pattern:e,dryRun:n});i(s),a(null)}catch(s){a(s instanceof Error?s.message:"Request failed")}}return T.jsxs(In,{title:"Purge",subtitle:"Safe cache invalidation tools.",children:[T.jsxs("div",{className:"toolbar",children:[T.jsx("input",{className:"input",value:e,onChange:s=>t(s.target.value)}),T.jsxs("label",{children:[T.jsx("input",{type:"checkbox",checked:n,onChange:s=>r(s.target.checked)})," dry run"]}),T.jsx("button",{className:"button",onClick:u,children:"Run Purge"})]}),o?T.jsx("div",{className:"code-block",children:o}):null,l?T.jsx("pre",{className:"code-block",children:JSON.stringify(l,null,2)}):null]})}function Oy(){const[e,t]=Ce.useState([]),[n,r]=Ce.useState(null);return Ce.useEffect(()=>{Jr("/__osham/admin/audit").then(t).catch(l=>r(l.message))},[]),T.jsxs(In,{title:"Audit",subtitle:"Recent admin actions.",children:[n?T.jsx("div",{className:"code-block",children:n}):null,T.jsxs("table",{className:"table",children:[T.jsx("thead",{children:T.jsxs("tr",{children:[T.jsx("th",{children:"Time"}),T.jsx("th",{children:"Action"}),T.jsx("th",{children:"Result"}),T.jsx("th",{children:"Actor"})]})}),T.jsx("tbody",{children:e.map((l,i)=>T.jsxs("tr",{children:[T.jsx("td",{children:l.time}),T.jsx("td",{children:l.action}),T.jsx("td",{children:l.result}),T.jsx("td",{children:l.actor})]},`${l.time}-${i}`))})]})]})}function Fy(){return T.jsx(In,{title:"Not Found",subtitle:"That route does not exist."})}const Iy=[["/admin/dashboard","Dashboard"],["/admin/config","Config"],["/admin/metrics","Metrics"],["/admin/health","Health"],["/admin/purge","Purge"],["/admin/audit","Audit"]];function Uy(){return T.jsxs("aside",{className:"sidebar",children:[T.jsx("div",{className:"brand",children:"Osham Admin"}),T.jsx("nav",{className:"nav-list",children:Iy.map(([e,t])=>T.jsx(Py,{to:e,className:({isActive:n})=>`nav-link${n?" active":""}`,children:t},e))})]})}function Ay(){const[e,t]=Ce.useState(()=>window.localStorage.getItem("osham-admin-secret")||"");function n(){window.localStorage.setItem("osham-admin-secret",e)}return T.jsxs("div",{className:"toolbar",children:[T.jsx("input",{className:"input",type:"password",placeholder:"x-osham-admin-secret",value:e,onChange:r=>t(r.target.value)}),T.jsx("button",{className:"button",onClick:n,children:"Save Secret"})]})}function By(){return T.jsxs("div",{className:"app-shell",children:[T.jsx(Uy,{}),T.jsxs("main",{className:"content-shell",children:[T.jsx(Ay,{}),T.jsx(ry,{})]})]})}const Hy=fy([{path:"/",element:T.jsx(By,{}),children:[{index:!0,element:T.jsx(ny,{to:"/admin/dashboard",replace:!0})},{path:"/admin/dashboard",element:T.jsx(Ty,{})},{path:"/admin/config",element:T.jsx(Dy,{})},{path:"/admin/metrics",element:T.jsx(My,{})},{path:"/admin/health",element:T.jsx(jy,{})},{path:"/admin/purge",element:T.jsx(zy,{})},{path:"/admin/audit",element:T.jsx(Oy,{})},{path:"*",element:T.jsx(Fy,{})}]}]);zo.createRoot(document.getElementById("root")).render(T.jsx(Ce.StrictMode,{children:T.jsx(wy,{router:Hy})})); diff --git a/admin-ui/dist/index.html b/admin-ui/dist/index.html index 13c7016..0890f7b 100644 --- a/admin-ui/dist/index.html +++ b/admin-ui/dist/index.html @@ -4,8 +4,8 @@ Osham Admin - - + +
diff --git a/admin-ui/src/api.ts b/admin-ui/src/api.ts new file mode 100644 index 0000000..baec924 --- /dev/null +++ b/admin-ui/src/api.ts @@ -0,0 +1,30 @@ +export async function apiGet(path: string): Promise { + const secret = window.localStorage.getItem('osham-admin-secret') || ''; + const res = await fetch(path, { + headers: secret ? { 'x-osham-admin-secret': secret } : {}, + }); + + const body = await res.json(); + if (!res.ok || body.ok === false) { + throw new Error(body?.error?.message || `Request failed: ${res.status}`); + } + return body.data as T; +} + +export async function apiPost(path: string, payload: unknown): Promise { + const secret = window.localStorage.getItem('osham-admin-secret') || ''; + const res = await fetch(path, { + method: 'POST', + headers: { + 'content-type': 'application/json', + ...(secret ? { 'x-osham-admin-secret': secret } : {}), + }, + body: JSON.stringify(payload), + }); + + const body = await res.json(); + if (!res.ok || body.ok === false) { + throw new Error(body?.error?.message || `Request failed: ${res.status}`); + } + return body.data as T; +} diff --git a/admin-ui/src/router.tsx b/admin-ui/src/router.tsx index 0285143..47afd4f 100644 --- a/admin-ui/src/router.tsx +++ b/admin-ui/src/router.tsx @@ -8,12 +8,14 @@ import { PurgePage } from './screens/PurgePage'; import { AuditPage } from './screens/AuditPage'; import { NotFoundPage } from './screens/NotFoundPage'; import { Sidebar } from './ui/Sidebar'; +import { AdminSecretBar } from './ui/AdminSecretBar'; function Layout() { return (
+
diff --git a/admin-ui/src/screens/AuditPage.tsx b/admin-ui/src/screens/AuditPage.tsx index cdab84b..1a58f30 100644 --- a/admin-ui/src/screens/AuditPage.tsx +++ b/admin-ui/src/screens/AuditPage.tsx @@ -1,10 +1,41 @@ import React from 'react'; import { Page } from '../ui/Page'; +import { apiGet } from '../api'; +import { AuditEvent } from '../types'; export function AuditPage() { + const [events, setEvents] = React.useState([]); + const [error, setError] = React.useState(null); + + React.useEffect(() => { + apiGet('/__osham/admin/audit') + .then(setEvents) + .catch(err => setError(err.message)); + }, []); + return ( -
Next step: table view over /__osham/admin/audit.
+ {error ?
{error}
: null} + + + + + + + + + + + {events.map((event, index) => ( + + + + + + + ))} + +
TimeActionResultActor
{event.time}{event.action}{event.result}{event.actor}
); } diff --git a/admin-ui/src/screens/DashboardPage.tsx b/admin-ui/src/screens/DashboardPage.tsx index 3102498..e89fd3d 100644 --- a/admin-ui/src/screens/DashboardPage.tsx +++ b/admin-ui/src/screens/DashboardPage.tsx @@ -1,22 +1,54 @@ import React from 'react'; import { Page } from '../ui/Page'; import { Card } from '../ui/Card'; +import { apiGet } from '../api'; +import { HealthResponse, MetricsSummary } from '../types'; + +interface StartupSummary { + namespaceCount: number; + features: Record; +} export function DashboardPage() { + const [health, setHealth] = React.useState(null); + const [metrics, setMetrics] = React.useState(null); + const [startup, setStartup] = React.useState(null); + const [error, setError] = React.useState(null); + + React.useEffect(() => { + Promise.all([ + apiGet('/__osham/admin/health'), + apiGet('/__osham/admin/metrics/summary'), + apiGet('/__osham/admin/startup-summary'), + ]) + .then(([healthData, metricsData, startupData]) => { + setHealth(healthData); + setMetrics(metricsData); + setStartup(startupData); + setError(null); + }) + .catch(err => setError(err.message)); + }, []); + return ( + {error ?
{error}
: null}
-

Wire to /__osham/admin/health.

+

Status: {health?.status || 'loading...'}

+

Cache: {health?.cache.backend || '-'}

- -

Wire to /__osham/admin/startup-summary.

+ +

Revision: {health?.config.revision || 'loading...'}

+

Applied: {health?.config.lastAppliedAt || '-'}

-

Wire to /__osham/admin/metrics/summary.

+

Requests: {metrics?.requests ?? '-'}

+

Hit ratio: {metrics ? `${(metrics.hitRatio * 100).toFixed(1)}%` : '-'}

- -

Show current config revision and apply state.

+ +

Count: {startup?.namespaceCount ?? '-'}

+

Metrics enabled: {String(startup?.features?.metrics ?? false)}

diff --git a/admin-ui/src/screens/HealthPage.tsx b/admin-ui/src/screens/HealthPage.tsx index ce669fa..30c2c86 100644 --- a/admin-ui/src/screens/HealthPage.tsx +++ b/admin-ui/src/screens/HealthPage.tsx @@ -1,10 +1,24 @@ import React from 'react'; import { Page } from '../ui/Page'; +import { apiGet } from '../api'; +import { HealthResponse } from '../types'; export function HealthPage() { + const [data, setData] = React.useState(null); + const [error, setError] = React.useState(null); + + React.useEffect(() => { + apiGet('/__osham/admin/health') + .then(setData) + .catch(err => setError(err.message)); + }, []); + return ( -
Surface cache backend, uptime, revision, and feature flags here.
+ {error ?
{error}
: null} +
+
{JSON.stringify(data, null, 2)}
+
); } diff --git a/admin-ui/src/screens/PurgePage.tsx b/admin-ui/src/screens/PurgePage.tsx index 49c034f..8b9de1d 100644 --- a/admin-ui/src/screens/PurgePage.tsx +++ b/admin-ui/src/screens/PurgePage.tsx @@ -1,10 +1,43 @@ import React from 'react'; import { Page } from '../ui/Page'; +import { apiPost } from '../api'; + +interface PurgeResponse { + purged: boolean; + dryRun: boolean; + deleted: number; + warnings: string[]; +} export function PurgePage() { + const [pattern, setPattern] = React.useState('**'); + const [dryRun, setDryRun] = React.useState(true); + const [result, setResult] = React.useState(null); + const [error, setError] = React.useState(null); + + async function submit() { + try { + const data = await apiPost('/__osham/admin/purge', { pattern, dryRun }); + setResult(data); + setError(null); + } catch (err) { + setError(err instanceof Error ? err.message : 'Request failed'); + } + } + return ( -
Next step: connect form to POST /__osham/admin/purge with warning display and dry-run support.
+
+ setPattern(e.target.value)} /> + + +
+ {error ?
{error}
: null} + {result ?
{JSON.stringify(result, null, 2)}
: null}
); } diff --git a/admin-ui/src/styles.css b/admin-ui/src/styles.css index 362d0ec..5171f24 100644 --- a/admin-ui/src/styles.css +++ b/admin-ui/src/styles.css @@ -60,6 +60,46 @@ body { padding: 28px; } +.toolbar { + display: flex; + gap: 12px; + align-items: center; + margin-bottom: 20px; + flex-wrap: wrap; +} + +.input, +.button, +.textarea { + border: 1px solid #31415e; + background: #0f172a; + color: #e7ecf3; + border-radius: 10px; + padding: 10px 12px; +} + +.input { + min-width: 280px; +} + +.button { + cursor: pointer; +} + +.table { + width: 100%; + border-collapse: collapse; + margin-top: 16px; +} + +.table th, +.table td { + border-bottom: 1px solid #22304a; + padding: 10px; + text-align: left; + vertical-align: top; +} + .page { max-width: 1080px; } diff --git a/admin-ui/src/types.ts b/admin-ui/src/types.ts new file mode 100644 index 0000000..0455632 --- /dev/null +++ b/admin-ui/src/types.ts @@ -0,0 +1,31 @@ +export interface HealthResponse { + status: string; + uptimeSeconds: number; + cache: { + status: string; + backend: string; + }; + config: { + loaded: boolean; + revision: string | null; + source?: string; + lastAppliedAt?: string | null; + }; +} + +export interface MetricsSummary { + requests: number; + cacheHits: number; + cacheMisses: number; + hitRatio: number; + pooledRequests: number; + cacheSizeBytes: number; +} + +export interface AuditEvent { + time: string; + action: string; + actor: string; + result: string; + details?: Record; +} diff --git a/admin-ui/src/ui/AdminSecretBar.tsx b/admin-ui/src/ui/AdminSecretBar.tsx new file mode 100644 index 0000000..d5fe7d5 --- /dev/null +++ b/admin-ui/src/ui/AdminSecretBar.tsx @@ -0,0 +1,24 @@ +import React from 'react'; + +export function AdminSecretBar() { + const [value, setValue] = React.useState(() => window.localStorage.getItem('osham-admin-secret') || ''); + + function save() { + window.localStorage.setItem('osham-admin-secret', value); + } + + return ( +
+ setValue(e.target.value)} + /> + +
+ ); +} diff --git a/admin-ui/tsconfig.tsbuildinfo b/admin-ui/tsconfig.tsbuildinfo index 2ca0631..51bd270 100644 --- a/admin-ui/tsconfig.tsbuildinfo +++ b/admin-ui/tsconfig.tsbuildinfo @@ -1 +1 @@ -{"root":["./src/main.tsx","./src/router.tsx","./src/screens/AuditPage.tsx","./src/screens/ConfigPage.tsx","./src/screens/DashboardPage.tsx","./src/screens/HealthPage.tsx","./src/screens/MetricsPage.tsx","./src/screens/NotFoundPage.tsx","./src/screens/PurgePage.tsx","./src/ui/Card.tsx","./src/ui/Page.tsx","./src/ui/Sidebar.tsx"],"version":"5.9.3"} \ No newline at end of file +{"root":["./src/api.ts","./src/main.tsx","./src/router.tsx","./src/types.ts","./src/screens/AuditPage.tsx","./src/screens/ConfigPage.tsx","./src/screens/DashboardPage.tsx","./src/screens/HealthPage.tsx","./src/screens/MetricsPage.tsx","./src/screens/NotFoundPage.tsx","./src/screens/PurgePage.tsx","./src/ui/AdminSecretBar.tsx","./src/ui/Card.tsx","./src/ui/Page.tsx","./src/ui/Sidebar.tsx"],"version":"5.9.3"} \ No newline at end of file From e81352ea8eafd4b7104fa43e0e549cb9f2667503 Mon Sep 17 00:00:00 2001 From: Ajay Singh Date: Mon, 23 Mar 2026 19:04:02 +0000 Subject: [PATCH 14/50] feat(admin-ui): finish config editor integration --- .gitignore | 7 + admin-ui/dist/assets/index-Brm5M7Pt.css | 1 - admin-ui/dist/assets/index-CtLrP-Jx.js | 68 ---- admin-ui/dist/index.html | 13 - admin-ui/package.json | 3 +- admin-ui/src/api.ts | 45 ++- admin-ui/src/screens/ConfigPage.tsx | 494 +++++++++++++++++++++++- admin-ui/src/styles.css | 59 +++ admin-ui/src/types.ts | 63 +++ tsconfig.json | 2 +- 10 files changed, 647 insertions(+), 108 deletions(-) delete mode 100644 admin-ui/dist/assets/index-Brm5M7Pt.css delete mode 100644 admin-ui/dist/assets/index-CtLrP-Jx.js delete mode 100644 admin-ui/dist/index.html diff --git a/.gitignore b/.gitignore index b27cb80..069b65d 100644 --- a/.gitignore +++ b/.gitignore @@ -62,6 +62,13 @@ typings/ lib +admin-ui/dist +admin-ui/src/**/*.js +admin-ui/src/**/*.d.ts +admin-ui/vite.config.js +admin-ui/vite.config.d.ts +admin-ui/tsconfig.tsbuildinfo + cache-config.yml .vscode diff --git a/admin-ui/dist/assets/index-Brm5M7Pt.css b/admin-ui/dist/assets/index-Brm5M7Pt.css deleted file mode 100644 index 2edd367..0000000 --- a/admin-ui/dist/assets/index-Brm5M7Pt.css +++ /dev/null @@ -1 +0,0 @@ -:root{font-family:Inter,system-ui,sans-serif;color:#e7ecf3;background:#0b1220}*{box-sizing:border-box}body{margin:0;min-width:320px;min-height:100vh;background:linear-gradient(180deg,#0b1220,#111827)}#root{min-height:100vh}.app-shell{display:grid;grid-template-columns:240px 1fr;min-height:100vh}.sidebar{border-right:1px solid #22304a;padding:24px 18px;background:#080f1ceb}.brand{font-size:1.2rem;font-weight:700;margin-bottom:20px}.nav-list{display:flex;flex-direction:column;gap:10px}.nav-link{color:#c2d1e6;text-decoration:none;padding:10px 12px;border-radius:10px}.nav-link.active,.nav-link:hover{background:#1d4ed8;color:#fff}.content-shell{padding:28px}.toolbar{display:flex;gap:12px;align-items:center;margin-bottom:20px;flex-wrap:wrap}.input,.button,.textarea{border:1px solid #31415e;background:#0f172a;color:#e7ecf3;border-radius:10px;padding:10px 12px}.input{min-width:280px}.button{cursor:pointer}.table{width:100%;border-collapse:collapse;margin-top:16px}.table th,.table td{border-bottom:1px solid #22304a;padding:10px;text-align:left;vertical-align:top}.page{max-width:1080px}.page h1{margin-top:0;margin-bottom:8px}.page p{color:#a8b4c7}.card-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:16px;margin-top:24px}.card{background:#111827e0;border:1px solid #22304a;border-radius:16px;padding:18px}.card h3{margin-top:0}.code-block{margin-top:16px;padding:16px;background:#0f172a;border:1px solid #22304a;border-radius:12px;overflow:auto}@media (max-width: 820px){.app-shell{grid-template-columns:1fr}.sidebar{border-right:0;border-bottom:1px solid #22304a}} diff --git a/admin-ui/dist/assets/index-CtLrP-Jx.js b/admin-ui/dist/assets/index-CtLrP-Jx.js deleted file mode 100644 index abd2720..0000000 --- a/admin-ui/dist/assets/index-CtLrP-Jx.js +++ /dev/null @@ -1,68 +0,0 @@ -function wc(e,t){for(var n=0;nr[l]})}}}return Object.freeze(Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}))}(function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const l of document.querySelectorAll('link[rel="modulepreload"]'))r(l);new MutationObserver(l=>{for(const i of l)if(i.type==="childList")for(const o of i.addedNodes)o.tagName==="LINK"&&o.rel==="modulepreload"&&r(o)}).observe(document,{childList:!0,subtree:!0});function n(l){const i={};return l.integrity&&(i.integrity=l.integrity),l.referrerPolicy&&(i.referrerPolicy=l.referrerPolicy),l.crossOrigin==="use-credentials"?i.credentials="include":l.crossOrigin==="anonymous"?i.credentials="omit":i.credentials="same-origin",i}function r(l){if(l.ep)return;l.ep=!0;const i=n(l);fetch(l.href,i)}})();function Sc(e){return e&&e.__esModule&&Object.prototype.hasOwnProperty.call(e,"default")?e.default:e}var Ec={exports:{}},Oi={},xc={exports:{}},Y={};/** - * @license React - * react.production.min.js - * - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */var vl=Symbol.for("react.element"),hp=Symbol.for("react.portal"),mp=Symbol.for("react.fragment"),vp=Symbol.for("react.strict_mode"),yp=Symbol.for("react.profiler"),gp=Symbol.for("react.provider"),wp=Symbol.for("react.context"),Sp=Symbol.for("react.forward_ref"),Ep=Symbol.for("react.suspense"),xp=Symbol.for("react.memo"),kp=Symbol.for("react.lazy"),$u=Symbol.iterator;function Cp(e){return e===null||typeof e!="object"?null:(e=$u&&e[$u]||e["@@iterator"],typeof e=="function"?e:null)}var kc={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},Cc=Object.assign,Pc={};function vr(e,t,n){this.props=e,this.context=t,this.refs=Pc,this.updater=n||kc}vr.prototype.isReactComponent={};vr.prototype.setState=function(e,t){if(typeof e!="object"&&typeof e!="function"&&e!=null)throw Error("setState(...): takes an object of state variables to update or a function which returns an object of state variables.");this.updater.enqueueSetState(this,e,t,"setState")};vr.prototype.forceUpdate=function(e){this.updater.enqueueForceUpdate(this,e,"forceUpdate")};function _c(){}_c.prototype=vr.prototype;function Da(e,t,n){this.props=e,this.context=t,this.refs=Pc,this.updater=n||kc}var Ma=Da.prototype=new _c;Ma.constructor=Da;Cc(Ma,vr.prototype);Ma.isPureReactComponent=!0;var Wu=Array.isArray,Rc=Object.prototype.hasOwnProperty,ja={current:null},Lc={key:!0,ref:!0,__self:!0,__source:!0};function Nc(e,t,n){var r,l={},i=null,o=null;if(t!=null)for(r in t.ref!==void 0&&(o=t.ref),t.key!==void 0&&(i=""+t.key),t)Rc.call(t,r)&&!Lc.hasOwnProperty(r)&&(l[r]=t[r]);var a=arguments.length-2;if(a===1)l.children=n;else if(1>>1,ne=N[te];if(0>>1;tel(Oe,W))Fel(nt,Oe)?(N[te]=nt,N[Fe]=W,te=Fe):(N[te]=Oe,N[Xe]=W,te=Xe);else if(Fel(nt,W))N[te]=nt,N[Fe]=W,te=Fe;else break e}}return V}function l(N,V){var W=N.sortIndex-V.sortIndex;return W!==0?W:N.id-V.id}if(typeof performance=="object"&&typeof performance.now=="function"){var i=performance;e.unstable_now=function(){return i.now()}}else{var o=Date,a=o.now();e.unstable_now=function(){return o.now()-a}}var u=[],s=[],d=1,f=null,p=3,w=!1,x=!1,E=!1,D=typeof setTimeout=="function"?setTimeout:null,m=typeof clearTimeout=="function"?clearTimeout:null,c=typeof setImmediate<"u"?setImmediate:null;typeof navigator<"u"&&navigator.scheduling!==void 0&&navigator.scheduling.isInputPending!==void 0&&navigator.scheduling.isInputPending.bind(navigator.scheduling);function v(N){for(var V=n(s);V!==null;){if(V.callback===null)r(s);else if(V.startTime<=N)r(s),V.sortIndex=V.expirationTime,t(u,V);else break;V=n(s)}}function k(N){if(E=!1,v(N),!x)if(n(u)!==null)x=!0,Ht(L);else{var V=n(s);V!==null&&Vt(k,V.startTime-N)}}function L(N,V){x=!1,E&&(E=!1,m(C),C=-1),w=!0;var W=p;try{for(v(V),f=n(u);f!==null&&(!(f.expirationTime>V)||N&&!q());){var te=f.callback;if(typeof te=="function"){f.callback=null,p=f.priorityLevel;var ne=te(f.expirationTime<=V);V=e.unstable_now(),typeof ne=="function"?f.callback=ne:f===n(u)&&r(u),v(V)}else r(u);f=n(u)}if(f!==null)var ct=!0;else{var Xe=n(s);Xe!==null&&Vt(k,Xe.startTime-V),ct=!1}return ct}finally{f=null,p=W,w=!1}}var z=!1,y=null,C=-1,H=5,M=-1;function q(){return!(e.unstable_now()-MN||125te?(N.sortIndex=W,t(s,N),n(u)===null&&N===n(s)&&(E?(m(C),C=-1):E=!0,Vt(k,W-te))):(N.sortIndex=ne,t(u,N),x||w||(x=!0,Ht(L))),N},e.unstable_shouldYield=q,e.unstable_wrapCallback=function(N){var V=p;return function(){var W=p;p=V;try{return N.apply(this,arguments)}finally{p=W}}}})(zc);jc.exports=zc;var Fp=jc.exports;/** - * @license React - * react-dom.production.min.js - * - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */var Ip=_,be=Fp;function R(e){for(var t="https://reactjs.org/docs/error-decoder.html?invariant="+e,n=1;n"u"||typeof window.document>"u"||typeof window.document.createElement>"u"),Oo=Object.prototype.hasOwnProperty,Up=/^[:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD][:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD\-.0-9\u00B7\u0300-\u036F\u203F-\u2040]*$/,Ku={},Yu={};function Ap(e){return Oo.call(Yu,e)?!0:Oo.call(Ku,e)?!1:Up.test(e)?Yu[e]=!0:(Ku[e]=!0,!1)}function Bp(e,t,n,r){if(n!==null&&n.type===0)return!1;switch(typeof t){case"function":case"symbol":return!0;case"boolean":return r?!1:n!==null?!n.acceptsBooleans:(e=e.toLowerCase().slice(0,5),e!=="data-"&&e!=="aria-");default:return!1}}function Hp(e,t,n,r){if(t===null||typeof t>"u"||Bp(e,t,n,r))return!0;if(r)return!1;if(n!==null)switch(n.type){case 3:return!t;case 4:return t===!1;case 5:return isNaN(t);case 6:return isNaN(t)||1>t}return!1}function He(e,t,n,r,l,i,o){this.acceptsBooleans=t===2||t===3||t===4,this.attributeName=r,this.attributeNamespace=l,this.mustUseProperty=n,this.propertyName=e,this.type=t,this.sanitizeURL=i,this.removeEmptyString=o}var Te={};"children dangerouslySetInnerHTML defaultValue defaultChecked innerHTML suppressContentEditableWarning suppressHydrationWarning style".split(" ").forEach(function(e){Te[e]=new He(e,0,!1,e,null,!1,!1)});[["acceptCharset","accept-charset"],["className","class"],["htmlFor","for"],["httpEquiv","http-equiv"]].forEach(function(e){var t=e[0];Te[t]=new He(t,1,!1,e[1],null,!1,!1)});["contentEditable","draggable","spellCheck","value"].forEach(function(e){Te[e]=new He(e,2,!1,e.toLowerCase(),null,!1,!1)});["autoReverse","externalResourcesRequired","focusable","preserveAlpha"].forEach(function(e){Te[e]=new He(e,2,!1,e,null,!1,!1)});"allowFullScreen async autoFocus autoPlay controls default defer disabled disablePictureInPicture disableRemotePlayback formNoValidate hidden loop noModule noValidate open playsInline readOnly required reversed scoped seamless itemScope".split(" ").forEach(function(e){Te[e]=new He(e,3,!1,e.toLowerCase(),null,!1,!1)});["checked","multiple","muted","selected"].forEach(function(e){Te[e]=new He(e,3,!0,e,null,!1,!1)});["capture","download"].forEach(function(e){Te[e]=new He(e,4,!1,e,null,!1,!1)});["cols","rows","size","span"].forEach(function(e){Te[e]=new He(e,6,!1,e,null,!1,!1)});["rowSpan","start"].forEach(function(e){Te[e]=new He(e,5,!1,e.toLowerCase(),null,!1,!1)});var Oa=/[\-:]([a-z])/g;function Fa(e){return e[1].toUpperCase()}"accent-height alignment-baseline arabic-form baseline-shift cap-height clip-path clip-rule color-interpolation color-interpolation-filters color-profile color-rendering dominant-baseline enable-background fill-opacity fill-rule flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-name glyph-orientation-horizontal glyph-orientation-vertical horiz-adv-x horiz-origin-x image-rendering letter-spacing lighting-color marker-end marker-mid marker-start overline-position overline-thickness paint-order panose-1 pointer-events rendering-intent shape-rendering stop-color stop-opacity strikethrough-position strikethrough-thickness stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width text-anchor text-decoration text-rendering underline-position underline-thickness unicode-bidi unicode-range units-per-em v-alphabetic v-hanging v-ideographic v-mathematical vector-effect vert-adv-y vert-origin-x vert-origin-y word-spacing writing-mode xmlns:xlink x-height".split(" ").forEach(function(e){var t=e.replace(Oa,Fa);Te[t]=new He(t,1,!1,e,null,!1,!1)});"xlink:actuate xlink:arcrole xlink:role xlink:show xlink:title xlink:type".split(" ").forEach(function(e){var t=e.replace(Oa,Fa);Te[t]=new He(t,1,!1,e,"http://www.w3.org/1999/xlink",!1,!1)});["xml:base","xml:lang","xml:space"].forEach(function(e){var t=e.replace(Oa,Fa);Te[t]=new He(t,1,!1,e,"http://www.w3.org/XML/1998/namespace",!1,!1)});["tabIndex","crossOrigin"].forEach(function(e){Te[e]=new He(e,1,!1,e.toLowerCase(),null,!1,!1)});Te.xlinkHref=new He("xlinkHref",1,!1,"xlink:href","http://www.w3.org/1999/xlink",!0,!1);["src","href","action","formAction"].forEach(function(e){Te[e]=new He(e,1,!1,e.toLowerCase(),null,!0,!0)});function Ia(e,t,n,r){var l=Te.hasOwnProperty(t)?Te[t]:null;(l!==null?l.type!==0:r||!(2a||l[o]!==i[a]){var u=` -`+l[o].replace(" at new "," at ");return e.displayName&&u.includes("")&&(u=u.replace("",e.displayName)),u}while(1<=o&&0<=a);break}}}finally{oo=!1,Error.prepareStackTrace=n}return(e=e?e.displayName||e.name:"")?Fr(e):""}function Vp(e){switch(e.tag){case 5:return Fr(e.type);case 16:return Fr("Lazy");case 13:return Fr("Suspense");case 19:return Fr("SuspenseList");case 0:case 2:case 15:return e=ao(e.type,!1),e;case 11:return e=ao(e.type.render,!1),e;case 1:return e=ao(e.type,!0),e;default:return""}}function Ao(e){if(e==null)return null;if(typeof e=="function")return e.displayName||e.name||null;if(typeof e=="string")return e;switch(e){case Qn:return"Fragment";case Wn:return"Portal";case Fo:return"Profiler";case Ua:return"StrictMode";case Io:return"Suspense";case Uo:return"SuspenseList"}if(typeof e=="object")switch(e.$$typeof){case Ic:return(e.displayName||"Context")+".Consumer";case Fc:return(e._context.displayName||"Context")+".Provider";case Aa:var t=e.render;return e=e.displayName,e||(e=t.displayName||t.name||"",e=e!==""?"ForwardRef("+e+")":"ForwardRef"),e;case Ba:return t=e.displayName||null,t!==null?t:Ao(e.type)||"Memo";case Yt:t=e._payload,e=e._init;try{return Ao(e(t))}catch{}}return null}function $p(e){var t=e.type;switch(e.tag){case 24:return"Cache";case 9:return(t.displayName||"Context")+".Consumer";case 10:return(t._context.displayName||"Context")+".Provider";case 18:return"DehydratedFragment";case 11:return e=t.render,e=e.displayName||e.name||"",t.displayName||(e!==""?"ForwardRef("+e+")":"ForwardRef");case 7:return"Fragment";case 5:return t;case 4:return"Portal";case 3:return"Root";case 6:return"Text";case 16:return Ao(t);case 8:return t===Ua?"StrictMode":"Mode";case 22:return"Offscreen";case 12:return"Profiler";case 21:return"Scope";case 13:return"Suspense";case 19:return"SuspenseList";case 25:return"TracingMarker";case 1:case 0:case 17:case 2:case 14:case 15:if(typeof t=="function")return t.displayName||t.name||null;if(typeof t=="string")return t}return null}function sn(e){switch(typeof e){case"boolean":case"number":case"string":case"undefined":return e;case"object":return e;default:return""}}function Ac(e){var t=e.type;return(e=e.nodeName)&&e.toLowerCase()==="input"&&(t==="checkbox"||t==="radio")}function Wp(e){var t=Ac(e)?"checked":"value",n=Object.getOwnPropertyDescriptor(e.constructor.prototype,t),r=""+e[t];if(!e.hasOwnProperty(t)&&typeof n<"u"&&typeof n.get=="function"&&typeof n.set=="function"){var l=n.get,i=n.set;return Object.defineProperty(e,t,{configurable:!0,get:function(){return l.call(this)},set:function(o){r=""+o,i.call(this,o)}}),Object.defineProperty(e,t,{enumerable:n.enumerable}),{getValue:function(){return r},setValue:function(o){r=""+o},stopTracking:function(){e._valueTracker=null,delete e[t]}}}}function Dl(e){e._valueTracker||(e._valueTracker=Wp(e))}function Bc(e){if(!e)return!1;var t=e._valueTracker;if(!t)return!0;var n=t.getValue(),r="";return e&&(r=Ac(e)?e.checked?"true":"false":e.value),e=r,e!==n?(t.setValue(e),!0):!1}function ai(e){if(e=e||(typeof document<"u"?document:void 0),typeof e>"u")return null;try{return e.activeElement||e.body}catch{return e.body}}function Bo(e,t){var n=t.checked;return de({},t,{defaultChecked:void 0,defaultValue:void 0,value:void 0,checked:n??e._wrapperState.initialChecked})}function Gu(e,t){var n=t.defaultValue==null?"":t.defaultValue,r=t.checked!=null?t.checked:t.defaultChecked;n=sn(t.value!=null?t.value:n),e._wrapperState={initialChecked:r,initialValue:n,controlled:t.type==="checkbox"||t.type==="radio"?t.checked!=null:t.value!=null}}function Hc(e,t){t=t.checked,t!=null&&Ia(e,"checked",t,!1)}function Ho(e,t){Hc(e,t);var n=sn(t.value),r=t.type;if(n!=null)r==="number"?(n===0&&e.value===""||e.value!=n)&&(e.value=""+n):e.value!==""+n&&(e.value=""+n);else if(r==="submit"||r==="reset"){e.removeAttribute("value");return}t.hasOwnProperty("value")?Vo(e,t.type,n):t.hasOwnProperty("defaultValue")&&Vo(e,t.type,sn(t.defaultValue)),t.checked==null&&t.defaultChecked!=null&&(e.defaultChecked=!!t.defaultChecked)}function Ju(e,t,n){if(t.hasOwnProperty("value")||t.hasOwnProperty("defaultValue")){var r=t.type;if(!(r!=="submit"&&r!=="reset"||t.value!==void 0&&t.value!==null))return;t=""+e._wrapperState.initialValue,n||t===e.value||(e.value=t),e.defaultValue=t}n=e.name,n!==""&&(e.name=""),e.defaultChecked=!!e._wrapperState.initialChecked,n!==""&&(e.name=n)}function Vo(e,t,n){(t!=="number"||ai(e.ownerDocument)!==e)&&(n==null?e.defaultValue=""+e._wrapperState.initialValue:e.defaultValue!==""+n&&(e.defaultValue=""+n))}var Ir=Array.isArray;function nr(e,t,n,r){if(e=e.options,t){t={};for(var l=0;l"+t.valueOf().toString()+"",t=Ml.firstChild;e.firstChild;)e.removeChild(e.firstChild);for(;t.firstChild;)e.appendChild(t.firstChild)}});function qr(e,t){if(t){var n=e.firstChild;if(n&&n===e.lastChild&&n.nodeType===3){n.nodeValue=t;return}}e.textContent=t}var Hr={animationIterationCount:!0,aspectRatio:!0,borderImageOutset:!0,borderImageSlice:!0,borderImageWidth:!0,boxFlex:!0,boxFlexGroup:!0,boxOrdinalGroup:!0,columnCount:!0,columns:!0,flex:!0,flexGrow:!0,flexPositive:!0,flexShrink:!0,flexNegative:!0,flexOrder:!0,gridArea:!0,gridRow:!0,gridRowEnd:!0,gridRowSpan:!0,gridRowStart:!0,gridColumn:!0,gridColumnEnd:!0,gridColumnSpan:!0,gridColumnStart:!0,fontWeight:!0,lineClamp:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,tabSize:!0,widows:!0,zIndex:!0,zoom:!0,fillOpacity:!0,floodOpacity:!0,stopOpacity:!0,strokeDasharray:!0,strokeDashoffset:!0,strokeMiterlimit:!0,strokeOpacity:!0,strokeWidth:!0},Qp=["Webkit","ms","Moz","O"];Object.keys(Hr).forEach(function(e){Qp.forEach(function(t){t=t+e.charAt(0).toUpperCase()+e.substring(1),Hr[t]=Hr[e]})});function Qc(e,t,n){return t==null||typeof t=="boolean"||t===""?"":n||typeof t!="number"||t===0||Hr.hasOwnProperty(e)&&Hr[e]?(""+t).trim():t+"px"}function Kc(e,t){e=e.style;for(var n in t)if(t.hasOwnProperty(n)){var r=n.indexOf("--")===0,l=Qc(n,t[n],r);n==="float"&&(n="cssFloat"),r?e.setProperty(n,l):e[n]=l}}var Kp=de({menuitem:!0},{area:!0,base:!0,br:!0,col:!0,embed:!0,hr:!0,img:!0,input:!0,keygen:!0,link:!0,meta:!0,param:!0,source:!0,track:!0,wbr:!0});function Qo(e,t){if(t){if(Kp[e]&&(t.children!=null||t.dangerouslySetInnerHTML!=null))throw Error(R(137,e));if(t.dangerouslySetInnerHTML!=null){if(t.children!=null)throw Error(R(60));if(typeof t.dangerouslySetInnerHTML!="object"||!("__html"in t.dangerouslySetInnerHTML))throw Error(R(61))}if(t.style!=null&&typeof t.style!="object")throw Error(R(62))}}function Ko(e,t){if(e.indexOf("-")===-1)return typeof t.is=="string";switch(e){case"annotation-xml":case"color-profile":case"font-face":case"font-face-src":case"font-face-uri":case"font-face-format":case"font-face-name":case"missing-glyph":return!1;default:return!0}}var Yo=null;function Ha(e){return e=e.target||e.srcElement||window,e.correspondingUseElement&&(e=e.correspondingUseElement),e.nodeType===3?e.parentNode:e}var Xo=null,rr=null,lr=null;function bu(e){if(e=wl(e)){if(typeof Xo!="function")throw Error(R(280));var t=e.stateNode;t&&(t=Bi(t),Xo(e.stateNode,e.type,t))}}function Yc(e){rr?lr?lr.push(e):lr=[e]:rr=e}function Xc(){if(rr){var e=rr,t=lr;if(lr=rr=null,bu(e),t)for(e=0;e>>=0,e===0?32:31-(rh(e)/lh|0)|0}var jl=64,zl=4194304;function Ur(e){switch(e&-e){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return e&4194240;case 4194304:case 8388608:case 16777216:case 33554432:case 67108864:return e&130023424;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 1073741824;default:return e}}function fi(e,t){var n=e.pendingLanes;if(n===0)return 0;var r=0,l=e.suspendedLanes,i=e.pingedLanes,o=n&268435455;if(o!==0){var a=o&~l;a!==0?r=Ur(a):(i&=o,i!==0&&(r=Ur(i)))}else o=n&~l,o!==0?r=Ur(o):i!==0&&(r=Ur(i));if(r===0)return 0;if(t!==0&&t!==r&&!(t&l)&&(l=r&-r,i=t&-t,l>=i||l===16&&(i&4194240)!==0))return t;if(r&4&&(r|=n&16),t=e.entangledLanes,t!==0)for(e=e.entanglements,t&=r;0n;n++)t.push(e);return t}function yl(e,t,n){e.pendingLanes|=t,t!==536870912&&(e.suspendedLanes=0,e.pingedLanes=0),e=e.eventTimes,t=31-vt(t),e[t]=n}function uh(e,t){var n=e.pendingLanes&~t;e.pendingLanes=t,e.suspendedLanes=0,e.pingedLanes=0,e.expiredLanes&=t,e.mutableReadLanes&=t,e.entangledLanes&=t,t=e.entanglements;var r=e.eventTimes;for(e=e.expirationTimes;0=$r),us=" ",ss=!1;function mf(e,t){switch(e){case"keyup":return Fh.indexOf(t.keyCode)!==-1;case"keydown":return t.keyCode!==229;case"keypress":case"mousedown":case"focusout":return!0;default:return!1}}function vf(e){return e=e.detail,typeof e=="object"&&"data"in e?e.data:null}var Kn=!1;function Uh(e,t){switch(e){case"compositionend":return vf(t);case"keypress":return t.which!==32?null:(ss=!0,us);case"textInput":return e=t.data,e===us&&ss?null:e;default:return null}}function Ah(e,t){if(Kn)return e==="compositionend"||!Ga&&mf(e,t)?(e=pf(),Zl=Ka=Zt=null,Kn=!1,e):null;switch(e){case"paste":return null;case"keypress":if(!(t.ctrlKey||t.altKey||t.metaKey)||t.ctrlKey&&t.altKey){if(t.char&&1=t)return{node:n,offset:t-e};e=r}e:{for(;n;){if(n.nextSibling){n=n.nextSibling;break e}n=n.parentNode}n=void 0}n=ps(n)}}function Sf(e,t){return e&&t?e===t?!0:e&&e.nodeType===3?!1:t&&t.nodeType===3?Sf(e,t.parentNode):"contains"in e?e.contains(t):e.compareDocumentPosition?!!(e.compareDocumentPosition(t)&16):!1:!1}function Ef(){for(var e=window,t=ai();t instanceof e.HTMLIFrameElement;){try{var n=typeof t.contentWindow.location.href=="string"}catch{n=!1}if(n)e=t.contentWindow;else break;t=ai(e.document)}return t}function Ja(e){var t=e&&e.nodeName&&e.nodeName.toLowerCase();return t&&(t==="input"&&(e.type==="text"||e.type==="search"||e.type==="tel"||e.type==="url"||e.type==="password")||t==="textarea"||e.contentEditable==="true")}function Xh(e){var t=Ef(),n=e.focusedElem,r=e.selectionRange;if(t!==n&&n&&n.ownerDocument&&Sf(n.ownerDocument.documentElement,n)){if(r!==null&&Ja(n)){if(t=r.start,e=r.end,e===void 0&&(e=t),"selectionStart"in n)n.selectionStart=t,n.selectionEnd=Math.min(e,n.value.length);else if(e=(t=n.ownerDocument||document)&&t.defaultView||window,e.getSelection){e=e.getSelection();var l=n.textContent.length,i=Math.min(r.start,l);r=r.end===void 0?i:Math.min(r.end,l),!e.extend&&i>r&&(l=r,r=i,i=l),l=hs(n,i);var o=hs(n,r);l&&o&&(e.rangeCount!==1||e.anchorNode!==l.node||e.anchorOffset!==l.offset||e.focusNode!==o.node||e.focusOffset!==o.offset)&&(t=t.createRange(),t.setStart(l.node,l.offset),e.removeAllRanges(),i>r?(e.addRange(t),e.extend(o.node,o.offset)):(t.setEnd(o.node,o.offset),e.addRange(t)))}}for(t=[],e=n;e=e.parentNode;)e.nodeType===1&&t.push({element:e,left:e.scrollLeft,top:e.scrollTop});for(typeof n.focus=="function"&&n.focus(),n=0;n=document.documentMode,Yn=null,ea=null,Qr=null,ta=!1;function ms(e,t,n){var r=n.window===n?n.document:n.nodeType===9?n:n.ownerDocument;ta||Yn==null||Yn!==ai(r)||(r=Yn,"selectionStart"in r&&Ja(r)?r={start:r.selectionStart,end:r.selectionEnd}:(r=(r.ownerDocument&&r.ownerDocument.defaultView||window).getSelection(),r={anchorNode:r.anchorNode,anchorOffset:r.anchorOffset,focusNode:r.focusNode,focusOffset:r.focusOffset}),Qr&&ll(Qr,r)||(Qr=r,r=hi(ea,"onSelect"),0Jn||(e.current=aa[Jn],aa[Jn]=null,Jn--)}function ie(e,t){Jn++,aa[Jn]=e.current,e.current=t}var cn={},ze=dn(cn),Qe=dn(!1),Ln=cn;function sr(e,t){var n=e.type.contextTypes;if(!n)return cn;var r=e.stateNode;if(r&&r.__reactInternalMemoizedUnmaskedChildContext===t)return r.__reactInternalMemoizedMaskedChildContext;var l={},i;for(i in n)l[i]=t[i];return r&&(e=e.stateNode,e.__reactInternalMemoizedUnmaskedChildContext=t,e.__reactInternalMemoizedMaskedChildContext=l),l}function Ke(e){return e=e.childContextTypes,e!=null}function vi(){ae(Qe),ae(ze)}function xs(e,t,n){if(ze.current!==cn)throw Error(R(168));ie(ze,t),ie(Qe,n)}function Tf(e,t,n){var r=e.stateNode;if(t=t.childContextTypes,typeof r.getChildContext!="function")return n;r=r.getChildContext();for(var l in r)if(!(l in t))throw Error(R(108,$p(e)||"Unknown",l));return de({},n,r)}function yi(e){return e=(e=e.stateNode)&&e.__reactInternalMemoizedMergedChildContext||cn,Ln=ze.current,ie(ze,e),ie(Qe,Qe.current),!0}function ks(e,t,n){var r=e.stateNode;if(!r)throw Error(R(169));n?(e=Tf(e,t,Ln),r.__reactInternalMemoizedMergedChildContext=e,ae(Qe),ae(ze),ie(ze,e)):ae(Qe),ie(Qe,n)}var Lt=null,Hi=!1,xo=!1;function Df(e){Lt===null?Lt=[e]:Lt.push(e)}function om(e){Hi=!0,Df(e)}function pn(){if(!xo&&Lt!==null){xo=!0;var e=0,t=ee;try{var n=Lt;for(ee=1;e>=o,l-=o,Nt=1<<32-vt(t)+l|n<C?(H=y,y=null):H=y.sibling;var M=p(m,y,v[C],k);if(M===null){y===null&&(y=H);break}e&&y&&M.alternate===null&&t(m,y),c=i(M,c,C),z===null?L=M:z.sibling=M,z=M,y=H}if(C===v.length)return n(m,y),se&&gn(m,C),L;if(y===null){for(;CC?(H=y,y=null):H=y.sibling;var q=p(m,y,M.value,k);if(q===null){y===null&&(y=H);break}e&&y&&q.alternate===null&&t(m,y),c=i(q,c,C),z===null?L=q:z.sibling=q,z=q,y=H}if(M.done)return n(m,y),se&&gn(m,C),L;if(y===null){for(;!M.done;C++,M=v.next())M=f(m,M.value,k),M!==null&&(c=i(M,c,C),z===null?L=M:z.sibling=M,z=M);return se&&gn(m,C),L}for(y=r(m,y);!M.done;C++,M=v.next())M=w(y,m,C,M.value,k),M!==null&&(e&&M.alternate!==null&&y.delete(M.key===null?C:M.key),c=i(M,c,C),z===null?L=M:z.sibling=M,z=M);return e&&y.forEach(function(re){return t(m,re)}),se&&gn(m,C),L}function D(m,c,v,k){if(typeof v=="object"&&v!==null&&v.type===Qn&&v.key===null&&(v=v.props.children),typeof v=="object"&&v!==null){switch(v.$$typeof){case Tl:e:{for(var L=v.key,z=c;z!==null;){if(z.key===L){if(L=v.type,L===Qn){if(z.tag===7){n(m,z.sibling),c=l(z,v.props.children),c.return=m,m=c;break e}}else if(z.elementType===L||typeof L=="object"&&L!==null&&L.$$typeof===Yt&&_s(L)===z.type){n(m,z.sibling),c=l(z,v.props),c.ref=Tr(m,z,v),c.return=m,m=c;break e}n(m,z);break}else t(m,z);z=z.sibling}v.type===Qn?(c=Rn(v.props.children,m.mode,k,v.key),c.return=m,m=c):(k=ii(v.type,v.key,v.props,null,m.mode,k),k.ref=Tr(m,c,v),k.return=m,m=k)}return o(m);case Wn:e:{for(z=v.key;c!==null;){if(c.key===z)if(c.tag===4&&c.stateNode.containerInfo===v.containerInfo&&c.stateNode.implementation===v.implementation){n(m,c.sibling),c=l(c,v.children||[]),c.return=m,m=c;break e}else{n(m,c);break}else t(m,c);c=c.sibling}c=To(v,m.mode,k),c.return=m,m=c}return o(m);case Yt:return z=v._init,D(m,c,z(v._payload),k)}if(Ir(v))return x(m,c,v,k);if(Pr(v))return E(m,c,v,k);Hl(m,v)}return typeof v=="string"&&v!==""||typeof v=="number"?(v=""+v,c!==null&&c.tag===6?(n(m,c.sibling),c=l(c,v),c.return=m,m=c):(n(m,c),c=No(v,m.mode,k),c.return=m,m=c),o(m)):n(m,c)}return D}var fr=Of(!0),Ff=Of(!1),Si=dn(null),Ei=null,bn=null,eu=null;function tu(){eu=bn=Ei=null}function nu(e){var t=Si.current;ae(Si),e._currentValue=t}function ca(e,t,n){for(;e!==null;){var r=e.alternate;if((e.childLanes&t)!==t?(e.childLanes|=t,r!==null&&(r.childLanes|=t)):r!==null&&(r.childLanes&t)!==t&&(r.childLanes|=t),e===n)break;e=e.return}}function or(e,t){Ei=e,eu=bn=null,e=e.dependencies,e!==null&&e.firstContext!==null&&(e.lanes&t&&(We=!0),e.firstContext=null)}function at(e){var t=e._currentValue;if(eu!==e)if(e={context:e,memoizedValue:t,next:null},bn===null){if(Ei===null)throw Error(R(308));bn=e,Ei.dependencies={lanes:0,firstContext:e}}else bn=bn.next=e;return t}var kn=null;function ru(e){kn===null?kn=[e]:kn.push(e)}function If(e,t,n,r){var l=t.interleaved;return l===null?(n.next=n,ru(t)):(n.next=l.next,l.next=n),t.interleaved=n,Ot(e,r)}function Ot(e,t){e.lanes|=t;var n=e.alternate;for(n!==null&&(n.lanes|=t),n=e,e=e.return;e!==null;)e.childLanes|=t,n=e.alternate,n!==null&&(n.childLanes|=t),n=e,e=e.return;return n.tag===3?n.stateNode:null}var Xt=!1;function lu(e){e.updateQueue={baseState:e.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,interleaved:null,lanes:0},effects:null}}function Uf(e,t){e=e.updateQueue,t.updateQueue===e&&(t.updateQueue={baseState:e.baseState,firstBaseUpdate:e.firstBaseUpdate,lastBaseUpdate:e.lastBaseUpdate,shared:e.shared,effects:e.effects})}function Dt(e,t){return{eventTime:e,lane:t,tag:0,payload:null,callback:null,next:null}}function ln(e,t,n){var r=e.updateQueue;if(r===null)return null;if(r=r.shared,G&2){var l=r.pending;return l===null?t.next=t:(t.next=l.next,l.next=t),r.pending=t,Ot(e,n)}return l=r.interleaved,l===null?(t.next=t,ru(r)):(t.next=l.next,l.next=t),r.interleaved=t,Ot(e,n)}function bl(e,t,n){if(t=t.updateQueue,t!==null&&(t=t.shared,(n&4194240)!==0)){var r=t.lanes;r&=e.pendingLanes,n|=r,t.lanes=n,$a(e,n)}}function Rs(e,t){var n=e.updateQueue,r=e.alternate;if(r!==null&&(r=r.updateQueue,n===r)){var l=null,i=null;if(n=n.firstBaseUpdate,n!==null){do{var o={eventTime:n.eventTime,lane:n.lane,tag:n.tag,payload:n.payload,callback:n.callback,next:null};i===null?l=i=o:i=i.next=o,n=n.next}while(n!==null);i===null?l=i=t:i=i.next=t}else l=i=t;n={baseState:r.baseState,firstBaseUpdate:l,lastBaseUpdate:i,shared:r.shared,effects:r.effects},e.updateQueue=n;return}e=n.lastBaseUpdate,e===null?n.firstBaseUpdate=t:e.next=t,n.lastBaseUpdate=t}function xi(e,t,n,r){var l=e.updateQueue;Xt=!1;var i=l.firstBaseUpdate,o=l.lastBaseUpdate,a=l.shared.pending;if(a!==null){l.shared.pending=null;var u=a,s=u.next;u.next=null,o===null?i=s:o.next=s,o=u;var d=e.alternate;d!==null&&(d=d.updateQueue,a=d.lastBaseUpdate,a!==o&&(a===null?d.firstBaseUpdate=s:a.next=s,d.lastBaseUpdate=u))}if(i!==null){var f=l.baseState;o=0,d=s=u=null,a=i;do{var p=a.lane,w=a.eventTime;if((r&p)===p){d!==null&&(d=d.next={eventTime:w,lane:0,tag:a.tag,payload:a.payload,callback:a.callback,next:null});e:{var x=e,E=a;switch(p=t,w=n,E.tag){case 1:if(x=E.payload,typeof x=="function"){f=x.call(w,f,p);break e}f=x;break e;case 3:x.flags=x.flags&-65537|128;case 0:if(x=E.payload,p=typeof x=="function"?x.call(w,f,p):x,p==null)break e;f=de({},f,p);break e;case 2:Xt=!0}}a.callback!==null&&a.lane!==0&&(e.flags|=64,p=l.effects,p===null?l.effects=[a]:p.push(a))}else w={eventTime:w,lane:p,tag:a.tag,payload:a.payload,callback:a.callback,next:null},d===null?(s=d=w,u=f):d=d.next=w,o|=p;if(a=a.next,a===null){if(a=l.shared.pending,a===null)break;p=a,a=p.next,p.next=null,l.lastBaseUpdate=p,l.shared.pending=null}}while(!0);if(d===null&&(u=f),l.baseState=u,l.firstBaseUpdate=s,l.lastBaseUpdate=d,t=l.shared.interleaved,t!==null){l=t;do o|=l.lane,l=l.next;while(l!==t)}else i===null&&(l.shared.lanes=0);Dn|=o,e.lanes=o,e.memoizedState=f}}function Ls(e,t,n){if(e=t.effects,t.effects=null,e!==null)for(t=0;tn?n:4,e(!0);var r=Co.transition;Co.transition={};try{e(!1),t()}finally{ee=n,Co.transition=r}}function td(){return ut().memoizedState}function cm(e,t,n){var r=an(e);if(n={lane:r,action:n,hasEagerState:!1,eagerState:null,next:null},nd(e))rd(t,n);else if(n=If(e,t,n,r),n!==null){var l=Ae();yt(n,e,r,l),ld(n,t,r)}}function fm(e,t,n){var r=an(e),l={lane:r,action:n,hasEagerState:!1,eagerState:null,next:null};if(nd(e))rd(t,l);else{var i=e.alternate;if(e.lanes===0&&(i===null||i.lanes===0)&&(i=t.lastRenderedReducer,i!==null))try{var o=t.lastRenderedState,a=i(o,n);if(l.hasEagerState=!0,l.eagerState=a,gt(a,o)){var u=t.interleaved;u===null?(l.next=l,ru(t)):(l.next=u.next,u.next=l),t.interleaved=l;return}}catch{}finally{}n=If(e,t,l,r),n!==null&&(l=Ae(),yt(n,e,r,l),ld(n,t,r))}}function nd(e){var t=e.alternate;return e===fe||t!==null&&t===fe}function rd(e,t){Kr=Ci=!0;var n=e.pending;n===null?t.next=t:(t.next=n.next,n.next=t),e.pending=t}function ld(e,t,n){if(n&4194240){var r=t.lanes;r&=e.pendingLanes,n|=r,t.lanes=n,$a(e,n)}}var Pi={readContext:at,useCallback:De,useContext:De,useEffect:De,useImperativeHandle:De,useInsertionEffect:De,useLayoutEffect:De,useMemo:De,useReducer:De,useRef:De,useState:De,useDebugValue:De,useDeferredValue:De,useTransition:De,useMutableSource:De,useSyncExternalStore:De,useId:De,unstable_isNewReconciler:!1},dm={readContext:at,useCallback:function(e,t){return Et().memoizedState=[e,t===void 0?null:t],e},useContext:at,useEffect:Ts,useImperativeHandle:function(e,t,n){return n=n!=null?n.concat([e]):null,ti(4194308,4,Jf.bind(null,t,e),n)},useLayoutEffect:function(e,t){return ti(4194308,4,e,t)},useInsertionEffect:function(e,t){return ti(4,2,e,t)},useMemo:function(e,t){var n=Et();return t=t===void 0?null:t,e=e(),n.memoizedState=[e,t],e},useReducer:function(e,t,n){var r=Et();return t=n!==void 0?n(t):t,r.memoizedState=r.baseState=t,e={pending:null,interleaved:null,lanes:0,dispatch:null,lastRenderedReducer:e,lastRenderedState:t},r.queue=e,e=e.dispatch=cm.bind(null,fe,e),[r.memoizedState,e]},useRef:function(e){var t=Et();return e={current:e},t.memoizedState=e},useState:Ns,useDebugValue:du,useDeferredValue:function(e){return Et().memoizedState=e},useTransition:function(){var e=Ns(!1),t=e[0];return e=sm.bind(null,e[1]),Et().memoizedState=e,[t,e]},useMutableSource:function(){},useSyncExternalStore:function(e,t,n){var r=fe,l=Et();if(se){if(n===void 0)throw Error(R(407));n=n()}else{if(n=t(),Pe===null)throw Error(R(349));Tn&30||Vf(r,t,n)}l.memoizedState=n;var i={value:n,getSnapshot:t};return l.queue=i,Ts(Wf.bind(null,r,i,e),[e]),r.flags|=2048,dl(9,$f.bind(null,r,i,n,t),void 0,null),n},useId:function(){var e=Et(),t=Pe.identifierPrefix;if(se){var n=Tt,r=Nt;n=(r&~(1<<32-vt(r)-1)).toString(32)+n,t=":"+t+"R"+n,n=cl++,0<\/script>",e=e.removeChild(e.firstChild)):typeof r.is=="string"?e=o.createElement(n,{is:r.is}):(e=o.createElement(n),n==="select"&&(o=e,r.multiple?o.multiple=!0:r.size&&(o.size=r.size))):e=o.createElementNS(e,n),e[xt]=t,e[al]=r,hd(e,t,!1,!1),t.stateNode=e;e:{switch(o=Ko(n,r),n){case"dialog":oe("cancel",e),oe("close",e),l=r;break;case"iframe":case"object":case"embed":oe("load",e),l=r;break;case"video":case"audio":for(l=0;lhr&&(t.flags|=128,r=!0,Dr(i,!1),t.lanes=4194304)}else{if(!r)if(e=ki(o),e!==null){if(t.flags|=128,r=!0,n=e.updateQueue,n!==null&&(t.updateQueue=n,t.flags|=4),Dr(i,!0),i.tail===null&&i.tailMode==="hidden"&&!o.alternate&&!se)return Me(t),null}else 2*ve()-i.renderingStartTime>hr&&n!==1073741824&&(t.flags|=128,r=!0,Dr(i,!1),t.lanes=4194304);i.isBackwards?(o.sibling=t.child,t.child=o):(n=i.last,n!==null?n.sibling=o:t.child=o,i.last=o)}return i.tail!==null?(t=i.tail,i.rendering=t,i.tail=t.sibling,i.renderingStartTime=ve(),t.sibling=null,n=ce.current,ie(ce,r?n&1|2:n&1),t):(Me(t),null);case 22:case 23:return gu(),r=t.memoizedState!==null,e!==null&&e.memoizedState!==null!==r&&(t.flags|=8192),r&&t.mode&1?Ge&1073741824&&(Me(t),t.subtreeFlags&6&&(t.flags|=8192)):Me(t),null;case 24:return null;case 25:return null}throw Error(R(156,t.tag))}function Sm(e,t){switch(qa(t),t.tag){case 1:return Ke(t.type)&&vi(),e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 3:return dr(),ae(Qe),ae(ze),au(),e=t.flags,e&65536&&!(e&128)?(t.flags=e&-65537|128,t):null;case 5:return ou(t),null;case 13:if(ae(ce),e=t.memoizedState,e!==null&&e.dehydrated!==null){if(t.alternate===null)throw Error(R(340));cr()}return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 19:return ae(ce),null;case 4:return dr(),null;case 10:return nu(t.type._context),null;case 22:case 23:return gu(),null;case 24:return null;default:return null}}var $l=!1,je=!1,Em=typeof WeakSet=="function"?WeakSet:Set,O=null;function er(e,t){var n=e.ref;if(n!==null)if(typeof n=="function")try{n(null)}catch(r){me(e,t,r)}else n.current=null}function wa(e,t,n){try{n()}catch(r){me(e,t,r)}}var Hs=!1;function xm(e,t){if(na=di,e=Ef(),Ja(e)){if("selectionStart"in e)var n={start:e.selectionStart,end:e.selectionEnd};else e:{n=(n=e.ownerDocument)&&n.defaultView||window;var r=n.getSelection&&n.getSelection();if(r&&r.rangeCount!==0){n=r.anchorNode;var l=r.anchorOffset,i=r.focusNode;r=r.focusOffset;try{n.nodeType,i.nodeType}catch{n=null;break e}var o=0,a=-1,u=-1,s=0,d=0,f=e,p=null;t:for(;;){for(var w;f!==n||l!==0&&f.nodeType!==3||(a=o+l),f!==i||r!==0&&f.nodeType!==3||(u=o+r),f.nodeType===3&&(o+=f.nodeValue.length),(w=f.firstChild)!==null;)p=f,f=w;for(;;){if(f===e)break t;if(p===n&&++s===l&&(a=o),p===i&&++d===r&&(u=o),(w=f.nextSibling)!==null)break;f=p,p=f.parentNode}f=w}n=a===-1||u===-1?null:{start:a,end:u}}else n=null}n=n||{start:0,end:0}}else n=null;for(ra={focusedElem:e,selectionRange:n},di=!1,O=t;O!==null;)if(t=O,e=t.child,(t.subtreeFlags&1028)!==0&&e!==null)e.return=t,O=e;else for(;O!==null;){t=O;try{var x=t.alternate;if(t.flags&1024)switch(t.tag){case 0:case 11:case 15:break;case 1:if(x!==null){var E=x.memoizedProps,D=x.memoizedState,m=t.stateNode,c=m.getSnapshotBeforeUpdate(t.elementType===t.type?E:dt(t.type,E),D);m.__reactInternalSnapshotBeforeUpdate=c}break;case 3:var v=t.stateNode.containerInfo;v.nodeType===1?v.textContent="":v.nodeType===9&&v.documentElement&&v.removeChild(v.documentElement);break;case 5:case 6:case 4:case 17:break;default:throw Error(R(163))}}catch(k){me(t,t.return,k)}if(e=t.sibling,e!==null){e.return=t.return,O=e;break}O=t.return}return x=Hs,Hs=!1,x}function Yr(e,t,n){var r=t.updateQueue;if(r=r!==null?r.lastEffect:null,r!==null){var l=r=r.next;do{if((l.tag&e)===e){var i=l.destroy;l.destroy=void 0,i!==void 0&&wa(t,n,i)}l=l.next}while(l!==r)}}function Wi(e,t){if(t=t.updateQueue,t=t!==null?t.lastEffect:null,t!==null){var n=t=t.next;do{if((n.tag&e)===e){var r=n.create;n.destroy=r()}n=n.next}while(n!==t)}}function Sa(e){var t=e.ref;if(t!==null){var n=e.stateNode;switch(e.tag){case 5:e=n;break;default:e=n}typeof t=="function"?t(e):t.current=e}}function yd(e){var t=e.alternate;t!==null&&(e.alternate=null,yd(t)),e.child=null,e.deletions=null,e.sibling=null,e.tag===5&&(t=e.stateNode,t!==null&&(delete t[xt],delete t[al],delete t[oa],delete t[lm],delete t[im])),e.stateNode=null,e.return=null,e.dependencies=null,e.memoizedProps=null,e.memoizedState=null,e.pendingProps=null,e.stateNode=null,e.updateQueue=null}function gd(e){return e.tag===5||e.tag===3||e.tag===4}function Vs(e){e:for(;;){for(;e.sibling===null;){if(e.return===null||gd(e.return))return null;e=e.return}for(e.sibling.return=e.return,e=e.sibling;e.tag!==5&&e.tag!==6&&e.tag!==18;){if(e.flags&2||e.child===null||e.tag===4)continue e;e.child.return=e,e=e.child}if(!(e.flags&2))return e.stateNode}}function Ea(e,t,n){var r=e.tag;if(r===5||r===6)e=e.stateNode,t?n.nodeType===8?n.parentNode.insertBefore(e,t):n.insertBefore(e,t):(n.nodeType===8?(t=n.parentNode,t.insertBefore(e,n)):(t=n,t.appendChild(e)),n=n._reactRootContainer,n!=null||t.onclick!==null||(t.onclick=mi));else if(r!==4&&(e=e.child,e!==null))for(Ea(e,t,n),e=e.sibling;e!==null;)Ea(e,t,n),e=e.sibling}function xa(e,t,n){var r=e.tag;if(r===5||r===6)e=e.stateNode,t?n.insertBefore(e,t):n.appendChild(e);else if(r!==4&&(e=e.child,e!==null))for(xa(e,t,n),e=e.sibling;e!==null;)xa(e,t,n),e=e.sibling}var Le=null,pt=!1;function Qt(e,t,n){for(n=n.child;n!==null;)wd(e,t,n),n=n.sibling}function wd(e,t,n){if(kt&&typeof kt.onCommitFiberUnmount=="function")try{kt.onCommitFiberUnmount(Fi,n)}catch{}switch(n.tag){case 5:je||er(n,t);case 6:var r=Le,l=pt;Le=null,Qt(e,t,n),Le=r,pt=l,Le!==null&&(pt?(e=Le,n=n.stateNode,e.nodeType===8?e.parentNode.removeChild(n):e.removeChild(n)):Le.removeChild(n.stateNode));break;case 18:Le!==null&&(pt?(e=Le,n=n.stateNode,e.nodeType===8?Eo(e.parentNode,n):e.nodeType===1&&Eo(e,n),nl(e)):Eo(Le,n.stateNode));break;case 4:r=Le,l=pt,Le=n.stateNode.containerInfo,pt=!0,Qt(e,t,n),Le=r,pt=l;break;case 0:case 11:case 14:case 15:if(!je&&(r=n.updateQueue,r!==null&&(r=r.lastEffect,r!==null))){l=r=r.next;do{var i=l,o=i.destroy;i=i.tag,o!==void 0&&(i&2||i&4)&&wa(n,t,o),l=l.next}while(l!==r)}Qt(e,t,n);break;case 1:if(!je&&(er(n,t),r=n.stateNode,typeof r.componentWillUnmount=="function"))try{r.props=n.memoizedProps,r.state=n.memoizedState,r.componentWillUnmount()}catch(a){me(n,t,a)}Qt(e,t,n);break;case 21:Qt(e,t,n);break;case 22:n.mode&1?(je=(r=je)||n.memoizedState!==null,Qt(e,t,n),je=r):Qt(e,t,n);break;default:Qt(e,t,n)}}function $s(e){var t=e.updateQueue;if(t!==null){e.updateQueue=null;var n=e.stateNode;n===null&&(n=e.stateNode=new Em),t.forEach(function(r){var l=Dm.bind(null,e,r);n.has(r)||(n.add(r),r.then(l,l))})}}function ft(e,t){var n=t.deletions;if(n!==null)for(var r=0;rl&&(l=o),r&=~i}if(r=l,r=ve()-r,r=(120>r?120:480>r?480:1080>r?1080:1920>r?1920:3e3>r?3e3:4320>r?4320:1960*Cm(r/1960))-r,10e?16:e,qt===null)var r=!1;else{if(e=qt,qt=null,Li=0,G&6)throw Error(R(331));var l=G;for(G|=4,O=e.current;O!==null;){var i=O,o=i.child;if(O.flags&16){var a=i.deletions;if(a!==null){for(var u=0;uve()-vu?_n(e,0):mu|=n),Ye(e,t)}function Rd(e,t){t===0&&(e.mode&1?(t=zl,zl<<=1,!(zl&130023424)&&(zl=4194304)):t=1);var n=Ae();e=Ot(e,t),e!==null&&(yl(e,t,n),Ye(e,n))}function Tm(e){var t=e.memoizedState,n=0;t!==null&&(n=t.retryLane),Rd(e,n)}function Dm(e,t){var n=0;switch(e.tag){case 13:var r=e.stateNode,l=e.memoizedState;l!==null&&(n=l.retryLane);break;case 19:r=e.stateNode;break;default:throw Error(R(314))}r!==null&&r.delete(t),Rd(e,n)}var Ld;Ld=function(e,t,n){if(e!==null)if(e.memoizedProps!==t.pendingProps||Qe.current)We=!0;else{if(!(e.lanes&n)&&!(t.flags&128))return We=!1,gm(e,t,n);We=!!(e.flags&131072)}else We=!1,se&&t.flags&1048576&&Mf(t,wi,t.index);switch(t.lanes=0,t.tag){case 2:var r=t.type;ni(e,t),e=t.pendingProps;var l=sr(t,ze.current);or(t,n),l=su(null,t,r,e,l,n);var i=cu();return t.flags|=1,typeof l=="object"&&l!==null&&typeof l.render=="function"&&l.$$typeof===void 0?(t.tag=1,t.memoizedState=null,t.updateQueue=null,Ke(r)?(i=!0,yi(t)):i=!1,t.memoizedState=l.state!==null&&l.state!==void 0?l.state:null,lu(t),l.updater=$i,t.stateNode=l,l._reactInternals=t,da(t,r,e,n),t=ma(null,t,r,!0,i,n)):(t.tag=0,se&&i&&Za(t),Ue(null,t,l,n),t=t.child),t;case 16:r=t.elementType;e:{switch(ni(e,t),e=t.pendingProps,l=r._init,r=l(r._payload),t.type=r,l=t.tag=jm(r),e=dt(r,e),l){case 0:t=ha(null,t,r,e,n);break e;case 1:t=Us(null,t,r,e,n);break e;case 11:t=Fs(null,t,r,e,n);break e;case 14:t=Is(null,t,r,dt(r.type,e),n);break e}throw Error(R(306,r,""))}return t;case 0:return r=t.type,l=t.pendingProps,l=t.elementType===r?l:dt(r,l),ha(e,t,r,l,n);case 1:return r=t.type,l=t.pendingProps,l=t.elementType===r?l:dt(r,l),Us(e,t,r,l,n);case 3:e:{if(fd(t),e===null)throw Error(R(387));r=t.pendingProps,i=t.memoizedState,l=i.element,Uf(e,t),xi(t,r,null,n);var o=t.memoizedState;if(r=o.element,i.isDehydrated)if(i={element:r,isDehydrated:!1,cache:o.cache,pendingSuspenseBoundaries:o.pendingSuspenseBoundaries,transitions:o.transitions},t.updateQueue.baseState=i,t.memoizedState=i,t.flags&256){l=pr(Error(R(423)),t),t=As(e,t,r,n,l);break e}else if(r!==l){l=pr(Error(R(424)),t),t=As(e,t,r,n,l);break e}else for(Ze=rn(t.stateNode.containerInfo.firstChild),qe=t,se=!0,mt=null,n=Ff(t,null,r,n),t.child=n;n;)n.flags=n.flags&-3|4096,n=n.sibling;else{if(cr(),r===l){t=Ft(e,t,n);break e}Ue(e,t,r,n)}t=t.child}return t;case 5:return Af(t),e===null&&sa(t),r=t.type,l=t.pendingProps,i=e!==null?e.memoizedProps:null,o=l.children,la(r,l)?o=null:i!==null&&la(r,i)&&(t.flags|=32),cd(e,t),Ue(e,t,o,n),t.child;case 6:return e===null&&sa(t),null;case 13:return dd(e,t,n);case 4:return iu(t,t.stateNode.containerInfo),r=t.pendingProps,e===null?t.child=fr(t,null,r,n):Ue(e,t,r,n),t.child;case 11:return r=t.type,l=t.pendingProps,l=t.elementType===r?l:dt(r,l),Fs(e,t,r,l,n);case 7:return Ue(e,t,t.pendingProps,n),t.child;case 8:return Ue(e,t,t.pendingProps.children,n),t.child;case 12:return Ue(e,t,t.pendingProps.children,n),t.child;case 10:e:{if(r=t.type._context,l=t.pendingProps,i=t.memoizedProps,o=l.value,ie(Si,r._currentValue),r._currentValue=o,i!==null)if(gt(i.value,o)){if(i.children===l.children&&!Qe.current){t=Ft(e,t,n);break e}}else for(i=t.child,i!==null&&(i.return=t);i!==null;){var a=i.dependencies;if(a!==null){o=i.child;for(var u=a.firstContext;u!==null;){if(u.context===r){if(i.tag===1){u=Dt(-1,n&-n),u.tag=2;var s=i.updateQueue;if(s!==null){s=s.shared;var d=s.pending;d===null?u.next=u:(u.next=d.next,d.next=u),s.pending=u}}i.lanes|=n,u=i.alternate,u!==null&&(u.lanes|=n),ca(i.return,n,t),a.lanes|=n;break}u=u.next}}else if(i.tag===10)o=i.type===t.type?null:i.child;else if(i.tag===18){if(o=i.return,o===null)throw Error(R(341));o.lanes|=n,a=o.alternate,a!==null&&(a.lanes|=n),ca(o,n,t),o=i.sibling}else o=i.child;if(o!==null)o.return=i;else for(o=i;o!==null;){if(o===t){o=null;break}if(i=o.sibling,i!==null){i.return=o.return,o=i;break}o=o.return}i=o}Ue(e,t,l.children,n),t=t.child}return t;case 9:return l=t.type,r=t.pendingProps.children,or(t,n),l=at(l),r=r(l),t.flags|=1,Ue(e,t,r,n),t.child;case 14:return r=t.type,l=dt(r,t.pendingProps),l=dt(r.type,l),Is(e,t,r,l,n);case 15:return ud(e,t,t.type,t.pendingProps,n);case 17:return r=t.type,l=t.pendingProps,l=t.elementType===r?l:dt(r,l),ni(e,t),t.tag=1,Ke(r)?(e=!0,yi(t)):e=!1,or(t,n),id(t,r,l),da(t,r,l,n),ma(null,t,r,!0,e,n);case 19:return pd(e,t,n);case 22:return sd(e,t,n)}throw Error(R(156,t.tag))};function Nd(e,t){return tf(e,t)}function Mm(e,t,n,r){this.tag=e,this.key=n,this.sibling=this.child=this.return=this.stateNode=this.type=this.elementType=null,this.index=0,this.ref=null,this.pendingProps=t,this.dependencies=this.memoizedState=this.updateQueue=this.memoizedProps=null,this.mode=r,this.subtreeFlags=this.flags=0,this.deletions=null,this.childLanes=this.lanes=0,this.alternate=null}function it(e,t,n,r){return new Mm(e,t,n,r)}function Su(e){return e=e.prototype,!(!e||!e.isReactComponent)}function jm(e){if(typeof e=="function")return Su(e)?1:0;if(e!=null){if(e=e.$$typeof,e===Aa)return 11;if(e===Ba)return 14}return 2}function un(e,t){var n=e.alternate;return n===null?(n=it(e.tag,t,e.key,e.mode),n.elementType=e.elementType,n.type=e.type,n.stateNode=e.stateNode,n.alternate=e,e.alternate=n):(n.pendingProps=t,n.type=e.type,n.flags=0,n.subtreeFlags=0,n.deletions=null),n.flags=e.flags&14680064,n.childLanes=e.childLanes,n.lanes=e.lanes,n.child=e.child,n.memoizedProps=e.memoizedProps,n.memoizedState=e.memoizedState,n.updateQueue=e.updateQueue,t=e.dependencies,n.dependencies=t===null?null:{lanes:t.lanes,firstContext:t.firstContext},n.sibling=e.sibling,n.index=e.index,n.ref=e.ref,n}function ii(e,t,n,r,l,i){var o=2;if(r=e,typeof e=="function")Su(e)&&(o=1);else if(typeof e=="string")o=5;else e:switch(e){case Qn:return Rn(n.children,l,i,t);case Ua:o=8,l|=8;break;case Fo:return e=it(12,n,t,l|2),e.elementType=Fo,e.lanes=i,e;case Io:return e=it(13,n,t,l),e.elementType=Io,e.lanes=i,e;case Uo:return e=it(19,n,t,l),e.elementType=Uo,e.lanes=i,e;case Uc:return Ki(n,l,i,t);default:if(typeof e=="object"&&e!==null)switch(e.$$typeof){case Fc:o=10;break e;case Ic:o=9;break e;case Aa:o=11;break e;case Ba:o=14;break e;case Yt:o=16,r=null;break e}throw Error(R(130,e==null?e:typeof e,""))}return t=it(o,n,t,l),t.elementType=e,t.type=r,t.lanes=i,t}function Rn(e,t,n,r){return e=it(7,e,r,t),e.lanes=n,e}function Ki(e,t,n,r){return e=it(22,e,r,t),e.elementType=Uc,e.lanes=n,e.stateNode={isHidden:!1},e}function No(e,t,n){return e=it(6,e,null,t),e.lanes=n,e}function To(e,t,n){return t=it(4,e.children!==null?e.children:[],e.key,t),t.lanes=n,t.stateNode={containerInfo:e.containerInfo,pendingChildren:null,implementation:e.implementation},t}function zm(e,t,n,r,l){this.tag=t,this.containerInfo=e,this.finishedWork=this.pingCache=this.current=this.pendingChildren=null,this.timeoutHandle=-1,this.callbackNode=this.pendingContext=this.context=null,this.callbackPriority=0,this.eventTimes=so(0),this.expirationTimes=so(-1),this.entangledLanes=this.finishedLanes=this.mutableReadLanes=this.expiredLanes=this.pingedLanes=this.suspendedLanes=this.pendingLanes=0,this.entanglements=so(0),this.identifierPrefix=r,this.onRecoverableError=l,this.mutableSourceEagerHydrationData=null}function Eu(e,t,n,r,l,i,o,a,u){return e=new zm(e,t,n,a,u),t===1?(t=1,i===!0&&(t|=8)):t=0,i=it(3,null,null,t),e.current=i,i.stateNode=e,i.memoizedState={element:r,isDehydrated:n,cache:null,transitions:null,pendingSuspenseBoundaries:null},lu(i),e}function Om(e,t,n){var r=3"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(jd)}catch(e){console.error(e)}}jd(),Mc.exports=et;var Pu=Mc.exports;const Bm=Sc(Pu),Hm=wc({__proto__:null,default:Bm},[Pu]);var Zs=Pu;zo.createRoot=Zs.createRoot,zo.hydrateRoot=Zs.hydrateRoot;/** - * @remix-run/router v1.23.2 - * - * Copyright (c) Remix Software Inc. - * - * This source code is licensed under the MIT license found in the - * LICENSE.md file in the root directory of this source tree. - * - * @license MIT - */function ue(){return ue=Object.assign?Object.assign.bind():function(e){for(var t=1;t"u")throw new Error(t)}function jn(e,t){if(!e){typeof console<"u"&&console.warn(t);try{throw new Error(t)}catch{}}}function $m(){return Math.random().toString(36).substr(2,8)}function bs(e,t){return{usr:e.state,key:e.key,idx:t}}function hl(e,t,n,r){return n===void 0&&(n=null),ue({pathname:typeof e=="string"?e:e.pathname,search:"",hash:""},typeof t=="string"?hn(t):t,{state:n,key:t&&t.key||r||$m()})}function zn(e){let{pathname:t="/",search:n="",hash:r=""}=e;return n&&n!=="?"&&(t+=n.charAt(0)==="?"?n:"?"+n),r&&r!=="#"&&(t+=r.charAt(0)==="#"?r:"#"+r),t}function hn(e){let t={};if(e){let n=e.indexOf("#");n>=0&&(t.hash=e.substr(n),e=e.substr(0,n));let r=e.indexOf("?");r>=0&&(t.search=e.substr(r),e=e.substr(0,r)),e&&(t.pathname=e)}return t}function Wm(e,t,n,r){r===void 0&&(r={});let{window:l=document.defaultView,v5Compat:i=!1}=r,o=l.history,a=ge.Pop,u=null,s=d();s==null&&(s=0,o.replaceState(ue({},o.state,{idx:s}),""));function d(){return(o.state||{idx:null}).idx}function f(){a=ge.Pop;let D=d(),m=D==null?null:D-s;s=D,u&&u({action:a,location:E.location,delta:m})}function p(D,m){a=ge.Push;let c=hl(E.location,D,m);s=d()+1;let v=bs(c,s),k=E.createHref(c);try{o.pushState(v,"",k)}catch(L){if(L instanceof DOMException&&L.name==="DataCloneError")throw L;l.location.assign(k)}i&&u&&u({action:a,location:E.location,delta:1})}function w(D,m){a=ge.Replace;let c=hl(E.location,D,m);s=d();let v=bs(c,s),k=E.createHref(c);o.replaceState(v,"",k),i&&u&&u({action:a,location:E.location,delta:0})}function x(D){let m=l.location.origin!=="null"?l.location.origin:l.location.href,c=typeof D=="string"?D:zn(D);return c=c.replace(/ $/,"%20"),K(m,"No window.location.(origin|href) available to create URL for href: "+c),new URL(c,m)}let E={get action(){return a},get location(){return e(l,o)},listen(D){if(u)throw new Error("A history only accepts one active listener");return l.addEventListener(qs,f),u=D,()=>{l.removeEventListener(qs,f),u=null}},createHref(D){return t(l,D)},createURL:x,encodeLocation(D){let m=x(D);return{pathname:m.pathname,search:m.search,hash:m.hash}},push:p,replace:w,go(D){return o.go(D)}};return E}var b;(function(e){e.data="data",e.deferred="deferred",e.redirect="redirect",e.error="error"})(b||(b={}));const Qm=new Set(["lazy","caseSensitive","path","id","index","children"]);function Km(e){return e.index===!0}function Di(e,t,n,r){return n===void 0&&(n=[]),r===void 0&&(r={}),e.map((l,i)=>{let o=[...n,String(i)],a=typeof l.id=="string"?l.id:o.join("-");if(K(l.index!==!0||!l.children,"Cannot specify children on an index route"),K(!r[a],'Found a route id collision on id "'+a+`". Route id's must be globally unique within Data Router usages`),Km(l)){let u=ue({},l,t(l),{id:a});return r[a]=u,u}else{let u=ue({},l,t(l),{id:a,children:void 0});return r[a]=u,l.children&&(u.children=Di(l.children,t,o,r)),u}})}function Sn(e,t,n){return n===void 0&&(n="/"),oi(e,t,n,!1)}function oi(e,t,n,r){let l=typeof t=="string"?hn(t):t,i=It(l.pathname||"/",n);if(i==null)return null;let o=zd(e);Xm(o);let a=null;for(let u=0;a==null&&u{let u={relativePath:a===void 0?i.path||"":a,caseSensitive:i.caseSensitive===!0,childrenIndex:o,route:i};u.relativePath.startsWith("/")&&(K(u.relativePath.startsWith(r),'Absolute route path "'+u.relativePath+'" nested under path '+('"'+r+'" is not valid. An absolute child route path ')+"must start with the combined path of all its parent routes."),u.relativePath=u.relativePath.slice(r.length));let s=Mt([r,u.relativePath]),d=n.concat(u);i.children&&i.children.length>0&&(K(i.index!==!0,"Index routes must not have child routes. Please remove "+('all child routes from route path "'+s+'".')),zd(i.children,t,d,s)),!(i.path==null&&!i.index)&&t.push({path:s,score:tv(s,i.index),routesMeta:d})};return e.forEach((i,o)=>{var a;if(i.path===""||!((a=i.path)!=null&&a.includes("?")))l(i,o);else for(let u of Od(i.path))l(i,o,u)}),t}function Od(e){let t=e.split("/");if(t.length===0)return[];let[n,...r]=t,l=n.endsWith("?"),i=n.replace(/\?$/,"");if(r.length===0)return l?[i,""]:[i];let o=Od(r.join("/")),a=[];return a.push(...o.map(u=>u===""?i:[i,u].join("/"))),l&&a.push(...o),a.map(u=>e.startsWith("/")&&u===""?"/":u)}function Xm(e){e.sort((t,n)=>t.score!==n.score?n.score-t.score:nv(t.routesMeta.map(r=>r.childrenIndex),n.routesMeta.map(r=>r.childrenIndex)))}const Gm=/^:[\w-]+$/,Jm=3,Zm=2,qm=1,bm=10,ev=-2,ec=e=>e==="*";function tv(e,t){let n=e.split("/"),r=n.length;return n.some(ec)&&(r+=ev),t&&(r+=Zm),n.filter(l=>!ec(l)).reduce((l,i)=>l+(Gm.test(i)?Jm:i===""?qm:bm),r)}function nv(e,t){return e.length===t.length&&e.slice(0,-1).every((r,l)=>r===t[l])?e[e.length-1]-t[t.length-1]:0}function rv(e,t,n){n===void 0&&(n=!1);let{routesMeta:r}=e,l={},i="/",o=[];for(let a=0;a{let{paramName:p,isOptional:w}=d;if(p==="*"){let E=a[f]||"";o=i.slice(0,i.length-E.length).replace(/(.)\/+$/,"$1")}const x=a[f];return w&&!x?s[p]=void 0:s[p]=(x||"").replace(/%2F/g,"/"),s},{}),pathname:i,pathnameBase:o,pattern:e}}function lv(e,t,n){t===void 0&&(t=!1),n===void 0&&(n=!0),jn(e==="*"||!e.endsWith("*")||e.endsWith("/*"),'Route path "'+e+'" will be treated as if it were '+('"'+e.replace(/\*$/,"/*")+'" because the `*` character must ')+"always follow a `/` in the pattern. To get rid of this warning, "+('please change the route path to "'+e.replace(/\*$/,"/*")+'".'));let r=[],l="^"+e.replace(/\/*\*?$/,"").replace(/^\/*/,"/").replace(/[\\.*+^${}|()[\]]/g,"\\$&").replace(/\/:([\w-]+)(\?)?/g,(o,a,u)=>(r.push({paramName:a,isOptional:u!=null}),u?"/?([^\\/]+)?":"/([^\\/]+)"));return e.endsWith("*")?(r.push({paramName:"*"}),l+=e==="*"||e==="/*"?"(.*)$":"(?:\\/(.+)|\\/*)$"):n?l+="\\/*$":e!==""&&e!=="/"&&(l+="(?:(?=\\/|$))"),[new RegExp(l,t?void 0:"i"),r]}function iv(e){try{return e.split("/").map(t=>decodeURIComponent(t).replace(/\//g,"%2F")).join("/")}catch(t){return jn(!1,'The URL path "'+e+'" could not be decoded because it is is a malformed URL segment. This is probably due to a bad percent '+("encoding ("+t+").")),e}}function It(e,t){if(t==="/")return e;if(!e.toLowerCase().startsWith(t.toLowerCase()))return null;let n=t.endsWith("/")?t.length-1:t.length,r=e.charAt(n);return r&&r!=="/"?null:e.slice(n)||"/"}const ov=/^(?:[a-z][a-z0-9+.-]*:|\/\/)/i,av=e=>ov.test(e);function uv(e,t){t===void 0&&(t="/");let{pathname:n,search:r="",hash:l=""}=typeof e=="string"?hn(e):e,i;if(n)if(av(n))i=n;else{if(n.includes("//")){let o=n;n=n.replace(/\/\/+/g,"/"),jn(!1,"Pathnames cannot have embedded double slashes - normalizing "+(o+" -> "+n))}n.startsWith("/")?i=tc(n.substring(1),"/"):i=tc(n,t)}else i=t;return{pathname:i,search:cv(r),hash:fv(l)}}function tc(e,t){let n=t.replace(/\/+$/,"").split("/");return e.split("/").forEach(l=>{l===".."?n.length>1&&n.pop():l!=="."&&n.push(l)}),n.length>1?n.join("/"):"/"}function Do(e,t,n,r){return"Cannot include a '"+e+"' character in a manually specified "+("`to."+t+"` field ["+JSON.stringify(r)+"]. Please separate it out to the ")+("`to."+n+"` field. Alternatively you may provide the full path as ")+'a string in and the router will parse it for you.'}function Fd(e){return e.filter((t,n)=>n===0||t.route.path&&t.route.path.length>0)}function Zi(e,t){let n=Fd(e);return t?n.map((r,l)=>l===n.length-1?r.pathname:r.pathnameBase):n.map(r=>r.pathnameBase)}function qi(e,t,n,r){r===void 0&&(r=!1);let l;typeof e=="string"?l=hn(e):(l=ue({},e),K(!l.pathname||!l.pathname.includes("?"),Do("?","pathname","search",l)),K(!l.pathname||!l.pathname.includes("#"),Do("#","pathname","hash",l)),K(!l.search||!l.search.includes("#"),Do("#","search","hash",l)));let i=e===""||l.pathname==="",o=i?"/":l.pathname,a;if(o==null)a=n;else{let f=t.length-1;if(!r&&o.startsWith("..")){let p=o.split("/");for(;p[0]==="..";)p.shift(),f-=1;l.pathname=p.join("/")}a=f>=0?t[f]:"/"}let u=uv(l,a),s=o&&o!=="/"&&o.endsWith("/"),d=(i||o===".")&&n.endsWith("/");return!u.pathname.endsWith("/")&&(s||d)&&(u.pathname+="/"),u}const Mt=e=>e.join("/").replace(/\/\/+/g,"/"),sv=e=>e.replace(/\/+$/,"").replace(/^\/*/,"/"),cv=e=>!e||e==="?"?"":e.startsWith("?")?e:"?"+e,fv=e=>!e||e==="#"?"":e.startsWith("#")?e:"#"+e;class ji{constructor(t,n,r,l){l===void 0&&(l=!1),this.status=t,this.statusText=n||"",this.internal=l,r instanceof Error?(this.data=r.toString(),this.error=r):this.data=r}}function ml(e){return e!=null&&typeof e.status=="number"&&typeof e.statusText=="string"&&typeof e.internal=="boolean"&&"data"in e}const Id=["post","put","patch","delete"],dv=new Set(Id),pv=["get",...Id],hv=new Set(pv),mv=new Set([301,302,303,307,308]),vv=new Set([307,308]),Mo={state:"idle",location:void 0,formMethod:void 0,formAction:void 0,formEncType:void 0,formData:void 0,json:void 0,text:void 0},yv={state:"idle",data:void 0,formMethod:void 0,formAction:void 0,formEncType:void 0,formData:void 0,json:void 0,text:void 0},jr={state:"unblocked",proceed:void 0,reset:void 0,location:void 0},_u=/^(?:[a-z][a-z0-9+.-]*:|\/\/)/i,gv=e=>({hasErrorBoundary:!!e.hasErrorBoundary}),Ud="remix-router-transitions";function wv(e){const t=e.window?e.window:typeof window<"u"?window:void 0,n=typeof t<"u"&&typeof t.document<"u"&&typeof t.document.createElement<"u",r=!n;K(e.routes.length>0,"You must provide a non-empty routes array to createRouter");let l;if(e.mapRouteProperties)l=e.mapRouteProperties;else if(e.detectErrorBoundary){let h=e.detectErrorBoundary;l=g=>({hasErrorBoundary:h(g)})}else l=gv;let i={},o=Di(e.routes,l,void 0,i),a,u=e.basename||"/",s=e.dataStrategy||kv,d=e.patchRoutesOnNavigation,f=ue({v7_fetcherPersist:!1,v7_normalizeFormMethod:!1,v7_partialHydration:!1,v7_prependBasename:!1,v7_relativeSplatPath:!1,v7_skipActionErrorRevalidation:!1},e.future),p=null,w=new Set,x=null,E=null,D=null,m=e.hydrationData!=null,c=Sn(o,e.history.location,u),v=!1,k=null;if(c==null&&!d){let h=Ve(404,{pathname:e.history.location.pathname}),{matches:g,route:S}=dc(o);c=g,k={[S.id]:h}}c&&!e.hydrationData&&Pl(c,o,e.history.location.pathname).active&&(c=null);let L;if(c)if(c.some(h=>h.route.lazy))L=!1;else if(!c.some(h=>h.route.loader))L=!0;else if(f.v7_partialHydration){let h=e.hydrationData?e.hydrationData.loaderData:null,g=e.hydrationData?e.hydrationData.errors:null;if(g){let S=c.findIndex(P=>g[P.route.id]!==void 0);L=c.slice(0,S+1).every(P=>!La(P.route,h,g))}else L=c.every(S=>!La(S.route,h,g))}else L=e.hydrationData!=null;else if(L=!1,c=[],f.v7_partialHydration){let h=Pl(null,o,e.history.location.pathname);h.active&&h.matches&&(v=!0,c=h.matches)}let z,y={historyAction:e.history.action,location:e.history.location,matches:c,initialized:L,navigation:Mo,restoreScrollPosition:e.hydrationData!=null?!1:null,preventScrollReset:!1,revalidation:"idle",loaderData:e.hydrationData&&e.hydrationData.loaderData||{},actionData:e.hydrationData&&e.hydrationData.actionData||null,errors:e.hydrationData&&e.hydrationData.errors||k,fetchers:new Map,blockers:new Map},C=ge.Pop,H=!1,M,q=!1,re=new Map,Se=null,_e=!1,st=!1,Ht=[],Vt=new Set,N=new Map,V=0,W=-1,te=new Map,ne=new Set,ct=new Map,Xe=new Map,Oe=new Set,Fe=new Map,nt=new Map,xl;function Zd(){if(p=e.history.listen(h=>{let{action:g,location:S,delta:P}=h;if(xl){xl(),xl=void 0;return}jn(nt.size===0||P!=null,"You are trying to use a blocker on a POP navigation to a location that was not created by @remix-run/router. This will fail silently in production. This can happen if you are navigating outside the router via `window.history.pushState`/`window.location.hash` instead of using router navigation APIs. This can also happen if you are using createHashRouter and the user manually changes the URL.");let j=Au({currentLocation:y.location,nextLocation:S,historyAction:g});if(j&&P!=null){let B=new Promise($=>{xl=$});e.history.go(P*-1),Cl(j,{state:"blocked",location:S,proceed(){Cl(j,{state:"proceeding",proceed:void 0,reset:void 0,location:S}),B.then(()=>e.history.go(P))},reset(){let $=new Map(y.blockers);$.set(j,jr),Ie({blockers:$})}});return}return mn(g,S)}),n){Iv(t,re);let h=()=>Uv(t,re);t.addEventListener("pagehide",h),Se=()=>t.removeEventListener("pagehide",h)}return y.initialized||mn(ge.Pop,y.location,{initialHydration:!0}),z}function qd(){p&&p(),Se&&Se(),w.clear(),M&&M.abort(),y.fetchers.forEach((h,g)=>kl(g)),y.blockers.forEach((h,g)=>Uu(g))}function bd(h){return w.add(h),()=>w.delete(h)}function Ie(h,g){g===void 0&&(g={}),y=ue({},y,h);let S=[],P=[];f.v7_fetcherPersist&&y.fetchers.forEach((j,B)=>{j.state==="idle"&&(Oe.has(B)?P.push(B):S.push(B))}),Oe.forEach(j=>{!y.fetchers.has(j)&&!N.has(j)&&P.push(j)}),[...w].forEach(j=>j(y,{deletedFetchers:P,viewTransitionOpts:g.viewTransitionOpts,flushSync:g.flushSync===!0})),f.v7_fetcherPersist?(S.forEach(j=>y.fetchers.delete(j)),P.forEach(j=>kl(j))):P.forEach(j=>Oe.delete(j))}function Un(h,g,S){var P,j;let{flushSync:B}=S===void 0?{}:S,$=y.actionData!=null&&y.navigation.formMethod!=null&&ht(y.navigation.formMethod)&&y.navigation.state==="loading"&&((P=h.state)==null?void 0:P._isRedirect)!==!0,I;g.actionData?Object.keys(g.actionData).length>0?I=g.actionData:I=null:$?I=y.actionData:I=null;let U=g.loaderData?cc(y.loaderData,g.loaderData,g.matches||[],g.errors):y.loaderData,F=y.blockers;F.size>0&&(F=new Map(F),F.forEach((X,Re)=>F.set(Re,jr)));let A=H===!0||y.navigation.formMethod!=null&&ht(y.navigation.formMethod)&&((j=h.state)==null?void 0:j._isRedirect)!==!0;a&&(o=a,a=void 0),_e||C===ge.Pop||(C===ge.Push?e.history.push(h,h.state):C===ge.Replace&&e.history.replace(h,h.state));let Q;if(C===ge.Pop){let X=re.get(y.location.pathname);X&&X.has(h.pathname)?Q={currentLocation:y.location,nextLocation:h}:re.has(h.pathname)&&(Q={currentLocation:h,nextLocation:y.location})}else if(q){let X=re.get(y.location.pathname);X?X.add(h.pathname):(X=new Set([h.pathname]),re.set(y.location.pathname,X)),Q={currentLocation:y.location,nextLocation:h}}Ie(ue({},g,{actionData:I,loaderData:U,historyAction:C,location:h,initialized:!0,navigation:Mo,revalidation:"idle",restoreScrollPosition:Hu(h,g.matches||y.matches),preventScrollReset:A,blockers:F}),{viewTransitionOpts:Q,flushSync:B===!0}),C=ge.Pop,H=!1,q=!1,_e=!1,st=!1,Ht=[]}async function Du(h,g){if(typeof h=="number"){e.history.go(h);return}let S=Ra(y.location,y.matches,u,f.v7_prependBasename,h,f.v7_relativeSplatPath,g==null?void 0:g.fromRouteId,g==null?void 0:g.relative),{path:P,submission:j,error:B}=nc(f.v7_normalizeFormMethod,!1,S,g),$=y.location,I=hl(y.location,P,g&&g.state);I=ue({},I,e.history.encodeLocation(I));let U=g&&g.replace!=null?g.replace:void 0,F=ge.Push;U===!0?F=ge.Replace:U===!1||j!=null&&ht(j.formMethod)&&j.formAction===y.location.pathname+y.location.search&&(F=ge.Replace);let A=g&&"preventScrollReset"in g?g.preventScrollReset===!0:void 0,Q=(g&&g.flushSync)===!0,X=Au({currentLocation:$,nextLocation:I,historyAction:F});if(X){Cl(X,{state:"blocked",location:I,proceed(){Cl(X,{state:"proceeding",proceed:void 0,reset:void 0,location:I}),Du(h,g)},reset(){let Re=new Map(y.blockers);Re.set(X,jr),Ie({blockers:Re})}});return}return await mn(F,I,{submission:j,pendingError:B,preventScrollReset:A,replace:g&&g.replace,enableViewTransition:g&&g.viewTransition,flushSync:Q})}function ep(){if(eo(),Ie({revalidation:"loading"}),y.navigation.state!=="submitting"){if(y.navigation.state==="idle"){mn(y.historyAction,y.location,{startUninterruptedRevalidation:!0});return}mn(C||y.historyAction,y.navigation.location,{overrideNavigation:y.navigation,enableViewTransition:q===!0})}}async function mn(h,g,S){M&&M.abort(),M=null,C=h,_e=(S&&S.startUninterruptedRevalidation)===!0,cp(y.location,y.matches),H=(S&&S.preventScrollReset)===!0,q=(S&&S.enableViewTransition)===!0;let P=a||o,j=S&&S.overrideNavigation,B=S!=null&&S.initialHydration&&y.matches&&y.matches.length>0&&!v?y.matches:Sn(P,g,u),$=(S&&S.flushSync)===!0;if(B&&y.initialized&&!st&&Nv(y.location,g)&&!(S&&S.submission&&ht(S.submission.formMethod))){Un(g,{matches:B},{flushSync:$});return}let I=Pl(B,P,g.pathname);if(I.active&&I.matches&&(B=I.matches),!B){let{error:le,notFoundMatches:Z,route:pe}=to(g.pathname);Un(g,{matches:Z,loaderData:{},errors:{[pe.id]:le}},{flushSync:$});return}M=new AbortController;let U=$n(e.history,g,M.signal,S&&S.submission),F;if(S&&S.pendingError)F=[En(B).route.id,{type:b.error,error:S.pendingError}];else if(S&&S.submission&&ht(S.submission.formMethod)){let le=await tp(U,g,S.submission,B,I.active,{replace:S.replace,flushSync:$});if(le.shortCircuited)return;if(le.pendingActionResult){let[Z,pe]=le.pendingActionResult;if(Je(pe)&&ml(pe.error)&&pe.error.status===404){M=null,Un(g,{matches:le.matches,loaderData:{},errors:{[Z]:pe.error}});return}}B=le.matches||B,F=le.pendingActionResult,j=jo(g,S.submission),$=!1,I.active=!1,U=$n(e.history,U.url,U.signal)}let{shortCircuited:A,matches:Q,loaderData:X,errors:Re}=await np(U,g,B,I.active,j,S&&S.submission,S&&S.fetcherSubmission,S&&S.replace,S&&S.initialHydration===!0,$,F);A||(M=null,Un(g,ue({matches:Q||B},fc(F),{loaderData:X,errors:Re})))}async function tp(h,g,S,P,j,B){B===void 0&&(B={}),eo();let $=Ov(g,S);if(Ie({navigation:$},{flushSync:B.flushSync===!0}),j){let F=await _l(P,g.pathname,h.signal);if(F.type==="aborted")return{shortCircuited:!0};if(F.type==="error"){let A=En(F.partialMatches).route.id;return{matches:F.partialMatches,pendingActionResult:[A,{type:b.error,error:F.error}]}}else if(F.matches)P=F.matches;else{let{notFoundMatches:A,error:Q,route:X}=to(g.pathname);return{matches:A,pendingActionResult:[X.id,{type:b.error,error:Q}]}}}let I,U=Br(P,g);if(!U.route.action&&!U.route.lazy)I={type:b.error,error:Ve(405,{method:h.method,pathname:g.pathname,routeId:U.route.id})};else if(I=(await Er("action",y,h,[U],P,null))[U.route.id],h.signal.aborted)return{shortCircuited:!0};if(Pn(I)){let F;return B&&B.replace!=null?F=B.replace:F=ac(I.response.headers.get("Location"),new URL(h.url),u,e.history)===y.location.pathname+y.location.search,await vn(h,I,!0,{submission:S,replace:F}),{shortCircuited:!0}}if(bt(I))throw Ve(400,{type:"defer-action"});if(Je(I)){let F=En(P,U.route.id);return(B&&B.replace)!==!0&&(C=ge.Push),{matches:P,pendingActionResult:[F.route.id,I]}}return{matches:P,pendingActionResult:[U.route.id,I]}}async function np(h,g,S,P,j,B,$,I,U,F,A){let Q=j||jo(g,B),X=B||$||hc(Q),Re=!_e&&(!f.v7_partialHydration||!U);if(P){if(Re){let he=Mu(A);Ie(ue({navigation:Q},he!==void 0?{actionData:he}:{}),{flushSync:F})}let J=await _l(S,g.pathname,h.signal);if(J.type==="aborted")return{shortCircuited:!0};if(J.type==="error"){let he=En(J.partialMatches).route.id;return{matches:J.partialMatches,loaderData:{},errors:{[he]:J.error}}}else if(J.matches)S=J.matches;else{let{error:he,notFoundMatches:Bn,route:Cr}=to(g.pathname);return{matches:Bn,loaderData:{},errors:{[Cr.id]:he}}}}let le=a||o,[Z,pe]=lc(e.history,y,S,X,g,f.v7_partialHydration&&U===!0,f.v7_skipActionErrorRevalidation,st,Ht,Vt,Oe,ct,ne,le,u,A);if(no(J=>!(S&&S.some(he=>he.route.id===J))||Z&&Z.some(he=>he.route.id===J)),W=++V,Z.length===0&&pe.length===0){let J=Fu();return Un(g,ue({matches:S,loaderData:{},errors:A&&Je(A[1])?{[A[0]]:A[1].error}:null},fc(A),J?{fetchers:new Map(y.fetchers)}:{}),{flushSync:F}),{shortCircuited:!0}}if(Re){let J={};if(!P){J.navigation=Q;let he=Mu(A);he!==void 0&&(J.actionData=he)}pe.length>0&&(J.fetchers=rp(pe)),Ie(J,{flushSync:F})}pe.forEach(J=>{Wt(J.key),J.controller&&N.set(J.key,J.controller)});let An=()=>pe.forEach(J=>Wt(J.key));M&&M.signal.addEventListener("abort",An);let{loaderResults:xr,fetcherResults:_t}=await ju(y,S,Z,pe,h);if(h.signal.aborted)return{shortCircuited:!0};M&&M.signal.removeEventListener("abort",An),pe.forEach(J=>N.delete(J.key));let wt=Kl(xr);if(wt)return await vn(h,wt.result,!0,{replace:I}),{shortCircuited:!0};if(wt=Kl(_t),wt)return ne.add(wt.key),await vn(h,wt.result,!0,{replace:I}),{shortCircuited:!0};let{loaderData:ro,errors:kr}=sc(y,S,xr,A,pe,_t,Fe);Fe.forEach((J,he)=>{J.subscribe(Bn=>{(Bn||J.done)&&Fe.delete(he)})}),f.v7_partialHydration&&U&&y.errors&&(kr=ue({},y.errors,kr));let yn=Fu(),Rl=Iu(W),Ll=yn||Rl||pe.length>0;return ue({matches:S,loaderData:ro,errors:kr},Ll?{fetchers:new Map(y.fetchers)}:{})}function Mu(h){if(h&&!Je(h[1]))return{[h[0]]:h[1].data};if(y.actionData)return Object.keys(y.actionData).length===0?null:y.actionData}function rp(h){return h.forEach(g=>{let S=y.fetchers.get(g.key),P=zr(void 0,S?S.data:void 0);y.fetchers.set(g.key,P)}),new Map(y.fetchers)}function lp(h,g,S,P){if(r)throw new Error("router.fetch() was called during the server render, but it shouldn't be. You are likely calling a useFetcher() method in the body of your component. Try moving it to a useEffect or a callback.");Wt(h);let j=(P&&P.flushSync)===!0,B=a||o,$=Ra(y.location,y.matches,u,f.v7_prependBasename,S,f.v7_relativeSplatPath,g,P==null?void 0:P.relative),I=Sn(B,$,u),U=Pl(I,B,$);if(U.active&&U.matches&&(I=U.matches),!I){Pt(h,g,Ve(404,{pathname:$}),{flushSync:j});return}let{path:F,submission:A,error:Q}=nc(f.v7_normalizeFormMethod,!0,$,P);if(Q){Pt(h,g,Q,{flushSync:j});return}let X=Br(I,F),Re=(P&&P.preventScrollReset)===!0;if(A&&ht(A.formMethod)){ip(h,g,F,X,I,U.active,j,Re,A);return}ct.set(h,{routeId:g,path:F}),op(h,g,F,X,I,U.active,j,Re,A)}async function ip(h,g,S,P,j,B,$,I,U){eo(),ct.delete(h);function F(ye){if(!ye.route.action&&!ye.route.lazy){let Hn=Ve(405,{method:U.formMethod,pathname:S,routeId:g});return Pt(h,g,Hn,{flushSync:$}),!0}return!1}if(!B&&F(P))return;let A=y.fetchers.get(h);$t(h,Fv(U,A),{flushSync:$});let Q=new AbortController,X=$n(e.history,S,Q.signal,U);if(B){let ye=await _l(j,new URL(X.url).pathname,X.signal,h);if(ye.type==="aborted")return;if(ye.type==="error"){Pt(h,g,ye.error,{flushSync:$});return}else if(ye.matches){if(j=ye.matches,P=Br(j,S),F(P))return}else{Pt(h,g,Ve(404,{pathname:S}),{flushSync:$});return}}N.set(h,Q);let Re=V,Z=(await Er("action",y,X,[P],j,h))[P.route.id];if(X.signal.aborted){N.get(h)===Q&&N.delete(h);return}if(f.v7_fetcherPersist&&Oe.has(h)){if(Pn(Z)||Je(Z)){$t(h,Kt(void 0));return}}else{if(Pn(Z))if(N.delete(h),W>Re){$t(h,Kt(void 0));return}else return ne.add(h),$t(h,zr(U)),vn(X,Z,!1,{fetcherSubmission:U,preventScrollReset:I});if(Je(Z)){Pt(h,g,Z.error);return}}if(bt(Z))throw Ve(400,{type:"defer-action"});let pe=y.navigation.location||y.location,An=$n(e.history,pe,Q.signal),xr=a||o,_t=y.navigation.state!=="idle"?Sn(xr,y.navigation.location,u):y.matches;K(_t,"Didn't find any matches after fetcher action");let wt=++V;te.set(h,wt);let ro=zr(U,Z.data);y.fetchers.set(h,ro);let[kr,yn]=lc(e.history,y,_t,U,pe,!1,f.v7_skipActionErrorRevalidation,st,Ht,Vt,Oe,ct,ne,xr,u,[P.route.id,Z]);yn.filter(ye=>ye.key!==h).forEach(ye=>{let Hn=ye.key,Vu=y.fetchers.get(Hn),pp=zr(void 0,Vu?Vu.data:void 0);y.fetchers.set(Hn,pp),Wt(Hn),ye.controller&&N.set(Hn,ye.controller)}),Ie({fetchers:new Map(y.fetchers)});let Rl=()=>yn.forEach(ye=>Wt(ye.key));Q.signal.addEventListener("abort",Rl);let{loaderResults:Ll,fetcherResults:J}=await ju(y,_t,kr,yn,An);if(Q.signal.aborted)return;Q.signal.removeEventListener("abort",Rl),te.delete(h),N.delete(h),yn.forEach(ye=>N.delete(ye.key));let he=Kl(Ll);if(he)return vn(An,he.result,!1,{preventScrollReset:I});if(he=Kl(J),he)return ne.add(he.key),vn(An,he.result,!1,{preventScrollReset:I});let{loaderData:Bn,errors:Cr}=sc(y,_t,Ll,void 0,yn,J,Fe);if(y.fetchers.has(h)){let ye=Kt(Z.data);y.fetchers.set(h,ye)}Iu(wt),y.navigation.state==="loading"&&wt>W?(K(C,"Expected pending action"),M&&M.abort(),Un(y.navigation.location,{matches:_t,loaderData:Bn,errors:Cr,fetchers:new Map(y.fetchers)})):(Ie({errors:Cr,loaderData:cc(y.loaderData,Bn,_t,Cr),fetchers:new Map(y.fetchers)}),st=!1)}async function op(h,g,S,P,j,B,$,I,U){let F=y.fetchers.get(h);$t(h,zr(U,F?F.data:void 0),{flushSync:$});let A=new AbortController,Q=$n(e.history,S,A.signal);if(B){let Z=await _l(j,new URL(Q.url).pathname,Q.signal,h);if(Z.type==="aborted")return;if(Z.type==="error"){Pt(h,g,Z.error,{flushSync:$});return}else if(Z.matches)j=Z.matches,P=Br(j,S);else{Pt(h,g,Ve(404,{pathname:S}),{flushSync:$});return}}N.set(h,A);let X=V,le=(await Er("loader",y,Q,[P],j,h))[P.route.id];if(bt(le)&&(le=await Ru(le,Q.signal,!0)||le),N.get(h)===A&&N.delete(h),!Q.signal.aborted){if(Oe.has(h)){$t(h,Kt(void 0));return}if(Pn(le))if(W>X){$t(h,Kt(void 0));return}else{ne.add(h),await vn(Q,le,!1,{preventScrollReset:I});return}if(Je(le)){Pt(h,g,le.error);return}K(!bt(le),"Unhandled fetcher deferred data"),$t(h,Kt(le.data))}}async function vn(h,g,S,P){let{submission:j,fetcherSubmission:B,preventScrollReset:$,replace:I}=P===void 0?{}:P;g.response.headers.has("X-Remix-Revalidate")&&(st=!0);let U=g.response.headers.get("Location");K(U,"Expected a Location header on the redirect Response"),U=ac(U,new URL(h.url),u,e.history);let F=hl(y.location,U,{_isRedirect:!0});if(n){let Z=!1;if(g.response.headers.has("X-Remix-Reload-Document"))Z=!0;else if(_u.test(U)){const pe=e.history.createURL(U);Z=pe.origin!==t.location.origin||It(pe.pathname,u)==null}if(Z){I?t.location.replace(U):t.location.assign(U);return}}M=null;let A=I===!0||g.response.headers.has("X-Remix-Replace")?ge.Replace:ge.Push,{formMethod:Q,formAction:X,formEncType:Re}=y.navigation;!j&&!B&&Q&&X&&Re&&(j=hc(y.navigation));let le=j||B;if(vv.has(g.response.status)&&le&&ht(le.formMethod))await mn(A,F,{submission:ue({},le,{formAction:U}),preventScrollReset:$||H,enableViewTransition:S?q:void 0});else{let Z=jo(F,j);await mn(A,F,{overrideNavigation:Z,fetcherSubmission:B,preventScrollReset:$||H,enableViewTransition:S?q:void 0})}}async function Er(h,g,S,P,j,B){let $,I={};try{$=await Cv(s,h,g,S,P,j,B,i,l)}catch(U){return P.forEach(F=>{I[F.route.id]={type:b.error,error:U}}),I}for(let[U,F]of Object.entries($))if(Tv(F)){let A=F.result;I[U]={type:b.redirect,response:Rv(A,S,U,j,u,f.v7_relativeSplatPath)}}else I[U]=await _v(F);return I}async function ju(h,g,S,P,j){let B=h.matches,$=Er("loader",h,j,S,g,null),I=Promise.all(P.map(async A=>{if(A.matches&&A.match&&A.controller){let X=(await Er("loader",h,$n(e.history,A.path,A.controller.signal),[A.match],A.matches,A.key))[A.match.route.id];return{[A.key]:X}}else return Promise.resolve({[A.key]:{type:b.error,error:Ve(404,{pathname:A.path})}})})),U=await $,F=(await I).reduce((A,Q)=>Object.assign(A,Q),{});return await Promise.all([jv(g,U,j.signal,B,h.loaderData),zv(g,F,P)]),{loaderResults:U,fetcherResults:F}}function eo(){st=!0,Ht.push(...no()),ct.forEach((h,g)=>{N.has(g)&&Vt.add(g),Wt(g)})}function $t(h,g,S){S===void 0&&(S={}),y.fetchers.set(h,g),Ie({fetchers:new Map(y.fetchers)},{flushSync:(S&&S.flushSync)===!0})}function Pt(h,g,S,P){P===void 0&&(P={});let j=En(y.matches,g);kl(h),Ie({errors:{[j.route.id]:S},fetchers:new Map(y.fetchers)},{flushSync:(P&&P.flushSync)===!0})}function zu(h){return Xe.set(h,(Xe.get(h)||0)+1),Oe.has(h)&&Oe.delete(h),y.fetchers.get(h)||yv}function kl(h){let g=y.fetchers.get(h);N.has(h)&&!(g&&g.state==="loading"&&te.has(h))&&Wt(h),ct.delete(h),te.delete(h),ne.delete(h),f.v7_fetcherPersist&&Oe.delete(h),Vt.delete(h),y.fetchers.delete(h)}function ap(h){let g=(Xe.get(h)||0)-1;g<=0?(Xe.delete(h),Oe.add(h),f.v7_fetcherPersist||kl(h)):Xe.set(h,g),Ie({fetchers:new Map(y.fetchers)})}function Wt(h){let g=N.get(h);g&&(g.abort(),N.delete(h))}function Ou(h){for(let g of h){let S=zu(g),P=Kt(S.data);y.fetchers.set(g,P)}}function Fu(){let h=[],g=!1;for(let S of ne){let P=y.fetchers.get(S);K(P,"Expected fetcher: "+S),P.state==="loading"&&(ne.delete(S),h.push(S),g=!0)}return Ou(h),g}function Iu(h){let g=[];for(let[S,P]of te)if(P0}function up(h,g){let S=y.blockers.get(h)||jr;return nt.get(h)!==g&&nt.set(h,g),S}function Uu(h){y.blockers.delete(h),nt.delete(h)}function Cl(h,g){let S=y.blockers.get(h)||jr;K(S.state==="unblocked"&&g.state==="blocked"||S.state==="blocked"&&g.state==="blocked"||S.state==="blocked"&&g.state==="proceeding"||S.state==="blocked"&&g.state==="unblocked"||S.state==="proceeding"&&g.state==="unblocked","Invalid blocker state transition: "+S.state+" -> "+g.state);let P=new Map(y.blockers);P.set(h,g),Ie({blockers:P})}function Au(h){let{currentLocation:g,nextLocation:S,historyAction:P}=h;if(nt.size===0)return;nt.size>1&&jn(!1,"A router only supports one blocker at a time");let j=Array.from(nt.entries()),[B,$]=j[j.length-1],I=y.blockers.get(B);if(!(I&&I.state==="proceeding")&&$({currentLocation:g,nextLocation:S,historyAction:P}))return B}function to(h){let g=Ve(404,{pathname:h}),S=a||o,{matches:P,route:j}=dc(S);return no(),{notFoundMatches:P,route:j,error:g}}function no(h){let g=[];return Fe.forEach((S,P)=>{(!h||h(P))&&(S.cancel(),g.push(P),Fe.delete(P))}),g}function sp(h,g,S){if(x=h,D=g,E=S||null,!m&&y.navigation===Mo){m=!0;let P=Hu(y.location,y.matches);P!=null&&Ie({restoreScrollPosition:P})}return()=>{x=null,D=null,E=null}}function Bu(h,g){return E&&E(h,g.map(P=>Ym(P,y.loaderData)))||h.key}function cp(h,g){if(x&&D){let S=Bu(h,g);x[S]=D()}}function Hu(h,g){if(x){let S=Bu(h,g),P=x[S];if(typeof P=="number")return P}return null}function Pl(h,g,S){if(d)if(h){if(Object.keys(h[0].params).length>0)return{active:!0,matches:oi(g,S,u,!0)}}else return{active:!0,matches:oi(g,S,u,!0)||[]};return{active:!1,matches:null}}async function _l(h,g,S,P){if(!d)return{type:"success",matches:h};let j=h;for(;;){let B=a==null,$=a||o,I=i;try{await d({signal:S,path:g,matches:j,fetcherKey:P,patch:(A,Q)=>{S.aborted||oc(A,Q,$,I,l)}})}catch(A){return{type:"error",error:A,partialMatches:j}}finally{B&&!S.aborted&&(o=[...o])}if(S.aborted)return{type:"aborted"};let U=Sn($,g,u);if(U)return{type:"success",matches:U};let F=oi($,g,u,!0);if(!F||j.length===F.length&&j.every((A,Q)=>A.route.id===F[Q].route.id))return{type:"success",matches:null};j=F}}function fp(h){i={},a=Di(h,l,void 0,i)}function dp(h,g){let S=a==null;oc(h,g,a||o,i,l),S&&(o=[...o],Ie({}))}return z={get basename(){return u},get future(){return f},get state(){return y},get routes(){return o},get window(){return t},initialize:Zd,subscribe:bd,enableScrollRestoration:sp,navigate:Du,fetch:lp,revalidate:ep,createHref:h=>e.history.createHref(h),encodeLocation:h=>e.history.encodeLocation(h),getFetcher:zu,deleteFetcher:ap,dispose:qd,getBlocker:up,deleteBlocker:Uu,patchRoutes:dp,_internalFetchControllers:N,_internalActiveDeferreds:Fe,_internalSetRoutes:fp},z}function Sv(e){return e!=null&&("formData"in e&&e.formData!=null||"body"in e&&e.body!==void 0)}function Ra(e,t,n,r,l,i,o,a){let u,s;if(o){u=[];for(let f of t)if(u.push(f),f.route.id===o){s=f;break}}else u=t,s=t[t.length-1];let d=qi(l||".",Zi(u,i),It(e.pathname,n)||e.pathname,a==="path");if(l==null&&(d.search=e.search,d.hash=e.hash),(l==null||l===""||l===".")&&s){let f=Lu(d.search);if(s.route.index&&!f)d.search=d.search?d.search.replace(/^\?/,"?index&"):"?index";else if(!s.route.index&&f){let p=new URLSearchParams(d.search),w=p.getAll("index");p.delete("index"),w.filter(E=>E).forEach(E=>p.append("index",E));let x=p.toString();d.search=x?"?"+x:""}}return r&&n!=="/"&&(d.pathname=d.pathname==="/"?n:Mt([n,d.pathname])),zn(d)}function nc(e,t,n,r){if(!r||!Sv(r))return{path:n};if(r.formMethod&&!Mv(r.formMethod))return{path:n,error:Ve(405,{method:r.formMethod})};let l=()=>({path:n,error:Ve(400,{type:"invalid-body"})}),i=r.formMethod||"get",o=e?i.toUpperCase():i.toLowerCase(),a=Hd(n);if(r.body!==void 0){if(r.formEncType==="text/plain"){if(!ht(o))return l();let p=typeof r.body=="string"?r.body:r.body instanceof FormData||r.body instanceof URLSearchParams?Array.from(r.body.entries()).reduce((w,x)=>{let[E,D]=x;return""+w+E+"="+D+` -`},""):String(r.body);return{path:n,submission:{formMethod:o,formAction:a,formEncType:r.formEncType,formData:void 0,json:void 0,text:p}}}else if(r.formEncType==="application/json"){if(!ht(o))return l();try{let p=typeof r.body=="string"?JSON.parse(r.body):r.body;return{path:n,submission:{formMethod:o,formAction:a,formEncType:r.formEncType,formData:void 0,json:p,text:void 0}}}catch{return l()}}}K(typeof FormData=="function","FormData is not available in this environment");let u,s;if(r.formData)u=Na(r.formData),s=r.formData;else if(r.body instanceof FormData)u=Na(r.body),s=r.body;else if(r.body instanceof URLSearchParams)u=r.body,s=uc(u);else if(r.body==null)u=new URLSearchParams,s=new FormData;else try{u=new URLSearchParams(r.body),s=uc(u)}catch{return l()}let d={formMethod:o,formAction:a,formEncType:r&&r.formEncType||"application/x-www-form-urlencoded",formData:s,json:void 0,text:void 0};if(ht(d.formMethod))return{path:n,submission:d};let f=hn(n);return t&&f.search&&Lu(f.search)&&u.append("index",""),f.search="?"+u,{path:zn(f),submission:d}}function rc(e,t,n){n===void 0&&(n=!1);let r=e.findIndex(l=>l.route.id===t);return r>=0?e.slice(0,n?r+1:r):e}function lc(e,t,n,r,l,i,o,a,u,s,d,f,p,w,x,E){let D=E?Je(E[1])?E[1].error:E[1].data:void 0,m=e.createURL(t.location),c=e.createURL(l),v=n;i&&t.errors?v=rc(n,Object.keys(t.errors)[0],!0):E&&Je(E[1])&&(v=rc(n,E[0]));let k=E?E[1].statusCode:void 0,L=o&&k&&k>=400,z=v.filter((C,H)=>{let{route:M}=C;if(M.lazy)return!0;if(M.loader==null)return!1;if(i)return La(M,t.loaderData,t.errors);if(Ev(t.loaderData,t.matches[H],C)||u.some(Se=>Se===C.route.id))return!0;let q=t.matches[H],re=C;return ic(C,ue({currentUrl:m,currentParams:q.params,nextUrl:c,nextParams:re.params},r,{actionResult:D,actionStatus:k,defaultShouldRevalidate:L?!1:a||m.pathname+m.search===c.pathname+c.search||m.search!==c.search||Ad(q,re)}))}),y=[];return f.forEach((C,H)=>{if(i||!n.some(_e=>_e.route.id===C.routeId)||d.has(H))return;let M=Sn(w,C.path,x);if(!M){y.push({key:H,routeId:C.routeId,path:C.path,matches:null,match:null,controller:null});return}let q=t.fetchers.get(H),re=Br(M,C.path),Se=!1;p.has(H)?Se=!1:s.has(H)?(s.delete(H),Se=!0):q&&q.state!=="idle"&&q.data===void 0?Se=a:Se=ic(re,ue({currentUrl:m,currentParams:t.matches[t.matches.length-1].params,nextUrl:c,nextParams:n[n.length-1].params},r,{actionResult:D,actionStatus:k,defaultShouldRevalidate:L?!1:a})),Se&&y.push({key:H,routeId:C.routeId,path:C.path,matches:M,match:re,controller:new AbortController})}),[z,y]}function La(e,t,n){if(e.lazy)return!0;if(!e.loader)return!1;let r=t!=null&&t[e.id]!==void 0,l=n!=null&&n[e.id]!==void 0;return!r&&l?!1:typeof e.loader=="function"&&e.loader.hydrate===!0?!0:!r&&!l}function Ev(e,t,n){let r=!t||n.route.id!==t.route.id,l=e[n.route.id]===void 0;return r||l}function Ad(e,t){let n=e.route.path;return e.pathname!==t.pathname||n!=null&&n.endsWith("*")&&e.params["*"]!==t.params["*"]}function ic(e,t){if(e.route.shouldRevalidate){let n=e.route.shouldRevalidate(t);if(typeof n=="boolean")return n}return t.defaultShouldRevalidate}function oc(e,t,n,r,l){var i;let o;if(e){let s=r[e];K(s,"No route found to patch children into: routeId = "+e),s.children||(s.children=[]),o=s.children}else o=n;let a=t.filter(s=>!o.some(d=>Bd(s,d))),u=Di(a,l,[e||"_","patch",String(((i=o)==null?void 0:i.length)||"0")],r);o.push(...u)}function Bd(e,t){return"id"in e&&"id"in t&&e.id===t.id?!0:e.index===t.index&&e.path===t.path&&e.caseSensitive===t.caseSensitive?(!e.children||e.children.length===0)&&(!t.children||t.children.length===0)?!0:e.children.every((n,r)=>{var l;return(l=t.children)==null?void 0:l.some(i=>Bd(n,i))}):!1}async function xv(e,t,n){if(!e.lazy)return;let r=await e.lazy();if(!e.lazy)return;let l=n[e.id];K(l,"No route found in manifest");let i={};for(let o in r){let u=l[o]!==void 0&&o!=="hasErrorBoundary";jn(!u,'Route "'+l.id+'" has a static property "'+o+'" defined but its lazy function is also returning a value for this property. '+('The lazy route property "'+o+'" will be ignored.')),!u&&!Qm.has(o)&&(i[o]=r[o])}Object.assign(l,i),Object.assign(l,ue({},t(l),{lazy:void 0}))}async function kv(e){let{matches:t}=e,n=t.filter(l=>l.shouldLoad);return(await Promise.all(n.map(l=>l.resolve()))).reduce((l,i,o)=>Object.assign(l,{[n[o].route.id]:i}),{})}async function Cv(e,t,n,r,l,i,o,a,u,s){let d=i.map(w=>w.route.lazy?xv(w.route,u,a):void 0),f=i.map((w,x)=>{let E=d[x],D=l.some(c=>c.route.id===w.route.id);return ue({},w,{shouldLoad:D,resolve:async c=>(c&&r.method==="GET"&&(w.route.lazy||w.route.loader)&&(D=!0),D?Pv(t,r,w,E,c,s):Promise.resolve({type:b.data,result:void 0}))})}),p=await e({matches:f,request:r,params:i[0].params,fetcherKey:o,context:s});try{await Promise.all(d)}catch{}return p}async function Pv(e,t,n,r,l,i){let o,a,u=s=>{let d,f=new Promise((x,E)=>d=E);a=()=>d(),t.signal.addEventListener("abort",a);let p=x=>typeof s!="function"?Promise.reject(new Error("You cannot call the handler for a route which defines a boolean "+('"'+e+'" [routeId: '+n.route.id+"]"))):s({request:t,params:n.params,context:i},...x!==void 0?[x]:[]),w=(async()=>{try{return{type:"data",result:await(l?l(E=>p(E)):p())}}catch(x){return{type:"error",result:x}}})();return Promise.race([w,f])};try{let s=n.route[e];if(r)if(s){let d,[f]=await Promise.all([u(s).catch(p=>{d=p}),r]);if(d!==void 0)throw d;o=f}else if(await r,s=n.route[e],s)o=await u(s);else if(e==="action"){let d=new URL(t.url),f=d.pathname+d.search;throw Ve(405,{method:t.method,pathname:f,routeId:n.route.id})}else return{type:b.data,result:void 0};else if(s)o=await u(s);else{let d=new URL(t.url),f=d.pathname+d.search;throw Ve(404,{pathname:f})}K(o.result!==void 0,"You defined "+(e==="action"?"an action":"a loader")+" for route "+('"'+n.route.id+"\" but didn't return anything from your `"+e+"` ")+"function. Please return a value or `null`.")}catch(s){return{type:b.error,result:s}}finally{a&&t.signal.removeEventListener("abort",a)}return o}async function _v(e){let{result:t,type:n}=e;if(Vd(t)){let f;try{let p=t.headers.get("Content-Type");p&&/\bapplication\/json\b/.test(p)?t.body==null?f=null:f=await t.json():f=await t.text()}catch(p){return{type:b.error,error:p}}return n===b.error?{type:b.error,error:new ji(t.status,t.statusText,f),statusCode:t.status,headers:t.headers}:{type:b.data,data:f,statusCode:t.status,headers:t.headers}}if(n===b.error){if(pc(t)){var r,l;if(t.data instanceof Error){var i,o;return{type:b.error,error:t.data,statusCode:(i=t.init)==null?void 0:i.status,headers:(o=t.init)!=null&&o.headers?new Headers(t.init.headers):void 0}}return{type:b.error,error:new ji(((r=t.init)==null?void 0:r.status)||500,void 0,t.data),statusCode:ml(t)?t.status:void 0,headers:(l=t.init)!=null&&l.headers?new Headers(t.init.headers):void 0}}return{type:b.error,error:t,statusCode:ml(t)?t.status:void 0}}if(Dv(t)){var a,u;return{type:b.deferred,deferredData:t,statusCode:(a=t.init)==null?void 0:a.status,headers:((u=t.init)==null?void 0:u.headers)&&new Headers(t.init.headers)}}if(pc(t)){var s,d;return{type:b.data,data:t.data,statusCode:(s=t.init)==null?void 0:s.status,headers:(d=t.init)!=null&&d.headers?new Headers(t.init.headers):void 0}}return{type:b.data,data:t}}function Rv(e,t,n,r,l,i){let o=e.headers.get("Location");if(K(o,"Redirects returned/thrown from loaders/actions must have a Location header"),!_u.test(o)){let a=r.slice(0,r.findIndex(u=>u.route.id===n)+1);o=Ra(new URL(t.url),a,l,!0,o,i),e.headers.set("Location",o)}return e}function ac(e,t,n,r){let l=["about:","blob:","chrome:","chrome-untrusted:","content:","data:","devtools:","file:","filesystem:","javascript:"];if(_u.test(e)){let i=e,o=i.startsWith("//")?new URL(t.protocol+i):new URL(i);if(l.includes(o.protocol))throw new Error("Invalid redirect location");let a=It(o.pathname,n)!=null;if(o.origin===t.origin&&a)return o.pathname+o.search+o.hash}try{let i=r.createURL(e);if(l.includes(i.protocol))throw new Error("Invalid redirect location")}catch{}return e}function $n(e,t,n,r){let l=e.createURL(Hd(t)).toString(),i={signal:n};if(r&&ht(r.formMethod)){let{formMethod:o,formEncType:a}=r;i.method=o.toUpperCase(),a==="application/json"?(i.headers=new Headers({"Content-Type":a}),i.body=JSON.stringify(r.json)):a==="text/plain"?i.body=r.text:a==="application/x-www-form-urlencoded"&&r.formData?i.body=Na(r.formData):i.body=r.formData}return new Request(l,i)}function Na(e){let t=new URLSearchParams;for(let[n,r]of e.entries())t.append(n,typeof r=="string"?r:r.name);return t}function uc(e){let t=new FormData;for(let[n,r]of e.entries())t.append(n,r);return t}function Lv(e,t,n,r,l){let i={},o=null,a,u=!1,s={},d=n&&Je(n[1])?n[1].error:void 0;return e.forEach(f=>{if(!(f.route.id in t))return;let p=f.route.id,w=t[p];if(K(!Pn(w),"Cannot handle redirect results in processLoaderData"),Je(w)){let x=w.error;d!==void 0&&(x=d,d=void 0),o=o||{};{let E=En(e,p);o[E.route.id]==null&&(o[E.route.id]=x)}i[p]=void 0,u||(u=!0,a=ml(w.error)?w.error.status:500),w.headers&&(s[p]=w.headers)}else bt(w)?(r.set(p,w.deferredData),i[p]=w.deferredData.data,w.statusCode!=null&&w.statusCode!==200&&!u&&(a=w.statusCode),w.headers&&(s[p]=w.headers)):(i[p]=w.data,w.statusCode&&w.statusCode!==200&&!u&&(a=w.statusCode),w.headers&&(s[p]=w.headers))}),d!==void 0&&n&&(o={[n[0]]:d},i[n[0]]=void 0),{loaderData:i,errors:o,statusCode:a||200,loaderHeaders:s}}function sc(e,t,n,r,l,i,o){let{loaderData:a,errors:u}=Lv(t,n,r,o);return l.forEach(s=>{let{key:d,match:f,controller:p}=s,w=i[d];if(K(w,"Did not find corresponding fetcher result"),!(p&&p.signal.aborted))if(Je(w)){let x=En(e.matches,f==null?void 0:f.route.id);u&&u[x.route.id]||(u=ue({},u,{[x.route.id]:w.error})),e.fetchers.delete(d)}else if(Pn(w))K(!1,"Unhandled fetcher revalidation redirect");else if(bt(w))K(!1,"Unhandled fetcher deferred data");else{let x=Kt(w.data);e.fetchers.set(d,x)}}),{loaderData:a,errors:u}}function cc(e,t,n,r){let l=ue({},t);for(let i of n){let o=i.route.id;if(t.hasOwnProperty(o)?t[o]!==void 0&&(l[o]=t[o]):e[o]!==void 0&&i.route.loader&&(l[o]=e[o]),r&&r.hasOwnProperty(o))break}return l}function fc(e){return e?Je(e[1])?{actionData:{}}:{actionData:{[e[0]]:e[1].data}}:{}}function En(e,t){return(t?e.slice(0,e.findIndex(r=>r.route.id===t)+1):[...e]).reverse().find(r=>r.route.hasErrorBoundary===!0)||e[0]}function dc(e){let t=e.length===1?e[0]:e.find(n=>n.index||!n.path||n.path==="/")||{id:"__shim-error-route__"};return{matches:[{params:{},pathname:"",pathnameBase:"",route:t}],route:t}}function Ve(e,t){let{pathname:n,routeId:r,method:l,type:i,message:o}=t===void 0?{}:t,a="Unknown Server Error",u="Unknown @remix-run/router error";return e===400?(a="Bad Request",l&&n&&r?u="You made a "+l+' request to "'+n+'" but '+('did not provide a `loader` for route "'+r+'", ')+"so there is no way to handle the request.":i==="defer-action"?u="defer() is not supported in actions":i==="invalid-body"&&(u="Unable to encode submission body")):e===403?(a="Forbidden",u='Route "'+r+'" does not match URL "'+n+'"'):e===404?(a="Not Found",u='No route matches URL "'+n+'"'):e===405&&(a="Method Not Allowed",l&&n&&r?u="You made a "+l.toUpperCase()+' request to "'+n+'" but '+('did not provide an `action` for route "'+r+'", ')+"so there is no way to handle the request.":l&&(u='Invalid request method "'+l.toUpperCase()+'"')),new ji(e||500,a,new Error(u),!0)}function Kl(e){let t=Object.entries(e);for(let n=t.length-1;n>=0;n--){let[r,l]=t[n];if(Pn(l))return{key:r,result:l}}}function Hd(e){let t=typeof e=="string"?hn(e):e;return zn(ue({},t,{hash:""}))}function Nv(e,t){return e.pathname!==t.pathname||e.search!==t.search?!1:e.hash===""?t.hash!=="":e.hash===t.hash?!0:t.hash!==""}function Tv(e){return Vd(e.result)&&mv.has(e.result.status)}function bt(e){return e.type===b.deferred}function Je(e){return e.type===b.error}function Pn(e){return(e&&e.type)===b.redirect}function pc(e){return typeof e=="object"&&e!=null&&"type"in e&&"data"in e&&"init"in e&&e.type==="DataWithResponseInit"}function Dv(e){let t=e;return t&&typeof t=="object"&&typeof t.data=="object"&&typeof t.subscribe=="function"&&typeof t.cancel=="function"&&typeof t.resolveData=="function"}function Vd(e){return e!=null&&typeof e.status=="number"&&typeof e.statusText=="string"&&typeof e.headers=="object"&&typeof e.body<"u"}function Mv(e){return hv.has(e.toLowerCase())}function ht(e){return dv.has(e.toLowerCase())}async function jv(e,t,n,r,l){let i=Object.entries(t);for(let o=0;o(p==null?void 0:p.route.id)===a);if(!s)continue;let d=r.find(p=>p.route.id===s.route.id),f=d!=null&&!Ad(d,s)&&(l&&l[s.route.id])!==void 0;bt(u)&&f&&await Ru(u,n,!1).then(p=>{p&&(t[a]=p)})}}async function zv(e,t,n){for(let r=0;r(s==null?void 0:s.route.id)===i)&&bt(a)&&(K(o,"Expected an AbortController for revalidating fetcher deferred result"),await Ru(a,o.signal,!0).then(s=>{s&&(t[l]=s)}))}}async function Ru(e,t,n){if(n===void 0&&(n=!1),!await e.deferredData.resolveData(t)){if(n)try{return{type:b.data,data:e.deferredData.unwrappedData}}catch(l){return{type:b.error,error:l}}return{type:b.data,data:e.deferredData.data}}}function Lu(e){return new URLSearchParams(e).getAll("index").some(t=>t==="")}function Br(e,t){let n=typeof t=="string"?hn(t).search:t.search;if(e[e.length-1].route.index&&Lu(n||""))return e[e.length-1];let r=Fd(e);return r[r.length-1]}function hc(e){let{formMethod:t,formAction:n,formEncType:r,text:l,formData:i,json:o}=e;if(!(!t||!n||!r)){if(l!=null)return{formMethod:t,formAction:n,formEncType:r,formData:void 0,json:void 0,text:l};if(i!=null)return{formMethod:t,formAction:n,formEncType:r,formData:i,json:void 0,text:void 0};if(o!==void 0)return{formMethod:t,formAction:n,formEncType:r,formData:void 0,json:o,text:void 0}}}function jo(e,t){return t?{state:"loading",location:e,formMethod:t.formMethod,formAction:t.formAction,formEncType:t.formEncType,formData:t.formData,json:t.json,text:t.text}:{state:"loading",location:e,formMethod:void 0,formAction:void 0,formEncType:void 0,formData:void 0,json:void 0,text:void 0}}function Ov(e,t){return{state:"submitting",location:e,formMethod:t.formMethod,formAction:t.formAction,formEncType:t.formEncType,formData:t.formData,json:t.json,text:t.text}}function zr(e,t){return e?{state:"loading",formMethod:e.formMethod,formAction:e.formAction,formEncType:e.formEncType,formData:e.formData,json:e.json,text:e.text,data:t}:{state:"loading",formMethod:void 0,formAction:void 0,formEncType:void 0,formData:void 0,json:void 0,text:void 0,data:t}}function Fv(e,t){return{state:"submitting",formMethod:e.formMethod,formAction:e.formAction,formEncType:e.formEncType,formData:e.formData,json:e.json,text:e.text,data:t?t.data:void 0}}function Kt(e){return{state:"idle",formMethod:void 0,formAction:void 0,formEncType:void 0,formData:void 0,json:void 0,text:void 0,data:e}}function Iv(e,t){try{let n=e.sessionStorage.getItem(Ud);if(n){let r=JSON.parse(n);for(let[l,i]of Object.entries(r||{}))i&&Array.isArray(i)&&t.set(l,new Set(i||[]))}}catch{}}function Uv(e,t){if(t.size>0){let n={};for(let[r,l]of t)n[r]=[...l];try{e.sessionStorage.setItem(Ud,JSON.stringify(n))}catch(r){jn(!1,"Failed to save applied view transitions in sessionStorage ("+r+").")}}}/** - * React Router v6.30.3 - * - * Copyright (c) Remix Software Inc. - * - * This source code is licensed under the MIT license found in the - * LICENSE.md file in the root directory of this source tree. - * - * @license MIT - */function zi(){return zi=Object.assign?Object.assign.bind():function(e){for(var t=1;t{a.current=!0}),_.useCallback(function(s,d){if(d===void 0&&(d={}),!a.current)return;if(typeof s=="number"){r.go(s);return}let f=qi(s,JSON.parse(o),i,d.relative==="path");e==null&&t!=="/"&&(f.pathname=f.pathname==="/"?t:Mt([t,f.pathname])),(d.replace?r.replace:r.push)(f,d.state,d)},[t,r,o,i,e])}const Hv=_.createContext(null);function Vv(e){let t=_.useContext(Bt).outlet;return t&&_.createElement(Hv.Provider,{value:e},t)}function bi(e,t){let{relative:n}=t===void 0?{}:t,{future:r}=_.useContext(At),{matches:l}=_.useContext(Bt),{pathname:i}=Sr(),o=JSON.stringify(Zi(l,r.v7_relativeSplatPath));return _.useMemo(()=>qi(e,JSON.parse(o),i,n==="path"),[e,o,i,n])}function $v(e,t,n,r){wr()||K(!1);let{navigator:l}=_.useContext(At),{matches:i}=_.useContext(Bt),o=i[i.length-1],a=o?o.params:{};o&&o.pathname;let u=o?o.pathnameBase:"/";o&&o.route;let s=Sr(),d;d=s;let f=d.pathname||"/",p=f;if(u!=="/"){let E=u.replace(/^\//,"").split("/");p="/"+f.replace(/^\//,"").split("/").slice(E.length).join("/")}let w=Sn(e,{pathname:p});return Xv(w&&w.map(E=>Object.assign({},E,{params:Object.assign({},a,E.params),pathname:Mt([u,l.encodeLocation?l.encodeLocation(E.pathname).pathname:E.pathname]),pathnameBase:E.pathnameBase==="/"?u:Mt([u,l.encodeLocation?l.encodeLocation(E.pathnameBase).pathname:E.pathnameBase])})),i,n,r)}function Wv(){let e=qv(),t=ml(e)?e.status+" "+e.statusText:e instanceof Error?e.message:JSON.stringify(e),n=e instanceof Error?e.stack:null,l={padding:"0.5rem",backgroundColor:"rgba(200,200,200, 0.5)"};return _.createElement(_.Fragment,null,_.createElement("h2",null,"Unexpected Application Error!"),_.createElement("h3",{style:{fontStyle:"italic"}},t),n?_.createElement("pre",{style:l},n):null,null)}const Qv=_.createElement(Wv,null);class Kv extends _.Component{constructor(t){super(t),this.state={location:t.location,revalidation:t.revalidation,error:t.error}}static getDerivedStateFromError(t){return{error:t}}static getDerivedStateFromProps(t,n){return n.location!==t.location||n.revalidation!=="idle"&&t.revalidation==="idle"?{error:t.error,location:t.location,revalidation:t.revalidation}:{error:t.error!==void 0?t.error:n.error,location:n.location,revalidation:t.revalidation||n.revalidation}}componentDidCatch(t,n){console.error("React Router caught the following error during render",t,n)}render(){return this.state.error!==void 0?_.createElement(Bt.Provider,{value:this.props.routeContext},_.createElement($d.Provider,{value:this.state.error,children:this.props.component})):this.props.children}}function Yv(e){let{routeContext:t,match:n,children:r}=e,l=_.useContext(El);return l&&l.static&&l.staticContext&&(n.route.errorElement||n.route.ErrorBoundary)&&(l.staticContext._deepestRenderedBoundaryId=n.route.id),_.createElement(Bt.Provider,{value:t},r)}function Xv(e,t,n,r){var l;if(t===void 0&&(t=[]),n===void 0&&(n=null),r===void 0&&(r=null),e==null){var i;if(!n)return null;if(n.errors)e=n.matches;else if((i=r)!=null&&i.v7_partialHydration&&t.length===0&&!n.initialized&&n.matches.length>0)e=n.matches;else return null}let o=e,a=(l=n)==null?void 0:l.errors;if(a!=null){let d=o.findIndex(f=>f.route.id&&(a==null?void 0:a[f.route.id])!==void 0);d>=0||K(!1),o=o.slice(0,Math.min(o.length,d+1))}let u=!1,s=-1;if(n&&r&&r.v7_partialHydration)for(let d=0;d=0?o=o.slice(0,s+1):o=[o[0]];break}}}return o.reduceRight((d,f,p)=>{let w,x=!1,E=null,D=null;n&&(w=a&&f.route.id?a[f.route.id]:void 0,E=f.route.errorElement||Qv,u&&(s<0&&p===0?(ey("route-fallback"),x=!0,D=null):s===p&&(x=!0,D=f.route.hydrateFallbackElement||null)));let m=t.concat(o.slice(0,p+1)),c=()=>{let v;return w?v=E:x?v=D:f.route.Component?v=_.createElement(f.route.Component,null):f.route.element?v=f.route.element:v=d,_.createElement(Yv,{match:f,routeContext:{outlet:d,matches:m,isDataRoute:n!=null},children:v})};return n&&(f.route.ErrorBoundary||f.route.errorElement||p===0)?_.createElement(Kv,{location:n.location,revalidation:n.revalidation,component:E,error:w,children:c(),routeContext:{outlet:null,matches:m,isDataRoute:!0}}):c()},null)}var Kd=function(e){return e.UseBlocker="useBlocker",e.UseRevalidator="useRevalidator",e.UseNavigateStable="useNavigate",e}(Kd||{}),Yd=function(e){return e.UseBlocker="useBlocker",e.UseLoaderData="useLoaderData",e.UseActionData="useActionData",e.UseRouteError="useRouteError",e.UseNavigation="useNavigation",e.UseRouteLoaderData="useRouteLoaderData",e.UseMatches="useMatches",e.UseRevalidator="useRevalidator",e.UseNavigateStable="useNavigate",e.UseRouteId="useRouteId",e}(Yd||{});function Gv(e){let t=_.useContext(El);return t||K(!1),t}function Jv(e){let t=_.useContext(Nu);return t||K(!1),t}function Zv(e){let t=_.useContext(Bt);return t||K(!1),t}function Xd(e){let t=Zv(),n=t.matches[t.matches.length-1];return n.route.id||K(!1),n.route.id}function qv(){var e;let t=_.useContext($d),n=Jv(Yd.UseRouteError),r=Xd();return t!==void 0?t:(e=n.errors)==null?void 0:e[r]}function bv(){let{router:e}=Gv(Kd.UseNavigateStable),t=Xd(),n=_.useRef(!1);return Wd(()=>{n.current=!0}),_.useCallback(function(l,i){i===void 0&&(i={}),n.current&&(typeof l=="number"?e.navigate(l):e.navigate(l,zi({fromRouteId:t},i)))},[e,t])}const mc={};function ey(e,t,n){mc[e]||(mc[e]=!0)}function ty(e,t){e==null||e.v7_startTransition,(e==null?void 0:e.v7_relativeSplatPath)===void 0&&(!t||t.v7_relativeSplatPath),t&&(t.v7_fetcherPersist,t.v7_normalizeFormMethod,t.v7_partialHydration,t.v7_skipActionErrorRevalidation)}function ny(e){let{to:t,replace:n,state:r,relative:l}=e;wr()||K(!1);let{future:i,static:o}=_.useContext(At),{matches:a}=_.useContext(Bt),{pathname:u}=Sr(),s=Qd(),d=qi(t,Zi(a,i.v7_relativeSplatPath),u,l==="path"),f=JSON.stringify(d);return _.useEffect(()=>s(JSON.parse(f),{replace:n,state:r,relative:l}),[s,f,l,n,r]),null}function ry(e){return Vv(e.context)}function ly(e){let{basename:t="/",children:n=null,location:r,navigationType:l=ge.Pop,navigator:i,static:o=!1,future:a}=e;wr()&&K(!1);let u=t.replace(/^\/*/,"/"),s=_.useMemo(()=>({basename:u,navigator:i,static:o,future:zi({v7_relativeSplatPath:!1},a)}),[u,a,i,o]);typeof r=="string"&&(r=hn(r));let{pathname:d="/",search:f="",hash:p="",state:w=null,key:x="default"}=r,E=_.useMemo(()=>{let D=It(d,u);return D==null?null:{location:{pathname:D,search:f,hash:p,state:w,key:x},navigationType:l}},[u,d,f,p,w,x,l]);return E==null?null:_.createElement(At.Provider,{value:s},_.createElement(Tu.Provider,{children:n,value:E}))}new Promise(()=>{});function iy(e){let t={hasErrorBoundary:e.ErrorBoundary!=null||e.errorElement!=null};return e.Component&&Object.assign(t,{element:_.createElement(e.Component),Component:void 0}),e.HydrateFallback&&Object.assign(t,{hydrateFallbackElement:_.createElement(e.HydrateFallback),HydrateFallback:void 0}),e.ErrorBoundary&&Object.assign(t,{errorElement:_.createElement(e.ErrorBoundary),ErrorBoundary:void 0}),t}/** - * React Router DOM v6.30.3 - * - * Copyright (c) Remix Software Inc. - * - * This source code is licensed under the MIT license found in the - * LICENSE.md file in the root directory of this source tree. - * - * @license MIT - */function mr(){return mr=Object.assign?Object.assign.bind():function(e){for(var t=1;t=0)&&(n[l]=e[l]);return n}function oy(e){return!!(e.metaKey||e.altKey||e.ctrlKey||e.shiftKey)}function ay(e,t){return e.button===0&&(!t||t==="_self")&&!oy(e)}const uy=["onClick","relative","reloadDocument","replace","state","target","to","preventScrollReset","viewTransition"],sy=["aria-current","caseSensitive","className","end","style","to","viewTransition","children"],cy="6";try{window.__reactRouterVersion=cy}catch{}function fy(e,t){return wv({basename:void 0,future:mr({},void 0,{v7_prependBasename:!0}),history:Vm({window:void 0}),hydrationData:dy(),routes:e,mapRouteProperties:iy,dataStrategy:void 0,patchRoutesOnNavigation:void 0,window:void 0}).initialize()}function dy(){var e;let t=(e=window)==null?void 0:e.__staticRouterHydrationData;return t&&t.errors&&(t=mr({},t,{errors:py(t.errors)})),t}function py(e){if(!e)return null;let t=Object.entries(e),n={};for(let[r,l]of t)if(l&&l.__type==="RouteErrorResponse")n[r]=new ji(l.status,l.statusText,l.data,l.internal===!0);else if(l&&l.__type==="Error"){if(l.__subType){let i=window[l.__subType];if(typeof i=="function")try{let o=new i(l.message);o.stack="",n[r]=o}catch{}}if(n[r]==null){let i=new Error(l.message);i.stack="",n[r]=i}}else n[r]=l;return n}const Jd=_.createContext({isTransitioning:!1}),hy=_.createContext(new Map),my="startTransition",vc=Np[my],vy="flushSync",yc=Hm[vy];function yy(e){vc?vc(e):e()}function Or(e){yc?yc(e):e()}class gy{constructor(){this.status="pending",this.promise=new Promise((t,n)=>{this.resolve=r=>{this.status==="pending"&&(this.status="resolved",t(r))},this.reject=r=>{this.status==="pending"&&(this.status="rejected",n(r))}})}}function wy(e){let{fallbackElement:t,router:n,future:r}=e,[l,i]=_.useState(n.state),[o,a]=_.useState(),[u,s]=_.useState({isTransitioning:!1}),[d,f]=_.useState(),[p,w]=_.useState(),[x,E]=_.useState(),D=_.useRef(new Map),{v7_startTransition:m}=r||{},c=_.useCallback(C=>{m?yy(C):C()},[m]),v=_.useCallback((C,H)=>{let{deletedFetchers:M,flushSync:q,viewTransitionOpts:re}=H;C.fetchers.forEach((_e,st)=>{_e.data!==void 0&&D.current.set(st,_e.data)}),M.forEach(_e=>D.current.delete(_e));let Se=n.window==null||n.window.document==null||typeof n.window.document.startViewTransition!="function";if(!re||Se){q?Or(()=>i(C)):c(()=>i(C));return}if(q){Or(()=>{p&&(d&&d.resolve(),p.skipTransition()),s({isTransitioning:!0,flushSync:!0,currentLocation:re.currentLocation,nextLocation:re.nextLocation})});let _e=n.window.document.startViewTransition(()=>{Or(()=>i(C))});_e.finished.finally(()=>{Or(()=>{f(void 0),w(void 0),a(void 0),s({isTransitioning:!1})})}),Or(()=>w(_e));return}p?(d&&d.resolve(),p.skipTransition(),E({state:C,currentLocation:re.currentLocation,nextLocation:re.nextLocation})):(a(C),s({isTransitioning:!0,flushSync:!1,currentLocation:re.currentLocation,nextLocation:re.nextLocation}))},[n.window,p,d,D,c]);_.useLayoutEffect(()=>n.subscribe(v),[n,v]),_.useEffect(()=>{u.isTransitioning&&!u.flushSync&&f(new gy)},[u]),_.useEffect(()=>{if(d&&o&&n.window){let C=o,H=d.promise,M=n.window.document.startViewTransition(async()=>{c(()=>i(C)),await H});M.finished.finally(()=>{f(void 0),w(void 0),a(void 0),s({isTransitioning:!1})}),w(M)}},[c,o,d,n.window]),_.useEffect(()=>{d&&o&&l.location.key===o.location.key&&d.resolve()},[d,p,l.location,o]),_.useEffect(()=>{!u.isTransitioning&&x&&(a(x.state),s({isTransitioning:!0,flushSync:!1,currentLocation:x.currentLocation,nextLocation:x.nextLocation}),E(void 0))},[u.isTransitioning,x]),_.useEffect(()=>{},[]);let k=_.useMemo(()=>({createHref:n.createHref,encodeLocation:n.encodeLocation,go:C=>n.navigate(C),push:(C,H,M)=>n.navigate(C,{state:H,preventScrollReset:M==null?void 0:M.preventScrollReset}),replace:(C,H,M)=>n.navigate(C,{replace:!0,state:H,preventScrollReset:M==null?void 0:M.preventScrollReset})}),[n]),L=n.basename||"/",z=_.useMemo(()=>({router:n,navigator:k,static:!1,basename:L}),[n,k,L]),y=_.useMemo(()=>({v7_relativeSplatPath:n.future.v7_relativeSplatPath}),[n.future.v7_relativeSplatPath]);return _.useEffect(()=>ty(r,n.future),[r,n.future]),_.createElement(_.Fragment,null,_.createElement(El.Provider,{value:z},_.createElement(Nu.Provider,{value:l},_.createElement(hy.Provider,{value:D.current},_.createElement(Jd.Provider,{value:u},_.createElement(ly,{basename:L,location:l.location,navigationType:l.historyAction,navigator:k,future:y},l.initialized||n.future.v7_partialHydration?_.createElement(Sy,{routes:n.routes,future:n.future,state:l}):t))))),null)}const Sy=_.memo(Ey);function Ey(e){let{routes:t,future:n,state:r}=e;return $v(t,void 0,r,n)}const xy=typeof window<"u"&&typeof window.document<"u"&&typeof window.document.createElement<"u",ky=/^(?:[a-z][a-z0-9+.-]*:|\/\/)/i,Cy=_.forwardRef(function(t,n){let{onClick:r,relative:l,reloadDocument:i,replace:o,state:a,target:u,to:s,preventScrollReset:d,viewTransition:f}=t,p=Gd(t,uy),{basename:w}=_.useContext(At),x,E=!1;if(typeof s=="string"&&ky.test(s)&&(x=s,xy))try{let v=new URL(window.location.href),k=s.startsWith("//")?new URL(v.protocol+s):new URL(s),L=It(k.pathname,w);k.origin===v.origin&&L!=null?s=L+k.search+k.hash:E=!0}catch{}let D=Av(s,{relative:l}),m=Ry(s,{replace:o,state:a,target:u,preventScrollReset:d,relative:l,viewTransition:f});function c(v){r&&r(v),v.defaultPrevented||m(v)}return _.createElement("a",mr({},p,{href:x||D,onClick:E||i?r:c,ref:n,target:u}))}),Py=_.forwardRef(function(t,n){let{"aria-current":r="page",caseSensitive:l=!1,className:i="",end:o=!1,style:a,to:u,viewTransition:s,children:d}=t,f=Gd(t,sy),p=bi(u,{relative:f.relative}),w=Sr(),x=_.useContext(Nu),{navigator:E,basename:D}=_.useContext(At),m=x!=null&&Ly(p)&&s===!0,c=E.encodeLocation?E.encodeLocation(p).pathname:p.pathname,v=w.pathname,k=x&&x.navigation&&x.navigation.location?x.navigation.location.pathname:null;l||(v=v.toLowerCase(),k=k?k.toLowerCase():null,c=c.toLowerCase()),k&&D&&(k=It(k,D)||k);const L=c!=="/"&&c.endsWith("/")?c.length-1:c.length;let z=v===c||!o&&v.startsWith(c)&&v.charAt(L)==="/",y=k!=null&&(k===c||!o&&k.startsWith(c)&&k.charAt(c.length)==="/"),C={isActive:z,isPending:y,isTransitioning:m},H=z?r:void 0,M;typeof i=="function"?M=i(C):M=[i,z?"active":null,y?"pending":null,m?"transitioning":null].filter(Boolean).join(" ");let q=typeof a=="function"?a(C):a;return _.createElement(Cy,mr({},f,{"aria-current":H,className:M,ref:n,style:q,to:u,viewTransition:s}),typeof d=="function"?d(C):d)});var Ta;(function(e){e.UseScrollRestoration="useScrollRestoration",e.UseSubmit="useSubmit",e.UseSubmitFetcher="useSubmitFetcher",e.UseFetcher="useFetcher",e.useViewTransitionState="useViewTransitionState"})(Ta||(Ta={}));var gc;(function(e){e.UseFetcher="useFetcher",e.UseFetchers="useFetchers",e.UseScrollRestoration="useScrollRestoration"})(gc||(gc={}));function _y(e){let t=_.useContext(El);return t||K(!1),t}function Ry(e,t){let{target:n,replace:r,state:l,preventScrollReset:i,relative:o,viewTransition:a}=t===void 0?{}:t,u=Qd(),s=Sr(),d=bi(e,{relative:o});return _.useCallback(f=>{if(ay(f,n)){f.preventDefault();let p=r!==void 0?r:zn(s)===zn(d);u(e,{replace:p,state:l,preventScrollReset:i,relative:o,viewTransition:a})}},[s,u,d,r,l,n,e,i,o,a])}function Ly(e,t){t===void 0&&(t={});let n=_.useContext(Jd);n==null&&K(!1);let{basename:r}=_y(Ta.useViewTransitionState),l=bi(e,{relative:t.relative});if(!n.isTransitioning)return!1;let i=It(n.currentLocation.pathname,r)||n.currentLocation.pathname,o=It(n.nextLocation.pathname,r)||n.nextLocation.pathname;return Mi(l.pathname,o)!=null||Mi(l.pathname,i)!=null}function In(e){return T.jsxs("section",{className:"page",children:[T.jsx("h1",{children:e.title}),T.jsx("p",{children:e.subtitle}),e.children]})}function Yl(e){return T.jsxs("div",{className:"card",children:[T.jsx("h3",{children:e.title}),e.children]})}async function Jr(e){var l;const t=window.localStorage.getItem("osham-admin-secret")||"",n=await fetch(e,{headers:t?{"x-osham-admin-secret":t}:{}}),r=await n.json();if(!n.ok||r.ok===!1)throw new Error(((l=r==null?void 0:r.error)==null?void 0:l.message)||`Request failed: ${n.status}`);return r.data}async function Ny(e,t){var i;const n=window.localStorage.getItem("osham-admin-secret")||"",r=await fetch(e,{method:"POST",headers:{"content-type":"application/json",...n?{"x-osham-admin-secret":n}:{}},body:JSON.stringify(t)}),l=await r.json();if(!r.ok||l.ok===!1)throw new Error(((i=l==null?void 0:l.error)==null?void 0:i.message)||`Request failed: ${r.status}`);return l.data}function Ty(){var u;const[e,t]=Ce.useState(null),[n,r]=Ce.useState(null),[l,i]=Ce.useState(null),[o,a]=Ce.useState(null);return Ce.useEffect(()=>{Promise.all([Jr("/__osham/admin/health"),Jr("/__osham/admin/metrics/summary"),Jr("/__osham/admin/startup-summary")]).then(([s,d,f])=>{t(s),r(d),i(f),a(null)}).catch(s=>a(s.message))},[]),T.jsxs(In,{title:"Dashboard",subtitle:"High-level operational overview for Osham.",children:[o?T.jsx("div",{className:"code-block",children:o}):null,T.jsxs("div",{className:"card-grid",children:[T.jsxs(Yl,{title:"Service Health",children:[T.jsxs("p",{children:["Status: ",(e==null?void 0:e.status)||"loading..."]}),T.jsxs("p",{children:["Cache: ",(e==null?void 0:e.cache.backend)||"-"]})]}),T.jsxs(Yl,{title:"Revision",children:[T.jsxs("p",{children:["Revision: ",(e==null?void 0:e.config.revision)||"loading..."]}),T.jsxs("p",{children:["Applied: ",(e==null?void 0:e.config.lastAppliedAt)||"-"]})]}),T.jsxs(Yl,{title:"Metrics Summary",children:[T.jsxs("p",{children:["Requests: ",(n==null?void 0:n.requests)??"-"]}),T.jsxs("p",{children:["Hit ratio: ",n?`${(n.hitRatio*100).toFixed(1)}%`:"-"]})]}),T.jsxs(Yl,{title:"Namespaces",children:[T.jsxs("p",{children:["Count: ",(l==null?void 0:l.namespaceCount)??"-"]}),T.jsxs("p",{children:["Metrics enabled: ",String(((u=l==null?void 0:l.features)==null?void 0:u.metrics)??!1)]})]})]})]})}function Dy(){return T.jsx(In,{title:"Config",subtitle:"Landing page for global settings and namespace editors.",children:T.jsxs("div",{className:"code-block",children:[T.jsx("strong",{children:"Planned next:"}),T.jsxs("ul",{children:[T.jsxs("li",{children:["load current config from ",T.jsx("code",{children:"/__osham/admin/config"})]}),T.jsx("li",{children:"draft editing for global config + namespaces"}),T.jsx("li",{children:"validate, save, and reload actions"})]})]})})}function My(){return T.jsx(In,{title:"Metrics",subtitle:"Namespace and aggregate cache metrics.",children:T.jsxs("div",{className:"code-block",children:["Hook into ",T.jsx("code",{children:"/__osham/admin/metrics/summary"})," and ",T.jsx("code",{children:"/__osham/admin/metrics/namespaces"}),"."]})})}function jy(){const[e,t]=Ce.useState(null),[n,r]=Ce.useState(null);return Ce.useEffect(()=>{Jr("/__osham/admin/health").then(t).catch(l=>r(l.message))},[]),T.jsxs(In,{title:"Health",subtitle:"Operational health and startup visibility.",children:[n?T.jsx("div",{className:"code-block",children:n}):null,T.jsx("div",{className:"code-block",children:T.jsx("pre",{children:JSON.stringify(e,null,2)})})]})}function zy(){const[e,t]=Ce.useState("**"),[n,r]=Ce.useState(!0),[l,i]=Ce.useState(null),[o,a]=Ce.useState(null);async function u(){try{const s=await Ny("/__osham/admin/purge",{pattern:e,dryRun:n});i(s),a(null)}catch(s){a(s instanceof Error?s.message:"Request failed")}}return T.jsxs(In,{title:"Purge",subtitle:"Safe cache invalidation tools.",children:[T.jsxs("div",{className:"toolbar",children:[T.jsx("input",{className:"input",value:e,onChange:s=>t(s.target.value)}),T.jsxs("label",{children:[T.jsx("input",{type:"checkbox",checked:n,onChange:s=>r(s.target.checked)})," dry run"]}),T.jsx("button",{className:"button",onClick:u,children:"Run Purge"})]}),o?T.jsx("div",{className:"code-block",children:o}):null,l?T.jsx("pre",{className:"code-block",children:JSON.stringify(l,null,2)}):null]})}function Oy(){const[e,t]=Ce.useState([]),[n,r]=Ce.useState(null);return Ce.useEffect(()=>{Jr("/__osham/admin/audit").then(t).catch(l=>r(l.message))},[]),T.jsxs(In,{title:"Audit",subtitle:"Recent admin actions.",children:[n?T.jsx("div",{className:"code-block",children:n}):null,T.jsxs("table",{className:"table",children:[T.jsx("thead",{children:T.jsxs("tr",{children:[T.jsx("th",{children:"Time"}),T.jsx("th",{children:"Action"}),T.jsx("th",{children:"Result"}),T.jsx("th",{children:"Actor"})]})}),T.jsx("tbody",{children:e.map((l,i)=>T.jsxs("tr",{children:[T.jsx("td",{children:l.time}),T.jsx("td",{children:l.action}),T.jsx("td",{children:l.result}),T.jsx("td",{children:l.actor})]},`${l.time}-${i}`))})]})]})}function Fy(){return T.jsx(In,{title:"Not Found",subtitle:"That route does not exist."})}const Iy=[["/admin/dashboard","Dashboard"],["/admin/config","Config"],["/admin/metrics","Metrics"],["/admin/health","Health"],["/admin/purge","Purge"],["/admin/audit","Audit"]];function Uy(){return T.jsxs("aside",{className:"sidebar",children:[T.jsx("div",{className:"brand",children:"Osham Admin"}),T.jsx("nav",{className:"nav-list",children:Iy.map(([e,t])=>T.jsx(Py,{to:e,className:({isActive:n})=>`nav-link${n?" active":""}`,children:t},e))})]})}function Ay(){const[e,t]=Ce.useState(()=>window.localStorage.getItem("osham-admin-secret")||"");function n(){window.localStorage.setItem("osham-admin-secret",e)}return T.jsxs("div",{className:"toolbar",children:[T.jsx("input",{className:"input",type:"password",placeholder:"x-osham-admin-secret",value:e,onChange:r=>t(r.target.value)}),T.jsx("button",{className:"button",onClick:n,children:"Save Secret"})]})}function By(){return T.jsxs("div",{className:"app-shell",children:[T.jsx(Uy,{}),T.jsxs("main",{className:"content-shell",children:[T.jsx(Ay,{}),T.jsx(ry,{})]})]})}const Hy=fy([{path:"/",element:T.jsx(By,{}),children:[{index:!0,element:T.jsx(ny,{to:"/admin/dashboard",replace:!0})},{path:"/admin/dashboard",element:T.jsx(Ty,{})},{path:"/admin/config",element:T.jsx(Dy,{})},{path:"/admin/metrics",element:T.jsx(My,{})},{path:"/admin/health",element:T.jsx(jy,{})},{path:"/admin/purge",element:T.jsx(zy,{})},{path:"/admin/audit",element:T.jsx(Oy,{})},{path:"*",element:T.jsx(Fy,{})}]}]);zo.createRoot(document.getElementById("root")).render(T.jsx(Ce.StrictMode,{children:T.jsx(wy,{router:Hy})})); diff --git a/admin-ui/dist/index.html b/admin-ui/dist/index.html deleted file mode 100644 index 0890f7b..0000000 --- a/admin-ui/dist/index.html +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - Osham Admin - - - - -
- - diff --git a/admin-ui/package.json b/admin-ui/package.json index cd6f636..ef81892 100644 --- a/admin-ui/package.json +++ b/admin-ui/package.json @@ -5,7 +5,8 @@ "type": "module", "scripts": { "dev": "vite", - "build": "tsc -b && vite build", + "clean": "find src -type f \\( -name '*.js' -o -name '*.d.ts' \\) -delete && rm -f vite.config.js vite.config.d.ts tsconfig.tsbuildinfo", + "build": "npm run clean && tsc -b && vite build", "preview": "vite preview" }, "dependencies": { diff --git a/admin-ui/src/api.ts b/admin-ui/src/api.ts index baec924..032eb7c 100644 --- a/admin-ui/src/api.ts +++ b/admin-ui/src/api.ts @@ -1,9 +1,4 @@ -export async function apiGet(path: string): Promise { - const secret = window.localStorage.getItem('osham-admin-secret') || ''; - const res = await fetch(path, { - headers: secret ? { 'x-osham-admin-secret': secret } : {}, - }); - +async function readJsonResponse(res: Response): Promise { const body = await res.json(); if (!res.ok || body.ok === false) { throw new Error(body?.error?.message || `Request failed: ${res.status}`); @@ -11,20 +6,38 @@ export async function apiGet(path: string): Promise { return body.data as T; } -export async function apiPost(path: string, payload: unknown): Promise { +function getAdminHeaders(contentType?: string): HeadersInit { const secret = window.localStorage.getItem('osham-admin-secret') || ''; + return { + ...(contentType ? { 'content-type': contentType } : {}), + ...(secret ? { 'x-osham-admin-secret': secret } : {}), + }; +} + +export async function apiGet(path: string): Promise { + const res = await fetch(path, { + headers: getAdminHeaders(), + }); + + return readJsonResponse(res); +} + +export async function apiPost(path: string, payload: unknown): Promise { const res = await fetch(path, { method: 'POST', - headers: { - 'content-type': 'application/json', - ...(secret ? { 'x-osham-admin-secret': secret } : {}), - }, + headers: getAdminHeaders('application/json'), body: JSON.stringify(payload), }); - const body = await res.json(); - if (!res.ok || body.ok === false) { - throw new Error(body?.error?.message || `Request failed: ${res.status}`); - } - return body.data as T; + return readJsonResponse(res); +} + +export async function apiPut(path: string, payload: unknown): Promise { + const res = await fetch(path, { + method: 'PUT', + headers: getAdminHeaders('application/json'), + body: JSON.stringify(payload), + }); + + return readJsonResponse(res); } diff --git a/admin-ui/src/screens/ConfigPage.tsx b/admin-ui/src/screens/ConfigPage.tsx index 28986bc..8289749 100644 --- a/admin-ui/src/screens/ConfigPage.tsx +++ b/admin-ui/src/screens/ConfigPage.tsx @@ -1,17 +1,495 @@ import React from 'react'; +import { apiGet, apiPost, apiPut } from '../api'; +import { AdminConfigView, CacheConfigView, NamespaceView, ValidationResult } from '../types'; import { Page } from '../ui/Page'; +import { Card } from '../ui/Card'; + +function toLines(value: string): string[] { + return value + .split('\n') + .map(line => line.trim()) + .filter(Boolean); +} + +function fromLines(value?: string[]): string { + return (value || []).join('\n'); +} + +type CacheSource = + | NamespaceView['cache'] + | { + expires?: string | number; + pool?: boolean; + query?: string[] | false; + headers?: string[] | false; + } + | false + | undefined; + +function toCacheView(cache: CacheSource): CacheConfigView { + if (!cache) { + return { enabled: false, expires: '', pool: false, query: [], headers: [] }; + } + return { + enabled: true, + expires: cache.expires ? String(cache.expires) : '', + pool: !!cache.pool, + query: Array.isArray(cache.query) ? cache.query : [], + headers: Array.isArray(cache.headers) ? cache.headers : [], + }; +} + +function normalizeConfig(data: AdminConfigView): AdminConfigView { + const namespaces = Object.fromEntries( + Object.entries(data.namespaces || {}).map(([name, ns]) => [ + name, + { + expose: ns.expose || '', + target: ns.target || '', + port: ns.port ? String(ns.port) : '', + timeout: ns.timeout ? String(ns.timeout) : '', + followRedirects: !!ns.followRedirects, + changeOrigin: !!ns.changeOrigin, + allow: ns.allow || [], + deny: ns.deny || [], + cache: toCacheView(ns.cache), + rules: (ns.rules || []).map(rule => ({ + pattern: rule.pattern, + cache: toCacheView(rule.cache), + })), + }, + ]), + ); + + return { + ...data, + namespaces, + }; +} + +function buildPayload(config: AdminConfigView) { + const namespaces = Object.fromEntries( + Object.entries(config.namespaces).map(([name, ns]) => { + const built: Record = { + expose: ns.expose, + target: ns.target, + }; + + if (ns.port) built.port = Number(ns.port); + if (ns.timeout) built.timeout = Number(ns.timeout); + if (ns.followRedirects) built.followRedirects = true; + if (ns.changeOrigin) built.changeOrigin = true; + if (ns.allow.length) built.allow = ns.allow; + if (ns.deny.length) built.deny = ns.deny; + + if (ns.cache.enabled) { + built.cache = { + ...(ns.cache.expires ? { expires: ns.cache.expires } : {}), + ...(ns.cache.pool ? { pool: true } : {}), + ...(ns.cache.query.length ? { query: ns.cache.query } : {}), + ...(ns.cache.headers.length ? { headers: ns.cache.headers } : {}), + }; + } else { + built.cache = false; + } + + if (ns.rules.length) { + built.rules = Object.fromEntries( + ns.rules.map(rule => [ + rule.pattern, + { + cache: rule.cache.enabled + ? { + ...(rule.cache.expires ? { expires: rule.cache.expires } : {}), + ...(rule.cache.pool ? { pool: true } : {}), + ...(rule.cache.query.length ? { query: rule.cache.query } : {}), + ...(rule.cache.headers.length ? { headers: rule.cache.headers } : {}), + } + : false, + }, + ]), + ); + } + + return [name, built]; + }), + ); + + return { + globalConfig: { + version: config.globalConfig.version, + xResponseTime: config.globalConfig.xResponseTime, + health: config.globalConfig.health, + purge: config.globalConfig.purge, + metrics: config.globalConfig.metrics, + changeOrigin: config.globalConfig.changeOrigin, + }, + namespaces, + }; +} export function ConfigPage() { + const [config, setConfig] = React.useState(null); + const [selectedNamespace, setSelectedNamespace] = React.useState(''); + const [allowText, setAllowText] = React.useState(''); + const [denyText, setDenyText] = React.useState(''); + const [rulesText, setRulesText] = React.useState('[]'); + const [validation, setValidation] = React.useState(null); + const [message, setMessage] = React.useState(null); + const [error, setError] = React.useState(null); + const [busy, setBusy] = React.useState(null); + + const loadConfig = React.useCallback(async () => { + try { + const data = normalizeConfig(await apiGet('/__osham/admin/config')); + const firstNamespace = Object.keys(data.namespaces)[0] || ''; + setConfig(data); + setSelectedNamespace(current => (current && data.namespaces[current] ? current : firstNamespace)); + setValidation(null); + setError(null); + } catch (err) { + setError(err instanceof Error ? err.message : 'Failed to load config'); + } + }, []); + + React.useEffect(() => { + loadConfig(); + }, [loadConfig]); + + const namespace = config && selectedNamespace ? config.namespaces[selectedNamespace] : null; + + React.useEffect(() => { + if (!namespace) return; + setAllowText(fromLines(namespace.allow)); + setDenyText(fromLines(namespace.deny)); + setRulesText(JSON.stringify(namespace.rules, null, 2)); + }, [namespace, selectedNamespace]); + + function patchConfig(updater: (current: AdminConfigView) => AdminConfigView) { + setConfig(current => (current ? updater(current) : current)); + } + + function patchNamespace(updater: (current: NamespaceView) => NamespaceView) { + if (!config || !selectedNamespace) return; + patchConfig(current => ({ + ...current, + namespaces: { + ...current.namespaces, + [selectedNamespace]: updater(current.namespaces[selectedNamespace]), + }, + })); + } + + function syncTextAreas() { + if (!namespace) return true; + try { + const parsedRules = JSON.parse(rulesText) as NamespaceView['rules']; + if (!Array.isArray(parsedRules)) { + throw new Error('Rules JSON must be an array'); + } + + patchNamespace(current => ({ + ...current, + allow: toLines(allowText), + deny: toLines(denyText), + rules: parsedRules.map(rule => ({ + pattern: rule.pattern, + cache: toCacheView(rule.cache), + })), + })); + return true; + } catch (err) { + setError(err instanceof Error ? err.message : 'Rules JSON is invalid'); + return false; + } + } + + async function runValidate() { + if (!config || !syncTextAreas()) return; + setBusy('validate'); + setMessage(null); + setError(null); + try { + const result = await apiPost('/__osham/admin/config/validate', { + config: buildPayload(config), + }); + setValidation(result); + setMessage(result.valid ? 'Validation passed.' : 'Validation failed. Review the issues below.'); + } catch (err) { + setError(err instanceof Error ? err.message : 'Validation failed'); + } finally { + setBusy(null); + } + } + + async function runSave() { + if (!config || !syncTextAreas()) return; + setBusy('save'); + setMessage(null); + setError(null); + try { + const data = await apiPut<{ saved: boolean; revision: string; warnings: { message: string }[] }>( + '/__osham/admin/config', + { + config: buildPayload(config), + expectedRevision: config.meta.revision, + }, + ); + setMessage(`Saved config revision ${data.revision}.`); + await loadConfig(); + if (data.warnings?.length) { + setValidation({ + valid: true, + errors: [], + warnings: data.warnings.map(warning => ({ + field: 'config', + message: warning.message, + severity: 'warning', + code: 'SAVE_WARNING', + })), + }); + } + } catch (err) { + setError(err instanceof Error ? err.message : 'Save failed'); + } finally { + setBusy(null); + } + } + + async function runReload() { + setBusy('reload'); + setMessage(null); + setError(null); + try { + const data = await apiPost<{ applied: boolean; revision: string; note?: string }>('/__osham/admin/config/reload', {}); + setMessage(data.note || `Reloaded admin state at revision ${data.revision}.`); + await loadConfig(); + } catch (err) { + setError(err instanceof Error ? err.message : 'Reload failed'); + } finally { + setBusy(null); + } + } + + function addNamespace() { + const name = window.prompt('Namespace name'); + if (!name || !config || config.namespaces[name]) return; + patchConfig(current => ({ + ...current, + namespaces: { + ...current.namespaces, + [name]: { + expose: '/api/*', + target: 'http://localhost:3000', + port: '', + timeout: '', + followRedirects: false, + changeOrigin: false, + allow: [], + deny: [], + cache: { enabled: true, expires: '', pool: false, query: [], headers: [] }, + rules: [], + }, + }, + })); + setSelectedNamespace(name); + } + return ( - -
- Planned next: -
    -
  • load current config from /__osham/admin/config
  • -
  • draft editing for global config + namespaces
  • -
  • validate, save, and reload actions
  • -
+ +
+ + + + +
+ + {message ?
{message}
: null} + {error ?
{error}
: null} + + {config ? ( + <> +
+ +

Revision: {config.meta.revision}

+

Source: {config.meta.source}

+

Last loaded: {config.meta.lastLoadedAt}

+

Last applied: {config.meta.lastAppliedAt || 'Not yet applied'}

+
+ +
+ + {[ + ['health', 'Health'], + ['metrics', 'Metrics'], + ['purge', 'Purge'], + ['xResponseTime', 'X-Response-Time'], + ['changeOrigin', 'Change Origin'], + ].map(([key, label]) => ( + + ))} +
+

Metrics path: {config.globalConfig.metricsPath || '/__osham/metrics'}

+

+ Secure mode: {String(config.globalConfig.secure?.enabled || false)} · SSL key:{' '} + {String(config.globalConfig.secure?.sslKeyConfigured || false)} · SSL cert:{' '} + {String(config.globalConfig.secure?.sslCertConfigured || false)} +

+
+
+ +
+ +
+ {Object.keys(config.namespaces).map(name => ( + + ))} +
+
+ + {namespace ? ( + +
+ + + + + + +
+ +
+ + + + +