diff --git a/.agents/skills/aspire-diagnostics/SKILL.md b/.agents/skills/aspire-diagnostics/SKILL.md index bc3682377d..79a17acf31 100644 --- a/.agents/skills/aspire-diagnostics/SKILL.md +++ b/.agents/skills/aspire-diagnostics/SKILL.md @@ -41,6 +41,6 @@ Report the exact action, resource names, trace IDs, repeated-call counts, durati ## Browser and environment boundaries -The AppHost enables `WithBrowserLogs()` for frontend resources. Use its dashboard for browser console/network evidence or screenshots when they explain a UI failure, and correlate browser activity with backend traces where available. +The AppHost enables `WithBrowserLogs()` for the Svelte `App` resource, which serves the application at `/`. Discover that resource's current endpoint. Use its dashboard for browser console/network evidence or screenshots when they explain a UI failure, and correlate browser activity with backend traces where available. Avoid restarting the whole AppHost as a diagnostic loop. For deployed or external environments, follow the user's explicit scope and the platform's diagnostics; do not assume local Aspire commands or endpoints apply. diff --git a/.agents/skills/dogfood/SKILL.md b/.agents/skills/dogfood/SKILL.md index 80299db073..05a92725f4 100644 --- a/.agents/skills/dogfood/SKILL.md +++ b/.agents/skills/dogfood/SKILL.md @@ -3,8 +3,7 @@ name: dogfood description: > Systematically dogfood Exceptionless locally. Use when asked to dogfood, QA, exploratory test, bug hunt, browser-test, or validate frontend/full-stack behavior in this repo. Start from the - AppHost/Aspire local URLs and choose the Svelte or legacy Angular app based on the changed - surface. External Exceptionless URLs require an explicit user-provided URL and confirmation. + AppHost/Aspire local URLs and use the Svelte application. External Exceptionless URLs require an explicit user-provided URL and confirmation. --- # Dogfood @@ -15,9 +14,10 @@ Systematically explore Exceptionless as a local user, find issues, and produce a Local targets: +The Svelte app at `/` is the only application UI. Exercise primary hash URLs when testing incoming-link compatibility, then verify they resolve to the root routes without losing the session. + - Aspire dashboard: `https://ex.dev.localhost:7101` -- Svelte app: `https://web-ex.dev.localhost:7131/next/` -- Legacy Angular app: `https://angular-ex.dev.localhost:7121` +- Svelte app: `https://web-ex.dev.localhost:7131/` - API health check: `https://api-ex.dev.localhost:7111/api/v2/about` - API health fallback for command-line tools with local TLS issues: `http://api-ex.dev.localhost:7110/api/v2/about` diff --git a/.agents/skills/frontend-architecture/SKILL.md b/.agents/skills/frontend-architecture/SKILL.md index a696729cfa..a2965c6298 100644 --- a/.agents/skills/frontend-architecture/SKILL.md +++ b/.agents/skills/frontend-architecture/SKILL.md @@ -5,7 +5,7 @@ description: Apply Exceptionless Svelte conventions when adding or restructuring # Frontend Architecture -Work in `src/Exceptionless.Web/ClientApp` and follow its `AGENTS.md`. +Work in `src/Exceptionless.Web/ClientApp`, the sole application frontend at `/`, and follow its `AGENTS.md`. Generate root routes with `$app/paths`; preserve primary historical hash links through the incoming-link compatibility layer. ## Feature structure diff --git a/.claude/agents/engineer.md b/.claude/agents/engineer.md index db9a988f93..b2aca1f83e 100644 --- a/.claude/agents/engineer.md +++ b/.claude/agents/engineer.md @@ -6,6 +6,8 @@ description: Implement scoped Exceptionless changes and verify the requested beh Follow `AGENTS.md` and the relevant local skill. Establish the requested result and affected compatibility boundaries, then implement the smallest complete change using current repository patterns. +Frontend work targets the Svelte application in `src/Exceptionless.Web/ClientApp` at `/`. Preserve incoming-link and session compatibility. + Work through dependencies in order. Verify the changed behavior, fix regressions caused by the work, and retry when the result identifies a useful correction. Report persistent blockers with evidence instead of repeating the same attempt. Complete delivery within the user's authorization. Summarize changes, verification, and remaining gaps. Do not require a separate reviewer or QA agent for every edit. diff --git a/.claude/agents/qa.md b/.claude/agents/qa.md index 3b15a951a9..6a98620896 100644 --- a/.claude/agents/qa.md +++ b/.claude/agents/qa.md @@ -10,6 +10,8 @@ maxTurns: 40 Follow `AGENTS.md` and [dogfood](../../.agents/skills/dogfood/SKILL.md) for local runtime setup and evidence. Do not fix application code; use synthetic fixtures and preserve unrelated data. +Test the Svelte `App` at `/`, including incoming-link compatibility and session continuity. + Test the affected success and failure paths, plus relevant loading, empty, keyboard, and accessibility states. For API checks, verify the expected status and response shape; a generic 4xx does not establish correct authorization. Report PASS, FAIL, or BLOCKED per behavior, with reproduction steps, expected/actual results, and evidence. Distinguish API health from a verified user flow. `SILENT_MODE` returns the report to the parent. diff --git a/.codex/environments/environment.toml b/.codex/environments/environment.toml index d3bd579ef3..2fa6e2ed3c 100644 --- a/.codex/environments/environment.toml +++ b/.codex/environments/environment.toml @@ -54,8 +54,6 @@ dotnet restore Exceptionless.slnx echo "Installing Svelte frontend dependencies..." npm ci --prefix src/Exceptionless.Web/ClientApp --prefer-offline --no-audit -echo "Installing legacy Angular frontend dependencies..." -npm ci --prefix src/Exceptionless.Web/ClientApp.angular --prefer-offline --no-audit echo "Setup complete. Use Run Aspire to start the app, Run Storybook for component work, or Run Lume Docs to test the static site." ''' diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 1eec789659..f94530ef5f 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -29,7 +29,7 @@ ] } }, - "forwardPorts": [5601, 7101, 7111, 7121, 7131, 8026], + "forwardPorts": [5601, 7101, 7111, 7131, 8026], "otherPortsAttributes": { "onAutoForward": "ignore" }, @@ -45,10 +45,6 @@ "label": "API", "protocol": "https" }, - "7121": { - "label": "OldApp", - "protocol": "https" - }, "7131": { "label": "App", "protocol": "https" diff --git a/.devcontainer/postCreateCommand.sh b/.devcontainer/postCreateCommand.sh index 9b1b2fb75d..3ba02e4fa9 100644 --- a/.devcontainer/postCreateCommand.sh +++ b/.devcontainer/postCreateCommand.sh @@ -6,4 +6,3 @@ mkdir -p "$HOME/.aspnet/https" "$HOME/.aspnet/dev-certs/trust" dotnet restore Exceptionless.slnx npm ci --prefix src/Exceptionless.Web/ClientApp -npm ci --prefix src/Exceptionless.Web/ClientApp.angular diff --git a/.dockerignore b/.dockerignore index b80e2d28ad..452b3978d7 100644 --- a/.dockerignore +++ b/.dockerignore @@ -10,4 +10,11 @@ LICENSE.txt **/bin/ **/obj/ **/TestResult/ -**/out/ \ No newline at end of file +**/out/ +**/node_modules/ +**/.svelte-kit/ +src/Exceptionless.Web/ClientApp/build/ +src/Exceptionless.Web/ClientApp/test-results/ +src/Exceptionless.Web/ClientApp/playwright-report/ +src/Exceptionless.Web/ClientApp/blob-report/ +dogfood-output/ diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 079133d11f..7a8f64a543 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -288,6 +288,9 @@ jobs: - name: Lint Client run: npm run lint + - name: Verify runtime configuration and entrypoints + run: node --test ../../../build/update-config.test.mjs + - name: Check run: npm run check @@ -343,9 +346,7 @@ jobs: with: node-version: 24 cache: npm - cache-dependency-path: | - src/Exceptionless.Web/ClientApp/package-lock.json - src/Exceptionless.Web/ClientApp.angular/package-lock.json + cache-dependency-path: src/Exceptionless.Web/ClientApp/package-lock.json - uses: actions/cache@v6 with: @@ -358,10 +359,6 @@ jobs: working-directory: src/Exceptionless.Web/ClientApp run: npm ci - - name: Install Legacy Client Npm Packages - working-directory: src/Exceptionless.Web/ClientApp.angular - run: npm ci - - name: Install Playwright Chromium working-directory: src/Exceptionless.Web/ClientApp # The hosted Ubuntu image includes Chromium's system libraries. Avoid apt @@ -386,7 +383,7 @@ jobs: run: | for attempt in {1..60}; do if curl --connect-timeout 5 --max-time 20 -fksS https://web-ex.dev.localhost:7131/api/v2/about > /dev/null && - curl --connect-timeout 5 --max-time 20 -fksS https://web-ex.dev.localhost:7131/next/login > /dev/null; then + curl --connect-timeout 5 --max-time 20 -fksS https://web-ex.dev.localhost:7131/login > /dev/null; then break fi @@ -406,7 +403,7 @@ jobs: - name: Verify E2E Endpoints run: | curl --connect-timeout 5 --max-time 30 -fksS https://web-ex.dev.localhost:7131/api/v2/about > /dev/null - curl --connect-timeout 5 --max-time 30 -fksS https://web-ex.dev.localhost:7131/next/login > /dev/null + curl --connect-timeout 5 --max-time 30 -fksS https://web-ex.dev.localhost:7131/login > /dev/null - name: Run Playwright E2E Tests working-directory: src/Exceptionless.Web/ClientApp @@ -420,7 +417,7 @@ jobs: if: ${{ failure() }} run: | mkdir -p aspire-logs - for resource in Api App OldApp; do + for resource in Api App; do timeout 30s aspire logs "$resource" --apphost src/Exceptionless.AppHost --tail 200 --timestamps --non-interactive > "aspire-logs/$resource.log" 2>&1 || true done free -m > aspire-logs/memory.log diff --git a/.gitignore b/.gitignore index 36508301c3..0b05b8501f 100644 --- a/.gitignore +++ b/.gitignore @@ -61,7 +61,6 @@ App_Data src/Exceptionless.Web/storage/ src/Exceptionless.Web/ClientApp/build/ src/Exceptionless.Web/appsettings.Local.yml -src/Exceptionless.Web/ClientApp.angular/dist/ src/Exceptionless.Web/wwwroot/ node_modules diff --git a/.vscode/launch.json b/.vscode/launch.json index c36c3d60d8..b467cd8048 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -61,7 +61,7 @@ "type": "msedge", "request": "launch", "name": "frontend: Web (Edge)", - "url": "https://web-ex.dev.localhost:7131/next", + "url": "https://web-ex.dev.localhost:7131/", "webRoot": "${workspaceFolder}/src/Exceptionless.Web/ClientApp", "preLaunchTask": "npm run dev" }, @@ -69,7 +69,7 @@ "type": "msedge", "request": "launch", "name": "frontend: Web Dev Api (Edge)", - "url": "https://web-ex.dev.localhost:7131/next", + "url": "https://web-ex.dev.localhost:7131/", "webRoot": "${workspaceFolder}/src/Exceptionless.Web/ClientApp", "preLaunchTask": "npm run dev:api" }, @@ -85,7 +85,7 @@ "type": "chrome", "request": "launch", "name": "frontend: Web (Chrome)", - "url": "https://web-ex.dev.localhost:7131/next", + "url": "https://web-ex.dev.localhost:7131/", "webRoot": "${workspaceFolder}/src/Exceptionless.Web/ClientApp", "preLaunchTask": "npm run dev" }, @@ -93,7 +93,7 @@ "type": "chrome", "request": "launch", "name": "frontend: Web Dev Api (Chrome)", - "url": "https://web-ex.dev.localhost:7131/next", + "url": "https://web-ex.dev.localhost:7131/", "webRoot": "${workspaceFolder}/src/Exceptionless.Web/ClientApp", "preLaunchTask": "npm run dev:api" }, diff --git a/AGENTS.md b/AGENTS.md index a394075a19..29cd8da703 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -35,17 +35,16 @@ src/ ├── Exceptionless.Core # Domain logic ├── Exceptionless.Insulation # Elasticsearch, Redis, Azure infrastructure ├── Exceptionless.Web # API host -│ ├── ClientApp.angular/ # Legacy Angular UI -│ └── ClientApp/ # Svelte 5 UI +│ └── ClientApp/ # Svelte 5 UI at / └── Exceptionless.Job # Background workers tests/ # C# tests and HTTP samples ``` ## Frontend Direction -- `src/Exceptionless.Web/ClientApp` is the default target for all new frontend UI work. -- `src/Exceptionless.Web/ClientApp.angular` is legacy. Touch it only when the user explicitly asks for Angular/legacy UI work or the bug exists only there. -- Do not copy Angular patterns into Svelte. Use the frontend skills for Svelte architecture, TanStack Query/Form, and shadcn-svelte details. +- `src/Exceptionless.Web/ClientApp` is the only application frontend and serves Svelte at `/`. +- Use root routes for navigation, documentation, and ordinary test fixtures. Keep hash-route examples in incoming-link compatibility tests only; preserve the existing session storage key and public contracts. +- Use the frontend skills for Svelte architecture, TanStack Query/Form, and shadcn-svelte details. ## Testing and Safety @@ -57,8 +56,7 @@ tests/ # C# tests and HTTP samples - Treat flaky tests as failures to investigate and fix. Retries are for diagnostics, not acceptance; preserve CI's flaky-test gate. Reproduce timing failures with retries disabled and repeat affected scenarios after fixing the cause. Synchronize on observable state and isolate unrelated background activity instead of widening assertions or adding arbitrary sleeps. - Local app URLs: - Aspire dashboard: `https://ex.dev.localhost:7101` - - Svelte app: `https://web-ex.dev.localhost:7131/next/` - - Legacy Angular app: `https://angular-ex.dev.localhost:7121` + - Svelte app: `https://web-ex.dev.localhost:7131/` - API health: `https://api-ex.dev.localhost:7111/api/v2/about` - API health fallback for command-line tools with local TLS issues: `http://api-ex.dev.localhost:7110/api/v2/about` - Aspire may assign dynamic local ports; when it does, use the endpoints emitted by the AppHost rather than assuming the fixed URLs above. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3f96414dfa..9e49248a1f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -22,6 +22,8 @@ Anyone wishing to contribute to the **[Exceptionless/Exceptionless](https://gith ## Contributing (Step-by-step) +See the [README](README.md#contributing) for Aspire setup. The only application frontend is Svelte in `src/Exceptionless.Web/ClientApp`, served at `/`. Follow its [frontend guide](src/Exceptionless.Web/ClientApp/README.md) and the applicable `AGENTS.md` instructions. + 1. Refer to the GitHub documentation on how to fork the repository: https://help.github.com/articles/fork-a-repo diff --git a/Dockerfile b/Dockerfile index 65c6845770..cd612b5e2b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -83,16 +83,13 @@ WORKDIR /app COPY --from=app-publish /app/src/Exceptionless.Web/out ./ COPY ./build/app-docker-entrypoint.sh ./ COPY ./build/update-config.sh /usr/local/bin/update-config -COPY ./build/update-config-next.sh /usr/local/bin/update-config-next ENV EX_ConnectionStrings__Storage=provider=folder;path=/app/storage \ EX_RunJobsInProcess=true \ - ASPNETCORE_URLS=http://+:8080 \ - EX_Html5Mode=true + ASPNETCORE_URLS=http://+:8080 RUN chmod +x /app/app-docker-entrypoint.sh RUN chmod +x /usr/local/bin/update-config -RUN chmod +x /usr/local/bin/update-config-next EXPOSE 8080 @@ -107,7 +104,6 @@ COPY --from=job-publish /app/src/Exceptionless.Job/out ./ COPY --from=app-publish /app/src/Exceptionless.Web/out ./ COPY ./build/docker-entrypoint.sh ./ COPY ./build/update-config.sh /usr/local/bin/update-config -COPY ./build/update-config-next.sh /usr/local/bin/update-config-next COPY ./build/supervisord.conf /etc/ USER root @@ -137,12 +133,10 @@ ENV discovery.type=single-node \ DOTNET_RUNNING_IN_CONTAINER=true \ EX_ConnectionStrings__Storage=provider=folder;path=/app/storage \ EX_ConnectionStrings__Elasticsearch=server=http://localhost:9200 \ - EX_RunJobsInProcess=true \ - EX_Html5Mode=true + EX_RunJobsInProcess=true RUN chmod +x /app/docker-entrypoint.sh && \ chmod +x /usr/local/bin/update-config && \ - chmod +x /usr/local/bin/update-config-next && \ chown -R elasticsearch:elasticsearch /app && \ mkdir -p /var/log/supervisor >/dev/null 2>&1 && \ chown -R elasticsearch:elasticsearch /var/log/supervisor diff --git a/README.md b/README.md index 8a416d200b..46dd2eedd8 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ Start here: After startup: -1. Open `https://localhost:7121/` if a browser does not open automatically. +1. Open `https://web-ex.dev.localhost:7131/` if a browser does not open automatically. If Aspire assigns a dynamic port, use the App endpoint shown in its dashboard. 2. In `Development` mode, a global administrator user `admin@exceptionless.test` with password `tester` is created automatically. Notes: @@ -48,14 +48,11 @@ Notes: 2. Backend tests bootstrap required infrastructure automatically. 3. Use Aspire CLI 13.6.0 or newer (`aspire update --self` to update). The AppHost uses the CLI bundle for the dashboard and resource orchestrator; when no CLI is installed, the SDK downloads the matching version through DNX. - - ## UI Development -Frontend work currently spans two apps: +The Svelte 5 UI in `src/Exceptionless.Web/ClientApp` is the only application frontend and serves at `/`. Aspire starts Svelte together with the API. Primary hash-route bookmarks resolve to the current pages, and existing signed-in sessions are retained. -1. The legacy Angular UI in `src/Exceptionless.Web/ClientApp.angular` is still the main site UI. Most of that app lives in `app/`, `components/`, `less/`, `img/`, `lang/`, and `grunt/`. -2. The Svelte 5 UI in `src/Exceptionless.Web/ClientApp` is still under development. +See the [frontend README](src/Exceptionless.Web/ClientApp/README.md) for development, build, and test commands. Published app images contain the Svelte build at the web root; API-only images retain their API landing page. For a quick API walkthrough, start with [exceptionless.http](exceptionless.http). The [tests/http](tests/http) folder contains additional examples for manual use, such as querying events, uploading source maps, and obtaining OAuth tokens. These are curated examples, not an automated test suite or an exhaustive endpoint catalog. Set the local API URL and sample variables, then run the requests you need. In Visual Studio Code, use the [REST Client](https://marketplace.visualstudio.com/items?itemName=humao.rest-client) extension. diff --git a/build/EmailTemplatePreviews.cs b/build/EmailTemplatePreviews.cs index 6c7c7c59da..cc0d07f70e 100644 --- a/build/EmailTemplatePreviews.cs +++ b/build/EmailTemplatePreviews.cs @@ -121,7 +121,7 @@ EmailLink[] GetActionLinks(string stackId) => new EmailLink("Mark event as fixed", $"{appUrl}/stack/{stackId}/mark-fixed"), new EmailLink("Stop sending notifications for this event", $"{appUrl}/stack/{stackId}/ignored"), new EmailLink("Discard future event occurrences", $"{appUrl}/stack/{stackId}/discarded"), - new EmailLink("Change your notification settings for this project", $"{appUrl}/account/manage?projectId=project-1&tab=notifications") + new EmailLink("Change your notification settings for this project", $"{appUrl}/account/notifications?project=project-1") ]; var stacks = new[] { @@ -183,24 +183,24 @@ EmailLink[] GetActionLinks(string stackId) => true, false, "11:00 PM", - $"{appUrl}/organization/organization-1/upgrade", - $"{appUrl}/organization/organization-1/frequent", + $"{appUrl}/organization/organization-1/billing?changePlan=true", + $"{appUrl}/organization/organization-1/dashboard?view=stacks", "https://github.com/exceptionless/Exceptionless/wiki/Frequently-Asked-Questions#q-what-happens-if-the-organization-plan-limit-is-reached", - [new("View usage", $"{appUrl}/organization/organization-1/manage"), new("Change your notification settings", $"{appUrl}/account/manage?tab=notifications")])), + [new("View usage", $"{appUrl}/organization/organization-1/usage"), new("Change your notification settings", $"{appUrl}/account/notifications")])), new("organization-hourly-limit", "Hourly throttling", new OrganizationNoticeEmail( "[Engineering] Events are currently being throttled.", "Engineering", false, true, "11:00 PM", - $"{appUrl}/organization/organization-1/upgrade", - $"{appUrl}/organization/organization-1/frequent", + $"{appUrl}/organization/organization-1/billing?changePlan=true", + $"{appUrl}/organization/organization-1/dashboard?view=stacks", "https://github.com/exceptionless/Exceptionless/wiki/Frequently-Asked-Questions#q-why-is-my-organization-throttled", - [new("View usage", $"{appUrl}/organization/organization-1/manage"), new("Change your notification settings", $"{appUrl}/account/manage?tab=notifications")])), + [new("View usage", $"{appUrl}/organization/organization-1/usage"), new("Change your notification settings", $"{appUrl}/account/notifications")])), new("organization-payment-failed", "Payment failed", new OrganizationPaymentFailedEmail( "[Engineering] Payment failed! Update billing information to avoid service interruption!", "Engineering", - $"{appUrl}/organization/organization-1/manage?tab=billing")), + $"{appUrl}/organization/organization-1/billing")), new("project-daily-summary", "Daily summary", new ProjectDailySummaryEmail( "[Storefront API] Summary for August 24, 2026", "Storefront API", @@ -214,12 +214,12 @@ EmailLink[] GetActionLinks(string stackId) => false, stacks, stacks[..2], - $"{appUrl}/project/project-1/error/timeline", + $"{appUrl}/project/project-1/dashboard?type=error", $"{appUrl}/project/project-1/configure", - $"{appUrl}/organization/organization-1/upgrade", - $"{appUrl}/project/project-1/error/frequent", - $"{appUrl}/project/project-1/error/new", - $"{appUrl}/account/manage?projectId=project-1&tab=notifications")), + $"{appUrl}/organization/organization-1/billing?changePlan=true", + $"{appUrl}/project/project-1/dashboard?type=error&view=stacks", + $"{appUrl}/project/project-1/dashboard?type=error&view=stacks", + $"{appUrl}/account/notifications?project=project-1")), new("project-daily-summary-throttled", "Daily summary with throttling", new ProjectDailySummaryEmail( "[Storefront API] Summary for August 24, 2026", "Storefront API", @@ -233,12 +233,12 @@ EmailLink[] GetActionLinks(string stackId) => true, stacks, stacks[..2], - $"{appUrl}/project/project-1/error/timeline", + $"{appUrl}/project/project-1/dashboard?type=error", $"{appUrl}/project/project-1/configure", - $"{appUrl}/organization/organization-1/upgrade", - $"{appUrl}/project/project-1/error/frequent", - $"{appUrl}/project/project-1/error/new", - $"{appUrl}/account/manage?projectId=project-1&tab=notifications")), + $"{appUrl}/organization/organization-1/billing?changePlan=true", + $"{appUrl}/project/project-1/dashboard?type=error&view=stacks", + $"{appUrl}/project/project-1/dashboard?type=error&view=stacks", + $"{appUrl}/account/notifications?project=project-1")), new("project-not-configured", "Project not configured", new ProjectDailySummaryEmail( "[Storefront API] Summary for August 24, 2026", "Storefront API", @@ -252,12 +252,12 @@ EmailLink[] GetActionLinks(string stackId) => false, [], [], - $"{appUrl}/project/project-1/error/timeline", + $"{appUrl}/project/project-1/dashboard?type=error", $"{appUrl}/project/project-1/configure", - $"{appUrl}/organization/organization-1/upgrade", - $"{appUrl}/project/project-1/error/frequent", - $"{appUrl}/project/project-1/error/new", - $"{appUrl}/account/manage?projectId=project-1&tab=notifications")), + $"{appUrl}/organization/organization-1/billing?changePlan=true", + $"{appUrl}/project/project-1/dashboard?type=error&view=stacks", + $"{appUrl}/project/project-1/dashboard?type=error&view=stacks", + $"{appUrl}/account/notifications?project=project-1")), new("user-email-verify", "Email verification", new UserEmailVerifyEmail( "Exceptionless Account Confirmation", "Ada Lovelace", diff --git a/build/app-docker-entrypoint.sh b/build/app-docker-entrypoint.sh index bed328c7a7..afbbf83579 100644 --- a/build/app-docker-entrypoint.sh +++ b/build/app-docker-entrypoint.sh @@ -1,11 +1,8 @@ #!/bin/bash +set -euo pipefail -pushd /app/wwwroot +cd /app/wwwroot update-config -popd +cd /app -pushd /app/wwwroot/next -update-config-next -popd - -eval "dotnet Exceptionless.Web.dll $@" +exec dotnet Exceptionless.Web.dll "$@" diff --git a/build/docker-entrypoint.sh b/build/docker-entrypoint.sh index f893511874..c51bf0ee6f 100755 --- a/build/docker-entrypoint.sh +++ b/build/docker-entrypoint.sh @@ -13,20 +13,14 @@ if [ "$#" -ne 0 ]; then echo "Starting Elasticsearch..." /usr/local/bin/docker-entrypoint.sh eswrapper > /var/log/exceptionless/elasticsearch.log 2>&1 & sleep 5 - eval "dotnet Exceptionless.Job.dll $@" + dotnet Exceptionless.Job.dll "$@" else echo "Running Exceptionless Web" mkdir -p /var/log/supervisor mkdir -p Temp/ - pushd /app/wwwroot - update-config - popd - - pushd /app/wwwroot/next - update-config-next - popd + (cd /app/wwwroot && update-config) || exit $? supervisord -c /etc/supervisord.conf diff --git a/build/update-config-next.sh b/build/update-config-next.sh deleted file mode 100644 index 6eac88f9b8..0000000000 --- a/build/update-config-next.sh +++ /dev/null @@ -1,60 +0,0 @@ -#!/bin/bash - -ApiUrl="${EX_ApiUrl:-}" -ClientSetupShowServerUrl="${EX_ClientSetupShowServerUrl:-true}" -EnableAccountCreation="${EX_EnableAccountCreation:-true}" - -OAuth="${EX_ConnectionStrings__OAuth:-}" -IFS=';' read -a oauthParts <<< "$OAuth" -for part in ${oauthParts[@]} -do - key="$( cut -d '=' -f 1 <<< $part )"; echo "key: $key" - value="$( cut -d '=' -f 2- <<< $part )"; echo "value: $value" - - if [ "$key" == "FacebookId" ]; then - FacebookAppId=$value - fi - if [ "$key" == "GitHubId" ]; then - GitHubAppId=$value - fi - if [ "$key" == "GoogleId" ]; then - GoogleAppId=$value - fi - if [ "$key" == "IntercomId" ]; then - IntercomAppId=$value - fi - if [ "$key" == "MicrosoftId" ]; then - MicrosoftAppId=$value - fi - if [ "$key" == "SlackId" ]; then - SlackAppId=$value - fi -done - -config_header="export const env={" - -config=" - PUBLIC_BASE_URL: '$ApiUrl' || window.location.origin, - PUBLIC_ENABLE_ACCOUNT_CREATION: '$EnableAccountCreation', - PUBLIC_SYSTEM_NOTIFICATION_MESSAGE: '$EX_NotificationMessage', - PUBLIC_EXCEPTIONLESS_API_KEY: '$EX_ExceptionlessApiKey', - PUBLIC_EXCEPTIONLESS_CLIENT_SETUP_SHOW_SERVER_URL: '$ClientSetupShowServerUrl', - PUBLIC_EXCEPTIONLESS_SERVER_URL: '$EX_ExceptionlessServerUrl', - PUBLIC_STRIPE_PUBLISHABLE_KEY: '$EX_StripePublishableApiKey', - PUBLIC_FACEBOOK_APPID: '$FacebookAppId', - PUBLIC_GITHUB_APPID: '$GitHubAppId', - PUBLIC_GOOGLE_APPID: '$GoogleAppId', - PUBLIC_MICROSOFT_APPID: '$MicrosoftAppId', - PUBLIC_INTERCOM_APPID: '$IntercomAppId', - PUBLIC_SLACK_APPID: '$SlackAppId'" - -config_footer="};" - -echo "Exceptionless UI Config" -echo "$config" - -checksum=`echo -n $config | md5sum | cut -c 1-32` -echo "$config_header$config$config_footer" > "_app/env.js" - -CONTENT=$(cat index.html) -echo "$CONTENT" | sed -E "s|/next/_app/env.js|/next/_app/env.js?v=$checksum|g" > index.html diff --git a/build/update-config.sh b/build/update-config.sh index b30349125c..c2d00dd8d0 100644 --- a/build/update-config.sh +++ b/build/update-config.sh @@ -1,65 +1,56 @@ #!/bin/bash - -ApiUrl="${EX_ApiUrl:-}" -Html5Mode="${EX_Html5Mode:-false}" -EnableSsl="${EX_EnableSsl:-false}" -EnableAccountCreation="${EX_EnableAccountCreation:-true}" - -OAuth="${EX_ConnectionStrings__OAuth:-}" -IFS=';' read -a oauthParts <<< "$OAuth" -for part in ${oauthParts[@]} -do - key="$( cut -d '=' -f 1 <<< $part )"; echo "key: $key" - value="$( cut -d '=' -f 2- <<< $part )"; echo "value: $value" - - if [ "$key" == "FacebookId" ]; then - FacebookAppId=$value - fi - if [ "$key" == "GitHubId" ]; then - GitHubAppId=$value - fi - if [ "$key" == "GoogleId" ]; then - GoogleAppId=$value - fi - if [ "$key" == "IntercomId" ]; then - IntercomAppId=$value - fi - if [ "$key" == "MicrosoftId" ]; then - MicrosoftAppId=$value - fi - if [ "$key" == "SlackId" ]; then - SlackAppId=$value - fi +set -euo pipefail + +# Only publish client IDs. OAuth secrets must never enter env.js or startup logs. +declare -A oauth_ids=() +IFS=';' read -ra oauth_parts <<< "${EX_ConnectionStrings__OAuth:-}" +for part in "${oauth_parts[@]}"; do + key="${part%%=*}" + key="${key//[[:space:]]/}" + value="${part#*=}" + value="${value#"${value%%[![:space:]]*}"}" + value="${value%"${value##*[![:space:]]}"}" + case "$key" in + FacebookId|GitHubId|GoogleId|IntercomId|MicrosoftId|SlackId) + oauth_ids[$key]="$value" ;; + esac done -config_header="(function () { - 'use strict'; - - angular.module('app.config', [])" - -config=" - .constant('BASE_URL', '$ApiUrl' || window.location.origin) - .constant('EXCEPTIONLESS_API_KEY', '$EX_ExceptionlessApiKey') - .constant('EXCEPTIONLESS_SERVER_URL', '$EX_ExceptionlessServerUrl') - .constant('FACEBOOK_APPID', '$FacebookAppId') - .constant('GITHUB_APPID', '$GitHubAppId') - .constant('GOOGLE_APPID', '$GoogleAppId') - .constant('INTERCOM_APPID', '$IntercomAppId') - .constant('LIVE_APPID', '$MicrosoftAppId') - .constant('SLACK_APPID', '$SlackAppId') - .constant('STRIPE_PUBLISHABLE_KEY', '$EX_StripePublishableApiKey') - .constant('SYSTEM_NOTIFICATION_MESSAGE', '$EX_NotificationMessage') - .constant('USE_HTML5_MODE', $Html5Mode) - .constant('USE_SSL', $EnableSsl) - .constant('ENABLE_ACCOUNT_CREATION', $EnableAccountCreation);" -config_footer=" -}());" - -echo "Exceptionless UI Config" -echo "$config" - -checksum=`echo -n $config | md5sum | cut -c 1-32` -echo "$config_header$config$config_footer" > "app.config.$checksum.js" - -CONTENT=$(cat index.html) -echo "$CONTENT" | sed -E "s/app\.config\..+\.js/app.config.$checksum.js/" > index.html +js_string() { + local value="${1:-}" + value="${value//\\/\\\\}" + value="${value//\'/\\\'}" + value="${value//$'\r'/\\r}" + value="${value//$'\n'/\\n}" + printf "'%s'" "$value" +} + +write_setting() { + printf ' %s: ' "$1" + js_string "$2" + printf ',\n' +} + +mkdir -p _app +{ + printf 'export const env={\n PUBLIC_BASE_URL: ' + js_string "${EX_ApiUrl:-}" + printf ' || window.location.origin,\n' + write_setting PUBLIC_ENABLE_ACCOUNT_CREATION "${EX_EnableAccountCreation:-true}" + write_setting PUBLIC_ENABLE_SSL "${EX_EnableSsl:-false}" + write_setting PUBLIC_SYSTEM_NOTIFICATION_MESSAGE "${EX_NotificationMessage:-}" + write_setting PUBLIC_EXCEPTIONLESS_API_KEY "${EX_ExceptionlessApiKey:-}" + write_setting PUBLIC_EXCEPTIONLESS_CLIENT_SETUP_SHOW_SERVER_URL "${EX_ClientSetupShowServerUrl:-true}" + write_setting PUBLIC_EXCEPTIONLESS_SERVER_URL "${EX_ExceptionlessServerUrl:-}" + write_setting PUBLIC_STRIPE_PUBLISHABLE_KEY "${EX_StripePublishableApiKey:-}" + write_setting PUBLIC_FACEBOOK_APPID "${oauth_ids[FacebookId]:-}" + write_setting PUBLIC_GITHUB_APPID "${oauth_ids[GitHubId]:-}" + write_setting PUBLIC_GOOGLE_APPID "${oauth_ids[GoogleId]:-}" + write_setting PUBLIC_MICROSOFT_APPID "${oauth_ids[MicrosoftId]:-}" + write_setting PUBLIC_INTERCOM_APPID "${oauth_ids[IntercomId]:-}" + write_setting PUBLIC_SLACK_APPID "${oauth_ids[SlackId]:-}" + printf '};\n' +} > _app/env.js + +checksum=$(md5sum _app/env.js | cut -c 1-32) +sed -E -i "s|/_app/env.js(\\?v=[a-f0-9]+)?|/_app/env.js?v=$checksum|g" index.html diff --git a/build/update-config.test.mjs b/build/update-config.test.mjs new file mode 100644 index 0000000000..cde04805fc --- /dev/null +++ b/build/update-config.test.mjs @@ -0,0 +1,91 @@ +import assert from 'node:assert/strict'; +import { execFileSync, spawnSync } from 'node:child_process'; +import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; +import test from 'node:test'; +import { fileURLToPath } from 'node:url'; +import { runInNewContext } from 'node:vm'; + +test('runtime configuration preserves the SSL setting, escaped values, and secret boundary', () => { + const script = fileURLToPath(new URL('./update-config.sh', import.meta.url)); + const message = "Quoted 'message' with \\ and a newline\n"; + for (const setting of [undefined, 'false', 'true']) { + const directory = mkdtempSync(join(tmpdir(), 'exceptionless-runtime-config-')); + try { + writeFileSync(join(directory, 'index.html'), ''); + execFileSync('bash', [script], { + cwd: directory, + env: { + PATH: process.env.PATH, + EX_NotificationMessage: message, + EX_ConnectionStrings__OAuth: 'GitHubId=public-client;GitHubSecret=private-marker', + ...(setting === undefined ? {} : { EX_EnableSsl: setting }) + } + }); + const source = readFileSync(join(directory, '_app/env.js'), 'utf8'); + const context = { window: { location: { origin: 'http://localhost:8080' } } }; + runInNewContext(source.replace('export const env=', 'globalThis.env='), context); + assert.equal(context.env.PUBLIC_ENABLE_SSL, setting ?? 'false'); + assert.equal(context.env.PUBLIC_BASE_URL, 'http://localhost:8080'); + assert.equal(context.env.PUBLIC_SYSTEM_NOTIFICATION_MESSAGE, message); + assert.equal(context.env.PUBLIC_GITHUB_APPID, 'public-client'); + assert.ok(!source.includes('private-marker')); + assert.match(readFileSync(join(directory, 'index.html'), 'utf8'), /\/_app\/env\.js\?v=[a-f0-9]{32}/); + } finally { + rmSync(directory, { recursive: true, force: true }); + } + } +}); + +function withStartupFixture(run) { + const directory = mkdtempSync(join(tmpdir(), 'exceptionless-entrypoint-')); + try { + const bin = join(directory, 'bin'); + mkdirSync(bin); + mkdirSync(join(directory, 'app/wwwroot'), { recursive: true }); + writeFileSync(join(bin, 'update-config'), '#!/bin/bash\nexit "$CONFIG_EXIT"\n', { mode: 0o700 }); + writeFileSync(join(bin, 'dotnet'), '#!/usr/bin/env node\nconsole.log(JSON.stringify({ pid: process.pid, args: process.argv.slice(2) }));\n', { mode: 0o700 }); + run((script, configExit, args = []) => spawnSync('bash', [ + '-c', + // Run the actual entrypoint with only external processes and container directories replaced. + 'cd() { builtin cd "$TEST_ROOT$1"; }; chown() { :; }; mkdir() { :; }; supervisord() { echo SUPERVISOR_STARTED; exit 0; }; export -f cd chown mkdir supervisord; exec bash "$1" "${@:2}"', + 'entrypoint-test', + fileURLToPath(new URL(script, import.meta.url)), + ...args + ], { + cwd: directory, + encoding: 'utf8', + timeout: 5000, + env: { ...process.env, PATH: `${bin}:${process.env.PATH}`, TEST_ROOT: directory, CONFIG_EXIT: String(configExit) } + })); + } finally { + rmSync(directory, { recursive: true, force: true }); + } +} + +test('app entrypoint propagates config failure and replaces itself without splitting arguments', () => { + withStartupFixture((run) => { + const failure = run('./app-docker-entrypoint.sh', 42); + assert.equal(failure.status, 42, failure.stderr); + assert.equal(failure.stdout, ''); + + const success = run('./app-docker-entrypoint.sh', 0, ['--Example=two words']); + assert.equal(success.status, 0, success.stderr); + const application = JSON.parse(success.stdout); + assert.deepEqual(application.args, ['Exceptionless.Web.dll', '--Example=two words']); + assert.equal(application.pid, success.pid); + }); +}); + +test('all-in-one entrypoint starts its supervisor only after successful config generation', () => { + withStartupFixture((run) => { + const failure = run('./docker-entrypoint.sh', 42); + assert.equal(failure.status, 42, failure.stderr); + assert.ok(!failure.stdout.includes('SUPERVISOR_STARTED')); + + const success = run('./docker-entrypoint.sh', 0); + assert.equal(success.status, 0, success.stderr); + assert.ok(success.stdout.includes('SUPERVISOR_STARTED')); + }); +}); diff --git a/k8s/exceptionless/templates/config.yaml b/k8s/exceptionless/templates/config.yaml index 432bc7f93a..6c53f373a8 100644 --- a/k8s/exceptionless/templates/config.yaml +++ b/k8s/exceptionless/templates/config.yaml @@ -17,7 +17,6 @@ data: {{- end }} EX_DisabledPlugins: GeoPlugin,LocationPlugin EX_EnableSsl: "true" - EX_Html5Mode: "true" EX_ConnectionStrings__Elasticsearch: {{ default "server=http://elasticsearch:9200" .Values.elasticsearch.connectionString }} {{- if (empty .Values.redis.connectionString) }} EX_ConnectionStrings__Redis: {{ template "exceptionless.fullname" . }}-redis-master,abortConnect=false diff --git a/src/Exceptionless.AppHost/Extensions/WorktreeScope.cs b/src/Exceptionless.AppHost/Extensions/WorktreeScope.cs index b36b2c6e82..d2384b9fd0 100644 --- a/src/Exceptionless.AppHost/Extensions/WorktreeScope.cs +++ b/src/Exceptionless.AppHost/Extensions/WorktreeScope.cs @@ -10,15 +10,11 @@ public sealed record WorktreePorts( int ApiHttp, int ApiHttps, int JobsHttp, - int OldAppHttp, - int OldAppHttps, - int OldAppLiveReload, int AppHttps, int DocsHttp) { public string ApiHttpUrl => $"http://localhost:{ApiHttp}"; public string ApiHttpsUrl => $"https://localhost:{ApiHttps}"; - public string OldAppHttpsUrl => $"https://angular-ex.dev.localhost:{OldAppHttps}"; public string AppHttpsUrl => $"https://web-ex.dev.localhost:{AppHttps}"; } @@ -51,7 +47,7 @@ public static class WorktreeScope public static WorktreePorts AssignFreePorts() { - int[] ports = FreePorts(12); + int[] ports = FreePorts(9); var assignments = new WorktreePorts( ports[0], ports[1], @@ -61,10 +57,7 @@ public static WorktreePorts AssignFreePorts() ports[5], ports[6], ports[7], - ports[8], - ports[9], - ports[10], - ports[11]); + ports[8]); Environment.SetEnvironmentVariable("ASPNETCORE_URLS", $"https://localhost:{assignments.DashboardHttps};http://localhost:{assignments.DashboardHttp}"); Environment.SetEnvironmentVariable("ASPIRE_DASHBOARD_OTLP_ENDPOINT_URL", $"https://localhost:{assignments.DashboardOtlp}"); diff --git a/src/Exceptionless.AppHost/Program.cs b/src/Exceptionless.AppHost/Program.cs index 3dc79890fb..9969a49efc 100644 --- a/src/Exceptionless.AppHost/Program.cs +++ b/src/Exceptionless.AppHost/Program.cs @@ -13,10 +13,6 @@ bool servicesOnly = HasArgument("--services-only"); bool ciE2E = HasArgument("--ci-e2e"); bool includeDevTools = !ciE2E; -int oldAppHttpPort = worktreePorts?.OldAppHttp ?? 7120; -int oldAppPort = worktreePorts?.OldAppHttps ?? 7121; -int oldAppLiveReloadPort = worktreePorts?.OldAppLiveReload ?? 35729; -string oldAppAspNetCoreUrls = String.Concat("http://localhost:", oldAppHttpPort); int appPort = worktreePorts?.AppHttps ?? 7131; string appOrigin = worktreePorts?.AppHttpsUrl ?? $"https://web-ex.dev.localhost:{appPort}"; int docsPort = worktreePorts?.DocsHttp ?? 7141; @@ -129,6 +125,7 @@ .WithReference(storageBlobs, "AzureStorage") .WithReference(storageQueues, "AzureQueues") .WithEnvironment("ConnectionStrings:Email", SharedEmailConnectionString) + .WithEnvironment("EX_BaseURL", appOrigin) .WithEnvironment("Mcp:AllowedOrigins:0", appOrigin) .WithEnvironment("RunJobsInProcess", "false") .WaitFor(elastic) @@ -161,6 +158,7 @@ .WithReference(storageBlobs, "AzureStorage") .WithReference(storageQueues, "AzureQueues") .WithEnvironment("ConnectionStrings:Email", SharedEmailConnectionString) + .WithEnvironment("EX_BaseURL", appOrigin) .WithEnvironment("EX_ExceptionlessApiKey", builder.Configuration["ExceptionlessApiKey"]) .WithEnvironment("EX_ExceptionlessServerUrl", api.GetEndpoint("http")) .WaitFor(api) @@ -188,35 +186,9 @@ } #pragma warning disable ASPIREBROWSERLOGS001 - var oldApp = builder.AddJavaScriptApp("OldApp", "../../src/Exceptionless.Web/ClientApp.angular", "serve") - .WithBrowserLogs() - .WithReference(api) - .WithEnvironment("ASPNETCORE_URLS", oldAppAspNetCoreUrls) - .WithEnvironment("USE_HTTPS", "true") - .WithEnvironment("LIVERELOAD_PORT", oldAppLiveReloadPort.ToString()) - .WithHttpEndpoint(port: oldAppPort, targetPort: oldAppPort, name: "https", env: "PORT", isProxied: false) - .WithEndpoint("https", e => - { - e.TargetHost = "angular-ex.dev.localhost"; - e.UriScheme = "https"; - }) - .WithHttpsDeveloperCertificate() - .WithUrlForEndpoint("https", u => - { - u.DisplayText = "Open App (Old)"; - }) - .WithParentRelationship(api); - - if (worktreePorts is not null) - { - oldApp.WithEnvironment("API_HTTP", worktreePorts.ApiHttpUrl) - .WithEnvironment("API_HTTPS", worktreePorts.ApiHttpsUrl); - } - var app = builder.AddViteApp("App", "../Exceptionless.Web/ClientApp") .WithBrowserLogs() .WithReference(api) - .WithReference(oldApp) .WithEnvironment("PUBLIC_EXCEPTIONLESS_API_KEY", builder.Configuration["PUBLIC_EXCEPTIONLESS_API_KEY"]) .WithEnvironment("PUBLIC_EXCEPTIONLESS_SERVER_URL", exceptionlessServerUrl) .WithEnvironment("PUBLIC_EXCEPTIONLESS_TELEMETRY_SERVER_URL", builder.Configuration["PUBLIC_EXCEPTIONLESS_TELEMETRY_SERVER_URL"] ?? String.Empty) @@ -234,16 +206,13 @@ .WithUrlForEndpoint("http", u => { u.DisplayText = "Open App"; - u.Url = $"{u.Url.TrimEnd('/')}/next/"; }) .WithParentRelationship(api); if (worktreePorts is not null) { app.WithEnvironment("API_HTTP", worktreePorts.ApiHttpUrl) - .WithEnvironment("API_HTTPS", worktreePorts.ApiHttpsUrl) - .WithEnvironment("OLDAPP_HTTP", worktreePorts.OldAppHttpsUrl) - .WithEnvironment("OLDAPP_HTTPS", worktreePorts.OldAppHttpsUrl); + .WithEnvironment("API_HTTPS", worktreePorts.ApiHttpsUrl); } if (includeDevTools) diff --git a/src/Exceptionless.Core/Configuration/AppOptions.cs b/src/Exceptionless.Core/Configuration/AppOptions.cs index 5473b994e5..d7ebd91d72 100644 --- a/src/Exceptionless.Core/Configuration/AppOptions.cs +++ b/src/Exceptionless.Core/Configuration/AppOptions.cs @@ -10,6 +10,8 @@ public class AppOptions { public string BaseURL { get; internal set; } = null!; + public string? ApiUrl { get; internal set; } + /// /// Internal project id keeps us from recursively logging to our self /// @@ -87,6 +89,7 @@ public static AppOptions ReadFromConfiguration(IConfiguration config) { var options = new AppOptions(); options.BaseURL = config.GetValue(nameof(options.BaseURL))?.TrimEnd('/') ?? throw new ApplicationException("BaseURL is a required configuration setting"); + options.ApiUrl = config.GetValue(nameof(options.ApiUrl))?.TrimEnd('/'); options.InternalProjectId = config.GetValue(nameof(options.InternalProjectId), "54b56e480ef9605a88a13153"); options.ExceptionlessApiKey = config.GetValue(nameof(options.ExceptionlessApiKey)); options.ExceptionlessServerUrl = config.GetValue(nameof(options.ExceptionlessServerUrl)); diff --git a/src/Exceptionless.Core/Extensions/UriExtensions.cs b/src/Exceptionless.Core/Extensions/UriExtensions.cs index c9355a3509..3144c9d800 100644 --- a/src/Exceptionless.Core/Extensions/UriExtensions.cs +++ b/src/Exceptionless.Core/Extensions/UriExtensions.cs @@ -4,6 +4,10 @@ namespace Exceptionless.Core.Extensions; public static class UriExtensions { + public static string GetOrigin(this string url) => new Uri(url).GetOrigin(); + + public static string GetOrigin(this Uri uri) => uri.GetLeftPart(UriPartial.Authority); + public static string ToQueryString(this NameValueCollection collection) { return collection.AsKeyValuePairs().ToQueryString(); diff --git a/src/Exceptionless.Core/Mail/Mailer.cs b/src/Exceptionless.Core/Mail/Mailer.cs index 982db88eb4..85933fb25a 100644 --- a/src/Exceptionless.Core/Mail/Mailer.cs +++ b/src/Exceptionless.Core/Mail/Mailer.cs @@ -2,6 +2,7 @@ using Exceptionless.Core.Models; using Exceptionless.Core.Plugins.Formatting; using Exceptionless.Core.Queues.Models; +using Exceptionless.Core.Utility; using Exceptionless.DateTimeExtensions; using Exceptionless.EmailTemplates; using Exceptionless.EmailTemplates.Models; @@ -16,7 +17,7 @@ public class Mailer : IMailer { private readonly IQueue _queue; private readonly IEmailTemplateRenderer _templateRenderer; - private readonly EmailAppUrlBuilder _appUrls; + private readonly AppUrlBuilder _appUrls; private readonly FormattingPluginManager _pluginManager; private readonly AppOptions _appOptions; private readonly TimeProvider _timeProvider; @@ -27,7 +28,7 @@ public Mailer(IQueue queue, IEmailTemplateRenderer templateRenderer { _queue = queue; _templateRenderer = templateRenderer; - _appUrls = new EmailAppUrlBuilder(appOptions.BaseURL); + _appUrls = new AppUrlBuilder(appOptions.BaseURL); _pluginManager = pluginManager; _appOptions = appOptions; _timeProvider = timeProvider; diff --git a/src/Exceptionless.Core/Plugins/Formatting/FormattingPluginBase.cs b/src/Exceptionless.Core/Plugins/Formatting/FormattingPluginBase.cs index 1b4960aa37..072ad5c412 100644 --- a/src/Exceptionless.Core/Plugins/Formatting/FormattingPluginBase.cs +++ b/src/Exceptionless.Core/Plugins/Formatting/FormattingPluginBase.cs @@ -1,6 +1,7 @@ using Exceptionless.Core.Extensions; using Exceptionless.Core.Models; using Exceptionless.Core.Models.Data; +using Exceptionless.Core.Utility; using Foundatio.Serializer; using Microsoft.Extensions.Logging; @@ -57,14 +58,14 @@ protected void AddDefaultSlackFields(PersistentEvent ev, List { $"• {GetSlackEventUrl(ev.Id, "View Event")}", - $"• <{baseUrl}/stack/{ev.StackId}/mark-fixed|Mark event as fixed>", - $"• <{baseUrl}/stack/{ev.StackId}/ignored|Stop sending notifications for this event>", - $"• <{baseUrl}/stack/{ev.StackId}/discarded|Discard future event occurrences>", - $"• <{baseUrl}/project/{ev.ProjectId}/manage?tab=integrations|Change your notification settings for this project>" + $"• <{appUrls.MarkStackFixed(ev.StackId)}|Mark event as fixed>", + $"• <{appUrls.IgnoreStack(ev.StackId)}|Stop sending notifications for this event>", + $"• <{appUrls.DiscardStack(ev.StackId)}|Discard future event occurrences>", + $"• <{appUrls.ProjectIntegrations(ev.ProjectId)}|Change your notification settings for this project>" }; attachmentFields.Add(new SlackMessage.SlackAttachmentFields { Title = "Other Actions", Value = String.Join("\n", actions) }); @@ -72,7 +73,7 @@ protected void AddDefaultSlackFields(PersistentEvent ev, List { $"{_options.BaseURL}/event/{eventId}" }; + var parts = new List { new AppUrlBuilder(_options.BaseURL).Event(eventId) }; if (!String.IsNullOrEmpty(message)) parts.Add($"|{message.Replace("&", "&").Replace("<", "<").Replace(">", ">")}"); diff --git a/src/Exceptionless.Core/Plugins/WebHook/Default/010_VersionOnePlugin.cs b/src/Exceptionless.Core/Plugins/WebHook/Default/010_VersionOnePlugin.cs index ae05ea24c1..fc46e1e9a5 100644 --- a/src/Exceptionless.Core/Plugins/WebHook/Default/010_VersionOnePlugin.cs +++ b/src/Exceptionless.Core/Plugins/WebHook/Default/010_VersionOnePlugin.cs @@ -41,7 +41,7 @@ public static JsonSerializerOptions CreateJsonSerializerOptions(JsonSerializerOp var requestInfo = ev.GetRequestInfo(_serializer, _logger); var environmentInfo = ev.GetEnvironmentInfo(_serializer, _logger); - return Task.FromResult(new VersionOneWebHookEvent(_options.BaseURL) + return Task.FromResult(new VersionOneWebHookEvent(_options.BaseURL.GetOrigin()) { Id = ev.Id, OccurrenceDate = ev.Date, @@ -76,7 +76,7 @@ public static JsonSerializerOptions CreateJsonSerializerOptions(JsonSerializerOp if (!String.Equals(ctx.WebHook.Version, Models.WebHook.KnownVersions.Version1)) return Task.FromResult(null); - return Task.FromResult(new VersionOneWebHookStack(_options.BaseURL) + return Task.FromResult(new VersionOneWebHookStack(_options.BaseURL.GetOrigin()) { Id = ctx.Stack.Id, Status = ctx.Stack.Status, diff --git a/src/Exceptionless.Core/Plugins/WebHook/Default/020_VersionTwoPlugin.cs b/src/Exceptionless.Core/Plugins/WebHook/Default/020_VersionTwoPlugin.cs index 81a859181c..dd3abcf5c8 100644 --- a/src/Exceptionless.Core/Plugins/WebHook/Default/020_VersionTwoPlugin.cs +++ b/src/Exceptionless.Core/Plugins/WebHook/Default/020_VersionTwoPlugin.cs @@ -1,5 +1,6 @@ using Exceptionless.Core.Models; using Exceptionless.Core.Pipeline; +using Exceptionless.Core.Extensions; using Microsoft.Extensions.Logging; namespace Exceptionless.Core.Plugins.WebHook; @@ -15,7 +16,7 @@ public VersionTwoPlugin(AppOptions options, ILoggerFactory loggerFactory) : base return Task.FromResult(null); var ev = ctx.Event!; - return Task.FromResult(new WebHookEvent(_options.BaseURL) + return Task.FromResult(new WebHookEvent(_options.BaseURL.GetOrigin()) { Id = ev.Id, OccurrenceDate = ev.Date, @@ -45,7 +46,7 @@ public VersionTwoPlugin(AppOptions options, ILoggerFactory loggerFactory) : base if (!String.Equals(ctx.WebHook.Version, Models.WebHook.KnownVersions.Version2)) return Task.FromResult(null); - return Task.FromResult(new WebHookStack(_options.BaseURL) + return Task.FromResult(new WebHookStack(_options.BaseURL.GetOrigin()) { Id = ctx.Stack.Id, Title = ctx.Stack.Title, diff --git a/src/Exceptionless.Core/Services/SlackService.cs b/src/Exceptionless.Core/Services/SlackService.cs index 7de5275616..a012dd1393 100644 --- a/src/Exceptionless.Core/Services/SlackService.cs +++ b/src/Exceptionless.Core/Services/SlackService.cs @@ -38,7 +38,7 @@ public SlackService(IQueue webHookNotificationQueue, Format { "client_id", _appOptions.SlackOptions.SlackId }, { "client_secret", _appOptions.SlackOptions.SlackSecret }, { "code", code }, - { "redirect_uri", new Uri(_appOptions.BaseURL).GetLeftPart(UriPartial.Authority) } + { "redirect_uri", _appOptions.BaseURL.GetOrigin() } }; string url = $"https://slack.com/api/oauth.access?{data!.ToQueryString()}"; diff --git a/src/Exceptionless.Core/Mail/EmailAppUrlBuilder.cs b/src/Exceptionless.Core/Utility/AppUrlBuilder.cs similarity index 68% rename from src/Exceptionless.Core/Mail/EmailAppUrlBuilder.cs rename to src/Exceptionless.Core/Utility/AppUrlBuilder.cs index 7fe2ac5631..3affcaed67 100644 --- a/src/Exceptionless.Core/Mail/EmailAppUrlBuilder.cs +++ b/src/Exceptionless.Core/Utility/AppUrlBuilder.cs @@ -1,15 +1,17 @@ -namespace Exceptionless.Core.Mail; +using Exceptionless.Core.Extensions; + +namespace Exceptionless.Core.Utility; /// -/// Centralizes the existing email destinations. Update these mappings when the Svelte UI moves to the application root. +/// Centralizes public application destinations used in notifications. /// -internal sealed class EmailAppUrlBuilder +internal sealed class AppUrlBuilder { private readonly string _baseUrl; - public EmailAppUrlBuilder(string baseUrl) + public AppUrlBuilder(string baseUrl) { - _baseUrl = baseUrl.TrimEnd('/'); + _baseUrl = baseUrl.GetOrigin(); } public string Event(string eventId) => Build($"event/{eventId}"); @@ -22,7 +24,9 @@ public EmailAppUrlBuilder(string baseUrl) public string DiscardStack(string stackId) => Build($"stack/{stackId}/discarded"); - public string ProjectNotifications(string projectId) => Build($"account/manage?projectId={Uri.EscapeDataString(projectId)}&tab=notifications"); + public string ProjectNotifications(string projectId) => Build($"account/notifications?project={Uri.EscapeDataString(projectId)}"); + + public string ProjectIntegrations(string projectId) => Build($"project/{projectId}/integrations"); public string OrganizationDashboard(string organizationId) => Build($"organization/{organizationId}/dashboard"); @@ -30,21 +34,21 @@ public EmailAppUrlBuilder(string baseUrl) public string OrganizationUpgrade(string organizationId) => Build($"organization/{organizationId}/upgrade"); - public string OrganizationFrequent(string organizationId) => Build($"organization/{organizationId}/frequent"); + public string OrganizationFrequent(string organizationId) => Build($"organization/{organizationId}/dashboard?view=stacks"); - public string OrganizationManage(string organizationId) => Build($"organization/{organizationId}/manage"); + public string OrganizationManage(string organizationId) => Build($"organization/{organizationId}/usage"); - public string OrganizationBilling(string organizationId) => Build($"organization/{organizationId}/manage?tab=billing"); + public string OrganizationBilling(string organizationId) => Build($"organization/{organizationId}/billing"); - public string ProjectTimeline(string projectId) => Build($"project/{projectId}/error/timeline"); + public string ProjectTimeline(string projectId) => Build($"project/{projectId}/dashboard?type=error"); public string ProjectConfigure(string projectId) => Build($"project/{projectId}/configure"); - public string ProjectMostFrequent(string projectId) => Build($"project/{projectId}/error/frequent"); + public string ProjectMostFrequent(string projectId) => Build($"project/{projectId}/dashboard?type=error&view=stacks"); - public string ProjectNewest(string projectId) => Build($"project/{projectId}/error/new"); + public string ProjectNewest(string projectId) => Build($"project/{projectId}/dashboard?type=error&view=stacks"); - public string AccountNotifications() => Build("account/manage?tab=notifications"); + public string AccountNotifications() => Build("account/notifications"); public string VerifyEmail(string token) => Build($"account/verify?token={Uri.EscapeDataString(token)}"); diff --git a/src/Exceptionless.Core/Utility/RandomEventGenerator.cs b/src/Exceptionless.Core/Utility/RandomEventGenerator.cs index f15ef8a6a9..916bcccd04 100644 --- a/src/Exceptionless.Core/Utility/RandomEventGenerator.cs +++ b/src/Exceptionless.Core/Utility/RandomEventGenerator.cs @@ -601,7 +601,7 @@ private static DataDictionary GenerateErrorData() private static readonly List PageNames = [ "/dashboard", "/project/settings", "/account/manage", "/event/search", - "/stack/details", "/api/v2/events", "/api/v2/stacks", "/next/login" + "/stack/details", "/api/v2/events", "/api/v2/stacks", "/login" ]; private static readonly List EventTypes = diff --git a/src/Exceptionless.EmailTemplates/README.md b/src/Exceptionless.EmailTemplates/README.md index 745a688f5e..2232c4afce 100644 --- a/src/Exceptionless.EmailTemplates/README.md +++ b/src/Exceptionless.EmailTemplates/README.md @@ -12,6 +12,8 @@ Each email has: `Exceptionless.Core.Mail.Mailer` creates the models and queues the rendered result. Template registration and dispatch are centralized in `RazorEmailTemplateRenderer`, whose generic constraints verify each component-to-model pairing at compile time. +Application links target the Svelte UI at `/` through the shared `AppUrlBuilder` in `Exceptionless.Core/Utility`, also used for Slack and webhook destinations. It normalizes historical base URLs; do not add path or hash-route prefixes to new messages. Event notification emails preserve the fixed, ignored, and discarded shortcuts. Each action opens a Svelte confirmation before changing the stack; historical action URLs use the same flow. The web host retains `/exceptionless.png` and `/img/logo.png` for previously delivered emails. + Run the focused rendering and mailer tests from the repository root: ```powershell diff --git a/src/Exceptionless.Job/appsettings.Development.yml b/src/Exceptionless.Job/appsettings.Development.yml index 7b7a1b7601..c759a5e519 100644 --- a/src/Exceptionless.Job/appsettings.Development.yml +++ b/src/Exceptionless.Job/appsettings.Development.yml @@ -9,7 +9,7 @@ ConnectionStrings: Email: smtp://localhost:1025 # Base url for the ui used to build links in emails and other places. -BaseURL: 'http://localhost:9001/#!' +BaseURL: https://web-ex.dev.localhost:7131 Serilog: MinimumLevel: diff --git a/src/Exceptionless.Job/appsettings.Staging.yml b/src/Exceptionless.Job/appsettings.Staging.yml index db415b6211..608f6972a1 100644 --- a/src/Exceptionless.Job/appsettings.Staging.yml +++ b/src/Exceptionless.Job/appsettings.Staging.yml @@ -9,7 +9,7 @@ ConnectionStrings: OAuth: FacebookId=395178683904310;GitHubId=7ef1dd5bfbc4ccf7f5ef;GoogleId=809763155066-enkkdmt4ierc33q9cft9nf5d5c02h30q.apps.googleusercontent.com;MicrosoftId=000000004C137E8B;SlackId=34500115540.177239122322; # Base url for the ui used to build links in emails and other places. -BaseURL: https://dev.exceptionless.io +BaseURL: https://dev-app.exceptionless.io # Exceptionless Client Settings ExceptionlessServerUrl: https://dev-api.exceptionless.io diff --git a/src/Exceptionless.Web/Api/Endpoints/OAuthEndpoints.cs b/src/Exceptionless.Web/Api/Endpoints/OAuthEndpoints.cs index bae765585e..a98ac1a932 100644 --- a/src/Exceptionless.Web/Api/Endpoints/OAuthEndpoints.cs +++ b/src/Exceptionless.Web/Api/Endpoints/OAuthEndpoints.cs @@ -16,18 +16,21 @@ public static IEndpointRouteBuilder MapOAuthEndpoints(this IEndpointRouteBuilder => await mediator.InvokeAsync(new GetAuthorizationServerMetadata())) .AllowAnonymous() .WithTags("OAuth") + .WithDescription("OAuth issuer and endpoint URLs use the configured public ApiUrl origin, falling back to BaseURL when ApiUrl is unset.") .Produces(); endpoints.MapGet(".well-known/oauth-protected-resource/mcp", async (IMediator mediator) => await mediator.InvokeAsync(new GetMcpProtectedResourceMetadata())) .AllowAnonymous() .WithTags("OAuth") + .WithDescription("MCP resource and authorization server URLs use the configured public ApiUrl origin, falling back to BaseURL when ApiUrl is unset.") .Produces(); endpoints.MapGet(".well-known/oauth-protected-resource/api/v2", async (IMediator mediator) => await mediator.InvokeAsync(new GetRestApiProtectedResourceMetadata())) .AllowAnonymous() .WithTags("OAuth") + .WithDescription("REST API resource and authorization server URLs use the configured public ApiUrl origin, falling back to BaseURL when ApiUrl is unset.") .Produces(); var group = endpoints.MapGroup("api/v2/oauth") @@ -45,6 +48,7 @@ public static IEndpointRouteBuilder MapOAuthEndpoints(this IEndpointRouteBuilder [FromQuery] string? resource = null) => await mediator.InvokeAsync(new RedirectToAuthorizeBridge())) .AllowAnonymous() + .WithDescription("Redirects to /oauth/authorize on the configured application BaseURL, preserving the authorization request query string.") .Produces(StatusCodes.Status302Found); group.MapPost("authorize", async (IMediator mediator, [FromBody] OAuthAuthorizeForm form) diff --git a/src/Exceptionless.Web/Api/Handlers/OAuthHandler.cs b/src/Exceptionless.Web/Api/Handlers/OAuthHandler.cs index d8738a7235..b615d9960c 100644 --- a/src/Exceptionless.Web/Api/Handlers/OAuthHandler.cs +++ b/src/Exceptionless.Web/Api/Handlers/OAuthHandler.cs @@ -56,7 +56,8 @@ public Task Handle(GetRestApiProtectedResourceMetadata message) public Task Handle(RedirectToAuthorizeBridge message) { - return Task.FromResult(HttpResults.Redirect("/next/oauth/authorize" + HttpContext.Request.QueryString)); + string applicationOrigin = appOptions.BaseURL.GetOrigin(); + return Task.FromResult(HttpResults.Redirect($"{applicationOrigin}/oauth/authorize{HttpContext.Request.QueryString}")); } public Task Handle(CompleteOAuthAuthorization message) @@ -153,7 +154,7 @@ private async Task IsDynamicClientRegistrationRateLimitedAsync() private string GetOrigin() { - return new Uri(appOptions.BaseURL).GetLeftPart(UriPartial.Authority); + return (String.IsNullOrWhiteSpace(appOptions.ApiUrl) ? appOptions.BaseURL : appOptions.ApiUrl).GetOrigin(); } private string GetResource(OAuthResourceDefinition resourceDefinition) diff --git a/src/Exceptionless.Web/Assistant/AssistantRoutes.cs b/src/Exceptionless.Web/Assistant/AssistantRoutes.cs index b38f49f0ff..e7750d10f4 100644 --- a/src/Exceptionless.Web/Assistant/AssistantRoutes.cs +++ b/src/Exceptionless.Web/Assistant/AssistantRoutes.cs @@ -3,11 +3,11 @@ namespace Exceptionless.Web.Assistant; internal static class AssistantRoutes { public static string Event(string stackId, string eventId) - => $"/next/stack/{Uri.EscapeDataString(stackId)}/event/{Uri.EscapeDataString(eventId)}"; + => $"/stack/{Uri.EscapeDataString(stackId)}/event/{Uri.EscapeDataString(eventId)}"; - public static string ProjectConfigure(string projectId) => $"/next/project/{Uri.EscapeDataString(projectId)}/configure"; + public static string ProjectConfigure(string projectId) => $"/project/{Uri.EscapeDataString(projectId)}/configure"; - public static string ProjectStacks(string projectId) => $"/next/project/{Uri.EscapeDataString(projectId)}/stacks"; + public static string ProjectStacks(string projectId) => $"/project/{Uri.EscapeDataString(projectId)}/stacks"; - public static string Stack(string stackId) => $"/next/stack/{Uri.EscapeDataString(stackId)}"; + public static string Stack(string stackId) => $"/stack/{Uri.EscapeDataString(stackId)}"; } diff --git a/src/Exceptionless.Web/ClientApp.angular/.editorconfig b/src/Exceptionless.Web/ClientApp.angular/.editorconfig deleted file mode 100644 index eb416d84f9..0000000000 --- a/src/Exceptionless.Web/ClientApp.angular/.editorconfig +++ /dev/null @@ -1,14 +0,0 @@ -# http://editorconfig.org -root = true - -# Default settings: -# A newline ending every file -# Use 4 spaces as indentation -[*] -insert_final_newline = true -indent_style = space -indent_size = 4 -trim_trailing_whitespace = true - -end_of_line = lf -charset = utf-8 diff --git a/src/Exceptionless.Web/ClientApp.angular/.eslintrc.json b/src/Exceptionless.Web/ClientApp.angular/.eslintrc.json deleted file mode 100644 index 78b777e866..0000000000 --- a/src/Exceptionless.Web/ClientApp.angular/.eslintrc.json +++ /dev/null @@ -1,51 +0,0 @@ -{ - "env": { - "browser": true, - "es2021": true - }, - "extends": ["eslint:recommended", "airbnb-base", "prettier"], - "globals": { - "$": "readonly", - "_": "readonly", - "angular": "readonly", - "define": "readonly", - "moment": "readonly", - "require": "readonly" - }, - "overrides": [], - "parserOptions": { - "ecmaVersion": 2015 - }, - "rules": { - "comma-dangle": "off", - "consistent-return": "off", - "func-names": "off", - "global-require": "off", - "guard-for-in": "off", - "max-len": "off", - "no-cond-assign": "off", - "no-continue": "off", - "no-extra-boolean-cast": "off", - "no-multi-assign": "off", - "no-param-reassign": "off", - "no-plusplus": "off", - "no-prototype-builtins": "off", - "no-underscore-dangle": "off", - "no-restricted-globals": "off", - "no-unused-vars": "off", - "no-restricted-syntax": "off", - "no-use-before-define": "off", - "no-shadow": "off", - "no-var": "off", - "no-void": "off", - "object-shorthand": "off", - "operator-linebreak": "off", - "prefer-arrow-callback": "off", - "prefer-destructuring": "off", - "prefer-regex-literals": "off", - "prefer-template": "off", - "quotes": ["error", "double", { "avoidEscape": true }], - "strict": "off", - "vars-on-top": "off" - } -} diff --git a/src/Exceptionless.Web/ClientApp.angular/.npmignore b/src/Exceptionless.Web/ClientApp.angular/.npmignore deleted file mode 100644 index 1b89ac3926..0000000000 --- a/src/Exceptionless.Web/ClientApp.angular/.npmignore +++ /dev/null @@ -1,6 +0,0 @@ -temp/ -dist/ -node_modules/ -_SpecRunner.html -.DS_Store -test-results.xml \ No newline at end of file diff --git a/src/Exceptionless.Web/ClientApp.angular/.npmrc b/src/Exceptionless.Web/ClientApp.angular/.npmrc deleted file mode 100644 index 7253a5ceee..0000000000 --- a/src/Exceptionless.Web/ClientApp.angular/.npmrc +++ /dev/null @@ -1 +0,0 @@ -min-release-age=7 diff --git a/src/Exceptionless.Web/ClientApp.angular/.prettierignore b/src/Exceptionless.Web/ClientApp.angular/.prettierignore deleted file mode 100644 index b267391279..0000000000 --- a/src/Exceptionless.Web/ClientApp.angular/.prettierignore +++ /dev/null @@ -1,2 +0,0 @@ -dist -img diff --git a/src/Exceptionless.Web/ClientApp.angular/.vscode/launch.json b/src/Exceptionless.Web/ClientApp.angular/.vscode/launch.json deleted file mode 100644 index 830ef68fb5..0000000000 --- a/src/Exceptionless.Web/ClientApp.angular/.vscode/launch.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - // Use IntelliSense to learn about possible attributes. - // Hover to view descriptions of existing attributes. - // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 - "version": "0.2.0", - "configurations": [ - { - "type": "chrome", - "request": "attach", - "port": 5100, - "webRoot": "${workspaceFolder}", - "name": "grunt:serve", - "preLaunchTask": "npm run serve" - }, - { - "type": "chrome", - "request": "attach", - "port": 5100, - "webRoot": "${workspaceFolder}", - "name": "grunt:serve (use exceptionless api)", - "preLaunchTask": "npm run serve (use exceptionless api)" - } - ] -} diff --git a/src/Exceptionless.Web/ClientApp.angular/.vscode/tasks.json b/src/Exceptionless.Web/ClientApp.angular/.vscode/tasks.json deleted file mode 100644 index 085a17e3d1..0000000000 --- a/src/Exceptionless.Web/ClientApp.angular/.vscode/tasks.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "version": "2.0.0", - "tasks": [ - { - "type": "npm", - "label": "npm run build", - "script": "build", - "group": { - "kind": "build", - "isDefault": true - }, - "problemMatcher": ["$lessCompile", "$eslint"] - }, - { - "type": "npm", - "label": "npm run serve", - "script": "serve", - "problemMatcher": [] - }, - { - "type": "npm", - "label": "npm run serve (use exceptionless api)", - "script": "prod-serve", - "problemMatcher": [] - }, - { - "type": "npm", - "label": "npm install", - "script": "install", - "problemMatcher": [] - } - ] -} diff --git a/src/Exceptionless.Web/ClientApp.angular/AGENTS.md b/src/Exceptionless.Web/ClientApp.angular/AGENTS.md deleted file mode 100644 index 6347bd30e6..0000000000 --- a/src/Exceptionless.Web/ClientApp.angular/AGENTS.md +++ /dev/null @@ -1,30 +0,0 @@ -# Legacy Angular ClientApp - -This directory is the legacy AngularJS frontend. Treat it as maintenance-only. - -Only edit this app when the user explicitly asks for Angular/legacy UI work or the bug exists only in the legacy UI. New frontend development belongs in `../ClientApp`. - -## Tooling - -Run commands from this directory. - -| Task | Command | -| --- | --- | -| Install deps | `npm ci` | -| Build | `npm run build` | -| Serve | `npm run serve` | - -## App Rules - -- Keep changes narrow and consistent with the existing AngularJS, Grunt, Less, and template patterns. -- Do not port new Svelte architecture, shadcn-svelte components, TanStack Query/Form, or Svelte routing patterns into this app. -- Preserve existing routes, translation keys, assets, and public behavior unless the user explicitly approves a change. -- If the same change could be made in either frontend, stop and confirm scope before touching Angular. - -## Local URLs - -- Legacy Angular app: `https://angular-ex.dev.localhost:7121` -- API health: `https://api-ex.dev.localhost:7111/api/v2/about` -- API health fallback for command-line tools with local TLS issues: `http://api-ex.dev.localhost:7110/api/v2/about` - -Browser automation and smoke tests must target local URLs unless the user explicitly provides an external URL and asks to use it. diff --git a/src/Exceptionless.Web/ClientApp.angular/Gruntfile.js b/src/Exceptionless.Web/ClientApp.angular/Gruntfile.js deleted file mode 100644 index 1855753c41..0000000000 --- a/src/Exceptionless.Web/ClientApp.angular/Gruntfile.js +++ /dev/null @@ -1,91 +0,0 @@ -/* jslint node: true */ - -"use strict"; - -// Using exclusion patterns slows down Grunt significantly -// instead of creating a set of patterns like '**/*.js' and '!**/node_modules/**' -// this method is used to create a set of inclusive patterns for all subdirectories -// skipping node_modules, dist, and any .dirs -// This enables users to create any directory structure they desire. -var createFolderGlobs = function (fileTypePatterns) { - fileTypePatterns = Array.isArray(fileTypePatterns) ? fileTypePatterns : [fileTypePatterns]; - var ignore = ["node_modules", "dist", "temp", "grunt"]; - var fs = require("fs"); - return fs - .readdirSync(process.cwd()) - .map(function (file) { - if (ignore.indexOf(file) !== -1 || file.indexOf(".") === 0 || !fs.lstatSync(file).isDirectory()) { - return null; - } - return fileTypePatterns.map(function (pattern) { - return file + "/**/" + pattern; - }); - }) - .filter(function (patterns) { - return patterns; - }) - .concat(fileTypePatterns); -}; - -module.exports = function (grunt) { - var path = require("path"); - - // load createFolderGlobs in options so it can be used across task configurations - grunt.option("folderGlobs", createFolderGlobs); - - // load all task configurations - require("load-grunt-config")(grunt, { - configPath: path.join(process.cwd(), "grunt/task-configs"), - init: true, - loadGruntTasks: { - pattern: "grunt-*", - config: require("./package.json"), - scope: "devDependencies", - }, - }); - - grunt.registerTask("build", [ - "eslint", - /* 'htmlangular', */ "clean:before", - "less", - "dom_munger", - "ngtemplates", - "cssmin", - "concat", - "ngAnnotate", - "uglify", - "copy", - "htmlmin", - "replace", - "cacheBust", - "clean:after", - ]); - grunt.registerTask("default", ["build"]); - grunt.registerTask("serve", ["dom_munger:read", "eslint", "configureProxies:main", "connect", "watch"]); - - grunt.event.on("watch", function (action, filepath) { - // https://github.com/gruntjs/grunt-contrib-watch/issues/156 - - var tasksToRun = []; - - if (filepath.lastIndexOf(".html") !== -1 && filepath.lastIndexOf(".html") === filepath.length - 5) { - // validate the changed html file - grunt.config("htmlangular.main.files.src", [filepath]); - tasksToRun.push("htmlangular"); - } - - if (filepath.lastIndexOf(".js") !== -1 && filepath.lastIndexOf(".js") === filepath.length - 3) { - // lint the changed js file - grunt.config("eslint.main.src", filepath); - tasksToRun.push("eslint"); - } - - // if index.html changed, we need to reread the ' }, - { - selector: "body", - html: '', - }, - { selector: "head", html: '' }, - { selector: "head", html: '' }, - ], - }, - src: "index.html", - dest: "dist/index.html", - }, -}; diff --git a/src/Exceptionless.Web/ClientApp.angular/grunt/task-configs/eslint.js b/src/Exceptionless.Web/ClientApp.angular/grunt/task-configs/eslint.js deleted file mode 100644 index c31d6c01f9..0000000000 --- a/src/Exceptionless.Web/ClientApp.angular/grunt/task-configs/eslint.js +++ /dev/null @@ -1,8 +0,0 @@ -module.exports = function (grunt) { - return { - main: { - options: {}, - src: grunt.option("folderGlobs")("*.js"), - }, - }; -}; diff --git a/src/Exceptionless.Web/ClientApp.angular/grunt/task-configs/htmlangular.js b/src/Exceptionless.Web/ClientApp.angular/grunt/task-configs/htmlangular.js deleted file mode 100644 index 25df4c1be2..0000000000 --- a/src/Exceptionless.Web/ClientApp.angular/grunt/task-configs/htmlangular.js +++ /dev/null @@ -1,61 +0,0 @@ -module.exports = { - options: { - customtags: [ - "accordion", - "date-*", - "events", - "extended-data-item", - "intercom", - "object-dump", - "organization-notifications", - "progressbar", - "project-filter", - "projects", - "rate-limit", - "rickshaw", - "search-filter", - "simple-stack-trace", - "stacks", - "stack-trace", - "summary", - "timeago", - "toaster-container", - ], - customattrs: [ - "auto-active", - "autocapitalize", - "autocorrect", - "autoscroll", - "checklist-*", - "clip-*", - "dropdown", - "dropdown-*", - "email-address-available-validator", - "gravatar-*", - "is-*", - "lines", - "match", - "payments-*", - "project-name-available-validator", - "refresh-*", - "search-filter-validator", - "typeahead", - "truncate", - "ui-*", - "x-autocompletetype", - ], - tmplext: "tpl.html", - reportpath: null, - relaxerror: [ - "A table row was 8 columns wide and exceeded the column count established by the first row (5).", - "Attribute href without an explicit value seen. The attribute may be dropped by IE7.", - "Element img is missing required attribute src.", - "Element tabset not allowed as child of element div in this context.", - "Element div not allowed as child of element pre in this context.", - "Table columns in range 7…8 established by element td have no cells beginning in them.", - ], - }, - files: { - src: ["index.html", "app/**/*.html", "components/**/*.html"], - }, -}; diff --git a/src/Exceptionless.Web/ClientApp.angular/grunt/task-configs/htmlmin.js b/src/Exceptionless.Web/ClientApp.angular/grunt/task-configs/htmlmin.js deleted file mode 100644 index ffdda4e278..0000000000 --- a/src/Exceptionless.Web/ClientApp.angular/grunt/task-configs/htmlmin.js +++ /dev/null @@ -1,17 +0,0 @@ -module.exports = { - main: { - options: { - collapseBooleanAttributes: true, - collapseWhitespace: true, - conservativeCollapse: true, - removeAttributeQuotes: false, - removeComments: true, - removeEmptyAttributes: true, - removeScriptTypeAttributes: true, - removeStyleLinkTypeAttributes: true, - }, - files: { - "dist/index.html": "dist/index.html", - }, - }, -}; diff --git a/src/Exceptionless.Web/ClientApp.angular/grunt/task-configs/less.js b/src/Exceptionless.Web/ClientApp.angular/grunt/task-configs/less.js deleted file mode 100644 index c3f256f529..0000000000 --- a/src/Exceptionless.Web/ClientApp.angular/grunt/task-configs/less.js +++ /dev/null @@ -1,8 +0,0 @@ -module.exports = { - main: { - options: {}, - files: { - "temp/app.css": "app.less", - }, - }, -}; diff --git a/src/Exceptionless.Web/ClientApp.angular/grunt/task-configs/ngAnnotate.js b/src/Exceptionless.Web/ClientApp.angular/grunt/task-configs/ngAnnotate.js deleted file mode 100644 index 6583e3f2ae..0000000000 --- a/src/Exceptionless.Web/ClientApp.angular/grunt/task-configs/ngAnnotate.js +++ /dev/null @@ -1,10 +0,0 @@ -module.exports = { - options: { - singleQuotes: true, - }, - main: { - files: { - "dist/app.js": ["dist/app.js"], - }, - }, -}; diff --git a/src/Exceptionless.Web/ClientApp.angular/grunt/task-configs/ngtemplates.js b/src/Exceptionless.Web/ClientApp.angular/grunt/task-configs/ngtemplates.js deleted file mode 100644 index 0e0edf3780..0000000000 --- a/src/Exceptionless.Web/ClientApp.angular/grunt/task-configs/ngtemplates.js +++ /dev/null @@ -1,12 +0,0 @@ -module.exports = function (grunt) { - return { - main: { - options: { - module: "app", - htmlmin: "<%= htmlmin.main.options %>", - }, - src: [grunt.option("folderGlobs")("*.html"), "!index.html", "!_SpecRunner.html"], - dest: "temp/templates.js", - }, - }; -}; diff --git a/src/Exceptionless.Web/ClientApp.angular/grunt/task-configs/replace.js b/src/Exceptionless.Web/ClientApp.angular/grunt/task-configs/replace.js deleted file mode 100644 index 8913a309ca..0000000000 --- a/src/Exceptionless.Web/ClientApp.angular/grunt/task-configs/replace.js +++ /dev/null @@ -1,21 +0,0 @@ -/* jslint node: true */ -module.exports = { - main: { - options: { - patterns: [ - { - match: "version", - replacement: process.env.VERSION || "8.0.0-dev", - }, - ], - }, - files: [ - { - expand: true, - flatten: true, - src: ["dist/app.*.js"], - dest: "dist/", - }, - ], - }, -}; diff --git a/src/Exceptionless.Web/ClientApp.angular/grunt/task-configs/uglify.js b/src/Exceptionless.Web/ClientApp.angular/grunt/task-configs/uglify.js deleted file mode 100644 index d07256bae0..0000000000 --- a/src/Exceptionless.Web/ClientApp.angular/grunt/task-configs/uglify.js +++ /dev/null @@ -1,21 +0,0 @@ -module.exports = { - options: { - sourceMap: true, - sourceMapIncludeSources: false, - mangle: { - reserved: ["$super"], - }, - }, - main: { - src: "dist/app.js", - dest: "dist/app.min.js", - }, - vendor: { - src: [ - "node_modules/jquery/dist/jquery.min.js", - "node_modules/bootstrap/dist/js/bootstrap.min.js", - "node_modules/lodash/lodash.min.js", - ], - dest: "dist/vendor.min.js", - }, -}; diff --git a/src/Exceptionless.Web/ClientApp.angular/grunt/task-configs/watch.js b/src/Exceptionless.Web/ClientApp.angular/grunt/task-configs/watch.js deleted file mode 100644 index 722d21ef9b..0000000000 --- a/src/Exceptionless.Web/ClientApp.angular/grunt/task-configs/watch.js +++ /dev/null @@ -1,15 +0,0 @@ -module.exports = function (grunt) { - var liveReloadPort = Number(process.env.LIVERELOAD_PORT) || 35729; - - return { - main: { - options: { - livereload: liveReloadPort, - livereloadOnError: false, - spawn: false, - }, - files: [grunt.option("folderGlobs")(["*.js", "*.less", "*.html"]), "!_SpecRunner.html", "!.grunt"], - tasks: [], // all the tasks are run dynamically during the watch event handler - }, - }; -}; diff --git a/src/Exceptionless.Web/ClientApp.angular/img/cards/amex.png b/src/Exceptionless.Web/ClientApp.angular/img/cards/amex.png deleted file mode 100644 index 7de472cdd9..0000000000 Binary files a/src/Exceptionless.Web/ClientApp.angular/img/cards/amex.png and /dev/null differ diff --git a/src/Exceptionless.Web/ClientApp.angular/img/cards/dinersclub.png b/src/Exceptionless.Web/ClientApp.angular/img/cards/dinersclub.png deleted file mode 100644 index 93b909d1b3..0000000000 Binary files a/src/Exceptionless.Web/ClientApp.angular/img/cards/dinersclub.png and /dev/null differ diff --git a/src/Exceptionless.Web/ClientApp.angular/img/cards/discover.png b/src/Exceptionless.Web/ClientApp.angular/img/cards/discover.png deleted file mode 100644 index 692bd76d40..0000000000 Binary files a/src/Exceptionless.Web/ClientApp.angular/img/cards/discover.png and /dev/null differ diff --git a/src/Exceptionless.Web/ClientApp.angular/img/cards/jcb.png b/src/Exceptionless.Web/ClientApp.angular/img/cards/jcb.png deleted file mode 100644 index 4160175eb5..0000000000 Binary files a/src/Exceptionless.Web/ClientApp.angular/img/cards/jcb.png and /dev/null differ diff --git a/src/Exceptionless.Web/ClientApp.angular/img/cards/mastercard.png b/src/Exceptionless.Web/ClientApp.angular/img/cards/mastercard.png deleted file mode 100644 index b51a920d15..0000000000 Binary files a/src/Exceptionless.Web/ClientApp.angular/img/cards/mastercard.png and /dev/null differ diff --git a/src/Exceptionless.Web/ClientApp.angular/img/cards/placeholder.png b/src/Exceptionless.Web/ClientApp.angular/img/cards/placeholder.png deleted file mode 100644 index 65d5607436..0000000000 Binary files a/src/Exceptionless.Web/ClientApp.angular/img/cards/placeholder.png and /dev/null differ diff --git a/src/Exceptionless.Web/ClientApp.angular/img/cards/unionpay.png b/src/Exceptionless.Web/ClientApp.angular/img/cards/unionpay.png deleted file mode 100644 index f057cf2b72..0000000000 Binary files a/src/Exceptionless.Web/ClientApp.angular/img/cards/unionpay.png and /dev/null differ diff --git a/src/Exceptionless.Web/ClientApp.angular/img/cards/visa.png b/src/Exceptionless.Web/ClientApp.angular/img/cards/visa.png deleted file mode 100644 index 0358fbf00b..0000000000 Binary files a/src/Exceptionless.Web/ClientApp.angular/img/cards/visa.png and /dev/null differ diff --git a/src/Exceptionless.Web/ClientApp.angular/img/exceptionless-32.png b/src/Exceptionless.Web/ClientApp.angular/img/exceptionless-32.png deleted file mode 100644 index bdceed6162..0000000000 Binary files a/src/Exceptionless.Web/ClientApp.angular/img/exceptionless-32.png and /dev/null differ diff --git a/src/Exceptionless.Web/ClientApp.angular/img/exceptionless-350.png b/src/Exceptionless.Web/ClientApp.angular/img/exceptionless-350.png deleted file mode 100644 index 1a5ad9a10a..0000000000 Binary files a/src/Exceptionless.Web/ClientApp.angular/img/exceptionless-350.png and /dev/null differ diff --git a/src/Exceptionless.Web/ClientApp.angular/img/exceptionless.png b/src/Exceptionless.Web/ClientApp.angular/img/exceptionless.png deleted file mode 100644 index bc1eee74f9..0000000000 Binary files a/src/Exceptionless.Web/ClientApp.angular/img/exceptionless.png and /dev/null differ diff --git a/src/Exceptionless.Web/ClientApp.angular/img/facebook.png b/src/Exceptionless.Web/ClientApp.angular/img/facebook.png deleted file mode 100644 index 9e10c426b0..0000000000 Binary files a/src/Exceptionless.Web/ClientApp.angular/img/facebook.png and /dev/null differ diff --git a/src/Exceptionless.Web/ClientApp.angular/img/github.png b/src/Exceptionless.Web/ClientApp.angular/img/github.png deleted file mode 100644 index c041d6cfc1..0000000000 Binary files a/src/Exceptionless.Web/ClientApp.angular/img/github.png and /dev/null differ diff --git a/src/Exceptionless.Web/ClientApp.angular/img/google.png b/src/Exceptionless.Web/ClientApp.angular/img/google.png deleted file mode 100644 index 61eed1cdbc..0000000000 Binary files a/src/Exceptionless.Web/ClientApp.angular/img/google.png and /dev/null differ diff --git a/src/Exceptionless.Web/ClientApp.angular/img/icons/events.ico b/src/Exceptionless.Web/ClientApp.angular/img/icons/events.ico deleted file mode 100644 index 3db0b1a898..0000000000 Binary files a/src/Exceptionless.Web/ClientApp.angular/img/icons/events.ico and /dev/null differ diff --git a/src/Exceptionless.Web/ClientApp.angular/img/icons/frequent.ico b/src/Exceptionless.Web/ClientApp.angular/img/icons/frequent.ico deleted file mode 100644 index b04b11a5d3..0000000000 Binary files a/src/Exceptionless.Web/ClientApp.angular/img/icons/frequent.ico and /dev/null differ diff --git a/src/Exceptionless.Web/ClientApp.angular/img/icons/new.ico b/src/Exceptionless.Web/ClientApp.angular/img/icons/new.ico deleted file mode 100644 index dbd65e798a..0000000000 Binary files a/src/Exceptionless.Web/ClientApp.angular/img/icons/new.ico and /dev/null differ diff --git a/src/Exceptionless.Web/ClientApp.angular/img/microsoft.png b/src/Exceptionless.Web/ClientApp.angular/img/microsoft.png deleted file mode 100644 index c8befa0d2a..0000000000 Binary files a/src/Exceptionless.Web/ClientApp.angular/img/microsoft.png and /dev/null differ diff --git a/src/Exceptionless.Web/ClientApp.angular/index.html b/src/Exceptionless.Web/ClientApp.angular/index.html deleted file mode 100644 index b58ed89ed0..0000000000 --- a/src/Exceptionless.Web/ClientApp.angular/index.html +++ /dev/null @@ -1,294 +0,0 @@ - - - - Exceptionless - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/Exceptionless.Web/ClientApp.angular/lang/en-us.json b/src/Exceptionless.Web/ClientApp.angular/lang/en-us.json deleted file mode 100644 index 9686358500..0000000000 --- a/src/Exceptionless.Web/ClientApp.angular/lang/en-us.json +++ /dev/null @@ -1,707 +0,0 @@ -{ - "Change Plan": "Change Plan", - "Change Password": "Change Password", - "Logout": "Logout", - "Exceptions": "Exceptions", - "Log Messages": "Log Messages", - "Broken Links": "Broken Links", - "Feature Usages": "Feature Usages", - "All": "All", - "Reports": "Reports", - "Exception Events": "Events", - "Message Events": "Events", - "Broken Links Events": "Events", - "Feature Events": "Events", - "Event Events": "Events", - "Most Frequent Exceptions": "Most Frequent", - "Most Frequent Messages": "Most Frequent", - "Most Frequent Links": "Most Frequent", - "Most Frequent Features": "Most Frequent", - "Most Frequent": "Most Frequent", - "Most Frequent Stacks": "Most Frequent Stacks", - "Most Users": "Most Users", - "Most Users Stacks": "Most Users Stacks", - "New Exception": "New Stacks", - "New LogMessage": "New Stacks", - "New BrokenLink": "New Stacks", - "New FeatureUsage": "New Stacks", - "New Event": "New", - "Sessions": "Sessions", - "Project Manager": "Projects", - "Organization Manager": "Organizations", - "My Account": "My Account", - "Documentation": "Documentation", - "Support": "Support", - "New Stacks": "New Stacks", - "Events Per Hour": "Events Per Hour", - "History": "History", - "Session Events": "Session Events", - "Session": "Session", - "Session End": "Session End", - "Session Heartbeat": "Session Heartbeat", - "Feature": "Feature", - "Go To Documentation": "Go To Documentation", - "Focus Search Bar": "Focus Search Bar", - "Go To My Account": "Go To My Account", - "Go To Notifications": "Go To Notifications", - "Go To Organizations": "Go To Organizations", - "Go To Projects": "Go To Projects", - "Go To GitHub project": "Go To GitHub project", - "Go to public Discord channel": "Go to public Discord channel", - "Chat with Support": "Chat with Support", - "Your user account was deleted. Please create a new account.": "Your user account was deleted. Please create a new account.", - "Billing is currently disabled.": "Billing is currently disabled.", - - "Page Not Found": "Page Not Found", - "We're sorry but the page was not found on this site.": "We're sorry but the page was not found on this site.", - - "Service Status": "Service Status", - "We're sorry but the website is currently undergoing maintenance.": "We're sorry but the website is currently undergoing maintenance.", - "You'll be automatically redirected when the maintenance is completed.": "You'll be automatically redirected when the maintenance is completed.", - - "Please enter a Reference Link": "Please enter a Reference Link", - "Reference Link": "Reference Link", - "Please enter a valid URL": "Please enter a valid URL", - "Reference Link is required.": "Reference Link is required.", - "Save Reference Link": "Save Reference Link", - "UserRatio": "Users", - "First": "First", - "Last": "Last", - "Stack": "Stack", - "Options": "Options", - "Hide": "Hide", - "Future Occurrences Are Critical": "Future Occurrences Are Critical", - "Disable Notifications": "Disable Notifications", - "Promote To External": "Promote To External", - "Add Reference Link": "Add Reference Link", - "Title": "Title", - "Project": "Project", - "Date Fixed": "Date Fixed", - "Description": "Description", - "Tags": "Tags", - "Stacking Information": "Stacking Information", - "ExceptionType": "ExceptionType", - "Method": "Method", - "Chart Options": "Chart Options", - "Show Average Value": "Show Average Value", - "Average Value": "Average Value", - "Show Value Sum": "Show Value Sum", - "Value Sum": "Value Sum", - "Recent Occurrences": "Recent Occurrences", - "Reference Links": "Reference Links", - "Mark Fixed": "Mark Fixed", - "No stacks were found with the current filter.": "No stacks were found with the current filter.", - "No stacks were found.": "No stacks were found.", - "Future Stack Occurrences are Not Critical": "Future Stack Occurrences are Not Critical", - "Future Stack Occurrences are Critical": "Future Stack Occurrences are Critical", - "Enable Stack Notifications": "Enable Stack Notifications", - "Disable Stack Notifications": "Disable Stack Notifications", - "Promote Stack To External": "Promote Stack To External", - "Add Stack Reference Link": "Add Stack Reference Link", - "Delete Stack": "Delete Stack", - "An error occurred while adding the reference link.": "An error occurred while adding the reference link.", - "Successfully promoted stack!": "Successfully promoted stack!", - "Manage Integrations": "Manage Integrations", - "An error occurred while promoting this stack.": "An error occurred while promoting this stack.", - "Are you sure you want to delete this reference link?": "Are you sure you want to delete this reference link?", - "An error occurred while deleting the external reference link.": "An error occurred while deleting the external reference link.", - "Are you sure you want to delete this stack (includes all stack events)?": "Are you sure you want to delete this stack (includes all stack events)?", - "Successfully queued the stack for deletion.": "Successfully queued the stack for deletion.", - "An error occurred while deleting this stack.": "An error occurred while deleting this stack.", - "The average of all event values": "The average of all event values", - "The sum of all event values": "The sum of all event values", - "Hide this stack from reports and mutes occurrence notifications": "Hide this stack from reports and mutes occurrence notifications", - "All future occurrences will be marked as critical": "All future occurrences will be marked as critical", - "Stop sending occurrence notifications for this stack": "Stop sending occurrence notifications for this stack", - "Used to promote stacks to external systems.": "Used to promote stacks to external systems.", - "Add a reference link to an external resource.": "Add a reference link to an external resource.", - "Delete this stack": "Delete this stack", - - "Summary": "Summary", - "Created": "Created", - "Duration": "Duration", - "Loading...": "Loading...", - "Users": "Users", - "Average Duration": "Average Duration", - "Sessions and Users": "Sessions and Users", - "View Active": "View Active", - "Recent Sessions": "Recent Sessions", - "No sessions were found with the current filter.": "No sessions were found with the current filter.", - "No sessions were found.": "No sessions were found.", - - "Occurred On": "Occurred On", - "ended": "ended", - "Reference": "Reference", - "Level": "Level", - "Event Type": "Event Type", - "Error Type": "Error Type", - "Source": "Source", - "Value": "Value", - "Message": "Message", - "Version": "Version", - "Geo": "Geo", - "URL": "URL", - "User Info": "User Info", - "User Email": "User Email", - "User Identity": "User Identity", - "User Name": "User Name", - "User Description": "User Description", - "Stack Trace": "Stack Trace", - "Machine Name": "Machine Name", - "IP Address": "IP Address", - "Processor Count": "Processor Count", - "Total Memory": "Total Memory", - "Available Memory": "Available Memory", - "Process Memory": "Process Memory", - "OS Name": "OS Name", - "OS Version": "OS Version", - "Architecture": "Architecture", - "Runtime Version": "Runtime Version", - "Process ID": "Process ID", - "Process Name": "Process Name", - "Command Line": "Command Line", - "Additional Data": "Additional Data", - "Code": "Code", - "Submission Method": "Submission Method", - "Loaded Modules": "Loaded Modules", - "Module Name": "Name", - "HTTP Method": "HTTP Method", - "Referrer": "Referrer", - "Client IP": "Client IP", - "User Agent": "User Agent", - "Device": "Device", - "Browser": "Browser", - "Browser OS": "Browser OS", - "Post Data": "Post Data", - "Event Occurrence": "Event Occurrence", - "Go To Stack": "Go To Stack", - "Previous Occurrence": "Previous Occurrence", - "Next Occurrence": "Next Occurrence", - "Toggle View": "Toggle View", - "Promote to Tab": "Promote to Tab", - "Demote Tab": "Demote Tab", - "References": "References", - "Overview": "Overview", - "Exception": "Exception", - "Environment": "Environment", - "Extended Data": "Extended Data", - "Request": "Request", - "to enable sessions and other premium features!": "to enable sessions and other premium features!", - "is attempting to use a premium feature.": "is attempting to use a premium feature.", - "View JSON": "View JSON", - "Copy Stack Trace to Clipboard": "Copy Stack Trace to Clipboard", - "Copy Event JSON to Clipboard": "Copy Event JSON to Clipboard", - "Copy to Clipboard": "Copy to Clipboard", - "Cookie Values": "Cookie Values", - "Headers": "Headers", - "No events were found with the current filter.": "No events were found with the current filter.", - "No events were found.": "No events were found.", - "An error occurred promoting tab.": "An error occurred promoting tab.", - "Cannot_Find_Event": "The event \"{{eventId}}\" could not be found.", - "Online": "Online", - "Seconds": "{{duration}} seconds", - - "User": "User", - "Bulk Action": "Bulk Action", - "Mark Hidden": "Mark Hidden", - "Sort Descending": "Sort Descending", - "Sort Ascending": "Sort Ascending", - "Date": "Date", - "Please select one or more events": "Please select one or more events", - "Successfully queued the events for deletion.": "Successfully queued the events for deletion.", - "An error occurred while deleting the events.": "An error occurred while deleting the events.", - "Are you sure you want to delete these events?": "Are you sure you want to delete these events?", - "DELETE EVENTS": "DELETE EVENTS", - - "Are you sure you want to delete these stacks (includes all stack events)?": "Are you sure you want to delete these stacks (includes all stack events)?", - "DELETE STACKS": "DELETE STACKS", - "An error occurred while deleting the stacks.": "An error occurred while deleting the stacks.", - "Successfully marked the stacks as fixed.": "Successfully marked the stacks as fixed.", - "An error occurred while marking stacks as fixed.": "An error occurred while marking stacks as fixed.", - "Successfully queued the stacks to be marked as hidden.": "Successfully queued the stacks to be marked as hidden.", - "An error occurred while marking stacks as hidden.": "An error occurred while marking stacks as hidden.", - "Please select one or more stacks": "Please select one or more stacks", - "Marks the stack as fixed. This will also prevent error occurrences from being displayed in the dashboard.": "Marks the stack as fixed. This will also prevent error occurrences from being displayed in the dashboard.", - "Optional:": "Optional:", - "Please enter the version in which the stack has been fixed. Any submitted occurrences with a lower version will not cause a regression.": "Please enter the version in which the stack has been fixed. Any submitted occurrences with a lower version will not cause a regression.", - "Versioning Documentation": "Versioning Documentation", - "Optional Version (Example: 1.2.3)": "Optional Version (Example: 1.2.3)", - "Invalid version.": "Invalid version.", - "Mark this stack as open": "Mark this stack as open", - "Mark this stack as fixed": "Mark this stack as fixed", - "Marking Not Fixed": "Marking Not Fixed", - "Marking Fixed": "Marking Fixed", - "Successfully queued the stacks for deletion.": "Successfully queued the stacks for deletion.", - "Stack_Deleted": "The stack \"{{stackId}}\" was deleted.", - "Cannot_Find_Stack": "The stack \"{{stackId}}\" could not be found.", - "Error_Load_Stack": "An error occurred while loading the stack \"{{stackId}}\".", - "An error occurred while marking this stack as open.": "An error occurred while marking this stack as open.", - "An error occurred while marking this stack as discarded.": "An error occurred while marking this stack as discarded.", - "Are you sure you want to all current stack events and discard any future stack events?": "Are you sure you want to all current stack events and discard any future stack events?", - "All future occurrences will be discarded and will not count against your event limit.": "All future occurrences will be discarded and will not count against your event limit.", - "An error occurred while marking future occurrences as not critical.": "An error occurred while marking future occurrences as not critical.", - "An error occurred while marking future occurrences as critical.": "An error occurred while marking future occurrences as critical.", - "Successfully marked the stack as open.": "Successfully marked the stack as open.", - "Successfully marked the stack as fixed.": "Successfully marked the stack as fixed.", - "Successfully marked the stacks as ignored.": "Successfully marked the stacks as ignored.", - "An error occurred while marking this stack as fixed.": "An error occurred while marking this stack as fixed.", - "An error occurred while marking this stack as snoozed.": "An error occurred while marking this stack as snoozed.", - "An error occurred while marking this stack as ignored.": "An error occurred while marking this stack as ignored.", - "An error occurred while marking stacks as ignored.": "An error occurred while marking stacks as ignored.", - "Successfully marked the stacks as open.": "Successfully marked the stacks as open.", - "An error occurred while marking stacks as open.": "An error occurred while marking stacks as open.", - "Successfully marked the stacks as discarded.": "Successfully marked the stacks as discarded.", - "An error occurred while marking stacks as discarded.": "An error occurred while marking stacks as discarded.", - - "Mark Open": "Mark Open", - "Mark Stack Open": "Mark Stack Open", - "Mark Stack Discarded": "Mark Stack Discarded", - "Mark Stack Fixed": "Mark Stack Fixed", - - "Fixed_With_Version": "FIXED IN {{fixed_in_version}}", - "Opening": "Opening", - "Discard": "Discard", - - "open": "Open", - "discarded": "Discarded", - "fixed": "Fixed", - "regressed": "Regressed", - "snoozed": "Snoozed", - "ignored": "Ignored", - "6 Hours": "6 Hours", - "1 Day": "1 Day", - "1 Week": "1 Week", - "1 Month": "1 Month", - - "All Projects": "All Projects", - "Loading Organizations...": "Loading Organizations...", - "Loading Projects...": "Loading Projects...", - "Add New Project": "Add New Project", - - "Last Hour": "Last Hour", - "Last 24 Hours": "Last 24 Hours", - "Last Week": "Last Week", - "Last 30 Days": "Last 30 Days", - "All Time": "All Time", - "Custom": "Custom", - "Select Date Range": "Select Date Range", - "First In Date Range": "First In Date Range", - "First Ever": "First Ever", - "Last In Date Range": "Last In Date Range", - "Last Ever": "Last Ever", - - "DayFormat": "D", - "DateFormat": "ddd, MMM D, YYYY", - "DateTimeFormat": "ddd, MMM D, YYYY h:mma", - "Filter": "Filter", - "Search Documentation": "Search Documentation", - - "My Projects": "My Projects", - "Project Information": "Project Information", - "Organization Name": "Organization Name", - "New Organization Name": "New Organization Name", - "Project Name": "Project Name", - "New Project Name": "New Project Name", - "Add Project": "Add Project", - "Manage Project": "Manage Project", - "General": "General", - "API Keys": "API Keys", - "Settings": "Settings", - "Client Configuration": "Client Configuration", - "Integrations": "Integrations", - "Download & Configure Client": "Download & Configure Client", - "Manage Organization": "Manage Organization", - "Manage Notification Settings": "Manage Notification Settings", - "Reset Project Data": "Reset Project Data", - "Delete Project": "Delete Project", - "API Key": "API Key", - "Enable": "Enable", - "Disable": "Disable", - "Are you sure you want to enable the API key?": "Are you sure you want to enable the API key?", - "ENABLE API KEY": "ENABLE API KEY", - "An error occurred while enabling the API key.": "An error occurred while enabling the API key.", - "Are you sure you want to disable the API key?": "Are you sure you want to disable the API key?", - "DISABLE API KEY": "DISABLE API KEY", - "An error occurred while disabling the API key.": "An error occurred while disabling the API key.", - "Notes": "Notes", - "Payment Number": "Payment Number", - "Actions": "Actions", - "Status": "Status", - "This project does not have an API Key.": "This project does not have an API Key.", - "New API Key": "New API Key", - "New Client Configuration": "New Client Configuration", - "Event Types": "Event Types", - "Add Web Hook": "Add Web Hook", - "Connect Zapier": "Connect Zapier", - "Data Exclusions": "Data Exclusions", - "User Namespaces": "User Namespaces", - "Error Stacking": "Error Stacking", - "Common Methods": "Common Methods", - "Spam Detection": "Spam Detection", - "Please enter a configuration setting": "Please enter a configuration setting", - "Name": "Name", - "Organization": "Organization", - "Stacks": "Stacks", - "Events": "Events", - "View Organization": "View Organization", - "Key": "Key", - "Project Name is required.": "Project Name is required.", - "A project with this name already exists.": "A project with this name already exists.", - "This project does not contain any custom configuration settings.": "This project does not contain any custom configuration settings.", - "NOTICE_CONFIGURATION": "The configuration values on this page will be sent to the Exceptionless clients in real time. This allows you to change how your app works without redeploying your app.", - "No projects were found with the current filter.": "No projects were found with the current filter.", - "No projects were found.": "No projects were found.", - "Notice_Data_Exclusions": "A comma delimited list of field names that should be removed from any error report data (e.g., extended data properties, form fields, cookies and query parameters). You can also specify a field name with wildcards (*) to specify starts with, ends with, or contains just to be extra safe.", - "Automatically remove user identifiable information from events (e.g., Machine Name, User Information, IP Addresses and more...).": "Automatically remove user identifiable information from events (e.g., Machine Name, User Information, IP Addresses and more...).", - "Notice_Error_Stacking": "A comma delimited list of the namespace names that your applications code belongs to. If this value is set, only methods inside of these namespaces will be considered as stacking targets.", - "Notice_Common_Methods": "A comma delimited list of common method names that should not be used as stacking targets. This is useful when your code contains shared utility methods that throw a lot of errors.", - "Notice_Spam_Detection": "A comma delimited list of user agents that should be ignored.", - "Example:": "Example:", - "Reduce noise by automatically hiding high volumes of events coming from a single client IP address.": "Reduce noise by automatically hiding high volumes of events coming from a single client IP address.", - "Please enter a valid key.": "Please enter a valid key.", - "Please enter a valid value.": "Please enter a valid value.", - "Configuration Key": "Configuration Key", - "Configuration Value": "Configuration Value", - "to enable integrations!": "to enable integrations!", - "Choose how often you want to receive slack notifications for event occurrences in this project.": "Choose how often you want to receive slack notifications for event occurrences in this project.", - "(Coming soon!)": "(Coming soon!)", - "Add Slack Notifications": "Add Slack Notifications", - "Remove Slack": "Remove Slack", - "Notice_Web_hooks": "The following web hooks will be called for this project.", - "Notice_Zapier": "Exceptionless has a native Zapier integration. You can use Zapier to connect your Exceptionless account to over 3,000 other applications all without writing any code.", - "This project does not contain any integrations.": "This project does not contain any integrations.", - "An error occurred while loading your projects.": "An error occurred while loading your projects.", - "Are you sure you want to delete this project?": "Are you sure you want to delete this project?", - "Successfully queued the project for deletion.": "Successfully queued the project for deletion.", - "An error occurred while trying to remove the project.": "An error occurred while trying to remove the project.", - "Download_Configure_Project": "Download & Configure Project {{projectName}} Client", - "The Exceptionless client can be integrated into your project in just a few easy steps.": "The Exceptionless client can be integrated into your project in just a few easy steps.", - "Generate Sample Data": "Generate Sample Data", - "Generating Sample Data...": "Generating Sample Data...", - "Sample data generation has been queued. Events will appear shortly.": "Sample data generation has been queued. Events will appear shortly.", - "An error occurred while generating sample data for your project.": "An error occurred while generating sample data for your project.", - "Select your project type:": "Select your project type:", - "Please select a project type": "Please select a project type", - "Execute the following in your shell:": "Execute the following in your shell:", - "Add the script to your HTML page:": "Add the script to your HTML page:", - "Configure the ExceptionlessClient with your Exceptionless API key:": "Configure the ExceptionlessClient with your Exceptionless API key:", - "Notice_Install_Nuget_Package": "Add the Exceptionless NuGet package to your Visual Studio project by running this command in the Package Manager Console:", - "Notice_Install_Manually": "If you wish to install manually or from a CDN please view the readme.", - "Notice_Install_Node_Package": "Install Exceptionless using the Node Package Manager:", - "Notice_Update_ApiKey": "Update the <exceptionless apiKey=\"API_KEY_HERE\" /> section located in the project's {{config}} with your Exceptionless API key:", - "Notice_Application_Startup_NetFx": "Import the Exceptionless namespace and call the ExceptionlessClient.Default.Startup(\"{{apiKey}}\") method with your api key during the startup of your app.", - "Notice_Application_Startup_NetCore": "Import the Exceptionless namespace and call the services.AddExceptionless(\"{{apiKey}}\") method inside of the ConfigureServices method. Next, call the app.UseExceptionless() method in the Configure during the startup of your app.", - "Notice_Application_Startup_Nancy": "Import the Exceptionless namespace and call the Exceptionless.ExceptionlessClient.Default.RegisterNancy(pipelines) method with an instance of Nancy.Bootstrapper.IPipelines inside of your Nancy Bootstrapper's ApplicationStartup method.", - "Notice_Application_Startup_Windows": "Import the Exceptionless namespace and call the ExceptionlessClient.Default.Register() method during the startup of your app.", - "Notice_Application_Startup_WebApi": "Import the Exceptionless namespace and call the ExceptionlessClient.Default.RegisterWebApi(config) method with an instance of HttpConfiguration during the startup of your app.", - "Notice_Hosting_WebApi_In_AspNet": "If you are hosting Web API inside of ASP.NET, you would register Exceptionless like: Exceptionless.ExceptionlessClient.Default.RegisterWebApi(GlobalConfiguration.Configuration)", - "Notice_Unhandled_Exception": "If you are targeting Net Standard or Portable Class Libraries (PCL) you may need to wire up to any unhandled exception events specific to your project type to start reporting them! Please see the Exceptionless docs for more information.", - "That's it!": "That's it!", - "Your project should now automatically be sending all unhandled exceptions to Exceptionless!": "Your project should now automatically be sending all unhandled exceptions to Exceptionless!", - "Notice_Submit_Exception": "You can also send handled exceptions to Exceptionless using {{sampleCode}}.", - "Notice_CommandLine_Application": "For more information and troubleshooting tips, view the Exceptionless docs.", - "Notice_DotNet_Application": "For more information and troubleshooting tips, view the Exceptionless docs.", - "Notice_JavaScript_Application": "For more information and platform specific integrations (E.G., AngularJS, Express.js, React, Vue) view the Exceptionless docs.", - "Copy not supported.": "Copy not supported.", - "Press ⌘-C to copy.": "Press ⌘-C to copy.", - "Press Ctrl-C to copy.": "Press Ctrl-C to copy.", - "An error occurred while getting the API key for your project.": "An error occurred while getting the API key for your project.", - "Console and Service applications": "Console and Service applications", - "Browser applications": "Browser applications", - "Cannot_Find_Project": "The project \"{{projectId}}\" could not be found.", - "An error occurred while creating the organization.": "An error occurred while creating the organization.", - "An error occurred while creating the project.": "An error occurred while creating the project.", - "": "", - "Add to Slack": "Add to Slack", - "Project Most Frequent": "Most Frequent", - "Please upgrade your plan to enable slack integration.": "Please upgrade your plan to enable slack integration.", - "An error occurred while adding Slack to your project.": "An error occurred while adding Slack to your project.", - "An error occurred while creating a new API key for your project.": "An error occurred while creating a new API key for your project.", - "An error occurred while saving the configuration setting.": "An error occurred while saving the configuration setting.", - "An error occurred loading the api keys.": "An error occurred loading the api keys.", - "An error occurred loading the notification settings.": "An error occurred loading the notification settings.", - "An error occurred while loading the slack notification settings.": "An error occurred while loading the slack notification settings.", - "Are you sure you want to delete this configuration setting?": "Are you sure you want to delete this configuration setting?", - "DELETE CONFIGURATION SETTING": "DELETE CONFIGURATION SETTING", - "An error occurred while trying to delete the configuration setting.": "An error occurred while trying to delete the configuration setting.", - "An error occurred while trying to delete the project.": "An error occurred while trying to delete the project.", - "Are you sure you want to remove slack support?": "Are you sure you want to remove slack support?", - "An error occurred while trying to remove slack.": "An error occurred while trying to remove slack.", - "Are you sure you want to delete this API key?": "Are you sure you want to delete this API key?", - "DELETE API KEY": "DELETE API KEY", - "An error occurred while trying to delete the API Key.": "An error occurred while trying to delete the API Key.", - "Are you sure you want to delete this web hook?": "Are you sure you want to delete this web hook?", - "DELETE WEB HOOK": "DELETE WEB HOOK", - "An error occurred while trying to delete the web hook.": "An error occurred while trying to delete the web hook.", - "Are you sure you want to reset the data for this project?": "Are you sure you want to reset the data for this project?", - "RESET PROJECT DATA": "RESET PROJECT DATA", - "An error occurred while resetting project data.": "An error occurred while resetting project data.", - "An error occurred while saving the project.": "An error occurred while saving the project.", - "An error occurred while saving the API key note.": "An error occurred while saving the API key note.", - "An error occurred while saving the common methods.": "An error occurred while saving the common methods.", - "An error occurred while saving the data exclusion.": "An error occurred while saving the data exclusion.", - "An error occurred while saving the include private information setting.": "An error occurred while saving the include private information setting.", - "An error occurred while saving the user agents.": "An error occurred while saving the user agents.", - "An error occurred while saving the user namespaces.": "An error occurred while saving the user namespaces.", - "An error occurred while saving your slack notification settings.": "An error occurred while saving your slack notification settings.", - "Project_Deleted": "The project \"{{projectId}}\" was deleted.", - - "My Organizations": "My Organizations", - "Plan": "Plan", - "Projects": "Projects", - "View Invoices": "View Invoices", - "Leave Organization": "Leave Organization", - "New Organization": "New Organization", - "Monthly Usage": "Monthly Usage", - "Invite User": "Invite User", - "Delete Organization": "Delete Organization", - "UserManager": "User", - "Billing": "Billing", - "Total number of projects": "Total number of projects", - "Total number of stacks": "Total number of stacks", - "Total number of events within the retention period": "Total number of events within the retention period", - "No organizations were found with the current filter.": "No organizations were found with the current filter.", - "No organizations were found.": "No organizations were found.", - "View Payment": "View Payment", - "View Stripe Invoice": "View Stripe Invoice", - "No invoices were found.": "No invoices were found.", - "Organization Name is required.": "Organization Name is required.", - "A organizations with this name already exists.": "A organizations with this name already exists.", - "The usage data above is refreshed periodically and may not reflect current totals.": "The usage data above is refreshed periodically and may not reflect current totals.", - "Click here to change your plan or billing information.": "Click here to change your plan or billing information.", - "Change your plan or billing information.": "Change your plan or billing information.", - "Organization_Billing_Plan": "You are currently on the {{planName}} plan.", - "An error occurred while loading your organizations.": "An error occurred while loading your organizations.", - "Cannot_Find_Organization": "The organization \"{{organizationId}}\" could not be found.", - "An error occurred while inviting the user.": "An error occurred while inviting the user.", - "Organization_Deleted": "The organization \"{{organizationId}}\" was deleted.", - "Are you sure you want to leave this organization?": "Are you sure you want to leave this organization?", - "An error occurred while trying to leave the organization.": "An error occurred while trying to leave the organization.", - "Are you sure you want to delete this organization?": "Are you sure you want to delete this organization?", - "Successfully queued the organization for deletion.": "Successfully queued the organization for deletion.", - "An error occurred while trying to delete the organization.": "An error occurred while trying to delete the organization.", - "An error occurred while saving the organization.": "An error occurred while saving the organization.", - "Configure_Project_Title": "Configure {{projectName}} ({{organizationName}})", - - "Invoice #": "Invoice #", - "Invoice Date": "Invoice Date", - "Invoice": "Invoice", - "Receipt": "Receipt", - "Paid": "Paid", - "Unpaid": "Unpaid", - "Amount": "Amount", - "Email:": "Email:", - "Website:": "Website:", - "Organization_Payment_Plan": "{{organization}} is currently on the {{planName}} plan.", - "Payment_Select_New_Plan": "Select new plan (view plan information). All plan changes are prorated.", - "Credit Card": "Credit Card", - "Coupon code": "Coupon code", - "Exceptionless Plan": "Exceptionless Plan", - "Organization to change": "Organization to change", - "Use a new credit card": "Use a new credit card", - "Card number": "Card number", - "Expires": "Expires", - "Name on card": "Name on card", - "Card code": "Card code", - "Help us improve Exceptionless!": "Help us improve Exceptionless!", - "Contact Us": "Contact Us", - "Changing Plan": "Changing Plan", - "Please contact support for more information.": "Please contact support for more information.", - "Cannot_Find_Invoice": "The invoice \"{{invoiceId}}\" could not be found.", - "An error occurred while changing plans.": "An error occurred while changing plans.", - "Thanks! Your billing plan has been successfully changed.": "Thanks! Your billing plan has been successfully changed.", - "An error occurred while loading available billing plans.": "An error occurred while loading available billing plans.", - "An error occurred while loading your user account.": "An error occurred while loading your user account.", - "Credit_Card_Ending": "Use credit card ending in {{card_last4}}", - - "Email": "Email", - "Full Name": "Full Name", - "Email Address": "Email Address", - "Email address (no spam)": "Email address (no spam)", - "Notifications": "Notifications", - "Password": "Password", - "Current Password": "Current Password", - "Current Password is required.": "Current Password is required.", - "New Password": "New Password", - "New Password is required.": "New Password is required.", - "New Password must be at least 6 characters long.": "New Password must be at least 6 characters long.", - "New Password must be less than 101 characters long.": "New Password must be less than 101 characters long.", - "Confirm password": "Confirm password", - "New Password and Confirmation Password fields do not match.": "New Password and Confirmation Password fields do not match.", - "Confirm Password is required.": "Confirm Password is required.", - "Confirm Password must be at least 6 characters long.": "Confirm Password must be at least 6 characters long.", - "Confirm Password must be less than 101 characters long.": "Confirm Password must be less than 101 characters long.", - "Set Password": "Set Password", - "Changing Password": "Changing Password", - "DELETE ACCOUNT": "DELETE ACCOUNT", - "External Logins": "External Logins", - "Add an external login": "Add an external login", - "Existing external logins": "Existing external logins", - "Remove": "Remove", - "No external logins were found.": "No external logins were found.", - "Log in using your Microsoft account": "Log in using your Microsoft account", - "Log in using your Google account": "Log in using your Google account", - "Log in using your Facebook account": "Log in using your Facebook account", - "Log in using your GitHub account": "Log in using your GitHub account", - "An error occurred while adding external login.": "An error occurred while adding external login.", - "An error occurred while loading the notification settings.": "An error occurred while loading the notification settings.", - "An error occurred while loading the projects.": "An error occurred while loading the projects.", - "An error occurred while loading your user profile.": "An error occurred while loading your user profile.", - "Are you sure you want to delete your account?": "Are you sure you want to delete your account?", - "Successfully removed your user account.": "Successfully removed your user account.", - "An error occurred while trying remove your user account.": "An error occurred while trying remove your user account.", - "An error occurred while sending your verification email.": "An error occurred while sending your verification email.", - "An error occurred while saving your email address.": "An error occurred while saving your email address.", - "An error occurred while saving your notification settings.": "An error occurred while saving your notification settings.", - "An error occurred while saving your email notification preferences.": "An error occurred while saving your email notification preferences.", - "An error occurred while saving your full name.": "An error occurred while saving your full name.", - "An error occurred while removing the external login.": "An error occurred while removing the external login.", - "Choose how often you want to receive notifications for event occurrences in this project.": "Choose how often you want to receive notifications for event occurrences in this project.", - "Are you sure you want to add the admin role for this user?": "Are you sure you want to add the admin role for this user?", - "Are you sure you want to remove the admin role from this user?": "Are you sure you want to remove the admin role from this user?", - "An error occurred while add the admin role.": "An error occurred while add the admin role.", - "An error occurred while remove the admin role.": "An error occurred while remove the admin role.", - - "Log in": "Log in", - "after": "after", - "before": "before", - "with": "with", - "OR": "OR", - "Login with": "Login with", - "Signup": "Signup", - "Forgot password?": "Forgot password?", - "Create an account": "Create an account", - "Create My Account": "Create My Account", - "Log In": "Log In", - "Change password": "Change password", - "Reset your password": "Reset your password", - "Forgot Password": "Forgot Password", - "NOTICE_SIGNINGUP": "By signing up, you agree to our Privacy Policy and Terms of Service.", - "Loggin_Failed_Message": "An error occurred while logging in. Please contact support for more information.", - "Login_Invalid_Credentials": "Invalid email or password.", - "Unable_to_connect_to": "Unable to connect to", - "ResetPassword_Failed_Message": "An error occurred while trying to reset your password.", - "ResetPassword_Success_Message": "An email was sent that contains instructions to change your password.", - "An error occurred while signing up. Please contact support for more information.": "An error occurred while signing up. Please contact support for more information.", - "You have successfully changed your password.": "You have successfully changed your password.", - "An error occurred while trying to change your password.": "An error occurred while trying to change your password.", - "Successfully verified your account.": "Successfully verified your account.", - "An error occurred while verifying your account.": "An error occurred while verifying your account.", - - "AdminRole": "Admin", - "Invited": "Invited", - "Resend": "Resend", - "Your first and last name": "Your first and last name", - "verification email.": "verification email.", - "Email not verified.": "Email not verified.", - "Email notifications are currently disabled.": "Email notifications are currently disabled.", - "To enable email notifications you must first verify your email address.": "To enable email notifications you must first verify your email address.", - "Enable email notifications": "Enable email notifications", - "Send daily project summary": "Send daily project summary", - "Notify me on new errors": "Notify me on new errors", - "Notify me on critical errors": "Notify me on critical errors", - "Notify me on error regressions": "Notify me on error regressions", - "Notify me on new events": "Notify me on new events", - "Notify me on critical events": "Notify me on critical events", - "Upgrade now": "Upgrade now", - "to enable occurrence level notifications!": "to enable occurrence level notifications!", - "Resend Invite Email": "Resend Invite Email", - "Revoke Invite": "Revoke Invite", - "Remove User": "Remove User", - "Add Admin Role": "Add Admin Role", - "Remove Admin Role": "Remove Admin Role", - "No users were found.": "No users were found.", - "Are you sure you want to remove this user from your organization?": "Are you sure you want to remove this user from your organization?", - "An error occurred while trying to remove the user.": "An error occurred while trying to remove the user.", - "An error occurred while trying to resend the notification.": "An error occurred while trying to resend the notification.", - - "Create New Web Hook": "Create New Web Hook", - "Url": "Url", - "Enter the URL to call": "Enter the URL to call", - "URL is required.": "URL is required.", - "Control when the web hook is called by choosing the event types below.": "Control when the web hook is called by choosing the event types below.", - "Please choose one or more event types.": "Please choose one or more event types.", - "Create Web Hook": "Create Web Hook", - "New Error": "New Error", - "Occurs when a new error that has never been seen before is reported to your project.": "Occurs when a new error that has never been seen before is reported to your project.", - "Critical Error": "Critical Error", - "Occurs when an error that has been marked as critical is reported to your project.": "Occurs when an error that has been marked as critical is reported to your project.", - "Regression": "Regression", - "Occurs when an event that has been marked as fixed has reoccurred in your project.": "Occurs when an event that has been marked as fixed has reoccurred in your project.", - "Occurs when a new event that has never been seen before is reported to your project.": "Occurs when a new event that has never been seen before is reported to your project.", - "Critical Event": "Critical Event", - "Occurs when an event that has been marked as critical is reported to your project.": "Occurs when an event that has been marked as critical is reported to your project.", - "Promoted": "Promoted", - "Used to promote event stacks to external systems.": "Used to promote event stacks to external systems.", - - "Delete": "Delete", - "Cancel": "Cancel", - "Apply": "Apply", - "DateTime": "{{date | date: 'medium'}}", - "Go To Most Frequent": "Go To Most Frequent", - "Save": "Save", - "Edit": "Edit", - "to": "to", - - "Email Address is required.": "Email Address is required.", - "Password is required": "Password is required", - "Password must be at least 6 characters long": "Password must be at least 6 characters long", - "Password must be less than 101 characters long": "Password must be less than 101 characters long", - "Signup for a FREE account in seconds": "Signup for a FREE account in seconds", - "Full Name is required.": "Full Name is required.", - "A user already exists with this email address.": "A user already exists with this email address.", - "Already have an account?": "Already have an account?", - "API rate limit exceeded": "API rate limit exceeded", - "Your API rate limit has been exceeded. Please try again in 15 minutes.": "Your API rate limit has been exceeded. Please try again in 15 minutes.", - "to enable premium features and extra storage!": "to enable premium features and extra storage!", - "to enable search and other premium features!": "to enable search and other premium features!", - "to continue receiving events.": "to continue receiving events.", - "to increase your limits.": "to increase your limits.", - "is currently on a free plan.": "is currently on a free plan.", - "has reached its monthly plan limit.": "has reached its monthly plan limit.", - "Events are currently being throttled to prevent using up your plan limit in a small window of time.": "Events are currently being throttled to prevent using up your plan limit in a small window of time.", - "Events are currently being throttled for": "Events are currently being throttled for", - "API requests are currently being throttled for": "API requests are currently being throttled for", - "We haven't received any data!": "We haven't received any data!", - "Setup your first project": "Setup your first project", - "Copied!": "Copied!", - "Message:": "Message:", - "Allowed": "Allowed", - "Blocked": "Blocked", - "Too Big": "Too Big", - "Limit": "Limit", - "Allowed in Organization": "Allowed in Organization", - "Occurrences": "Occurrences", - "Log source": "Log source", - "Add": "Add", - "Total": "Total", - - "Log Level": "Log Level", - "trace": "Trace", - "debug": "Debug", - "info": "Info", - "warn": "Warn", - "error": "Error", - "fatal": "Fatal", - "off": "Off", - "Default": "Default", - "Notice_Default_Log_Level": "The default log level controls the minimum log level that should be accepted for log events. Log levels can also be overridden at the log stack level.", - - "DIALOGS_ERROR": "Error", - "DIALOGS_ERROR_MSG": "An unknown error has occurred.", - "DIALOGS_CLOSE": "Close", - "DIALOGS_PLEASE_WAIT": "Please Wait", - "DIALOGS_PLEASE_WAIT_ELIPS": "Please Wait...", - "DIALOGS_PLEASE_WAIT_MSG": "Waiting on operation to complete.", - "DIALOGS_PERCENT_COMPLETE": "% Complete", - "DIALOGS_NOTIFICATION": "Notification", - "DIALOGS_NOTIFICATION_MSG": "Unknown application notification.", - "DIALOGS_CONFIRMATION": "Confirmation", - "DIALOGS_CONFIRMATION_MSG": "Confirmation required.", - "DIALOGS_JSON": "JSON", - "DIALOGS_OK": "OK", - "DIALOGS_YES": "Yes", - "DIALOGS_NO": "No", - - "NOTICE_GRAVATAR": "Your avatar is generated by requesting a Gravatar image with the email address below." -} diff --git a/src/Exceptionless.Web/ClientApp.angular/lang/zh-cn.json b/src/Exceptionless.Web/ClientApp.angular/lang/zh-cn.json deleted file mode 100644 index 6fcabc3af0..0000000000 --- a/src/Exceptionless.Web/ClientApp.angular/lang/zh-cn.json +++ /dev/null @@ -1,708 +0,0 @@ -{ - "Change Plan": "变更计划", - "Change Password": "修改密码", - "Logout": "退出", - "Exceptions": "异常情况", - "Log Messages": "日志消息", - "Broken Links": "失效链接", - "Feature Usages": "特性使用", - "All": "全部日志", - "Reports": "报表反馈", - "Exception Events": "例外时间表", - "Message Events": "消息时间表", - "Broken Links Events": "链接断开时间轴", - "Feature Events": "功能时间表", - "Event Events": "活动时间表", - "Most Frequent Exceptions": "高频异常", - "Most Frequent Messages": "高频消息", - "Most Frequent Links": "高频死链", - "Most Frequent Features": "高频特性", - "Most Frequent": "高频日志", - "Most Frequent Stacks": "最频繁的堆栈", - "Most Users": "用户最多", - "Most Users Stacks": "大多数用户堆栈", - "New Exception": "最新异常", - "New LogMessage": "最新消息", - "New BrokenLink": "最新死链", - "New FeatureUsage": "最新特性", - "New Event": "最新日志", - "Sessions": "会话量", - "Project Manager": "项目管理", - "Organization Manager": "组织管理", - "My Account": "我的帐户", - "Documentation": "说明文档", - "Support": "在线支持", - "New Stacks": "最新堆栈数", - "Events Per Hour": "事件数每小时", - "History": "历史", - "Session": "会话", - "Session End": "会话结束", - "Session Heartbeat": "会话心跳", - "Feature": "特性", - "Go To Documentation": "前往说明文档", - "Focus Search Bar": "激活搜索框", - "Go To My Account": "前往我的账户", - "Go To Notifications": "前往通知视图", - "Go To Organizations": "前往组织视图", - "Go To Projects": "前往项目视图", - "Go To GitHub project": "前往 GitHub 项目", - "Go to public Discord channel": "前往 Discord 公共频道", - "Chat with Support": "联系客服", - "Your user account was deleted. Please create a new account.": "您的账号已经被删除。请创建新账号。", - "Billing is currently disabled.": "当前系统不支持在线支付。", - - "Page Not Found": "未找到页面", - "We're sorry but the page was not found on this site.": "很抱歉,在此网站中没有找到您要访问的页面。", - - "Service Status": "服务状态", - "We're sorry but the website is currently undergoing maintenance.": "很抱歉,此网站目前正在进行维护。", - "You'll be automatically redirected when the maintenance is completed.": "在维护完成后,系统将自动重定向到您要访问的页面。", - - "Please enter a Reference Link": "请输入参考链接", - "Reference Link": "参考链接", - "Please enter a valid URL": "请输入有效网址", - "Reference Link is required.": "参考链接是必需的。", - "Save Reference Link": "保存参考链接", - "UserRatio": "用户比例", - "First": "首次", - "Last": "末次", - "Stack": "堆栈", - "Options": "选项", - "Hide": "隐藏", - "Future Occurrences Are Critical": "此堆栈后续的事件标记为关键事件", - "Disable Notifications": "禁止通知", - "Promote To External": "外部推送", - "Add Reference Link": "新增参考链接", - "Title": "标题", - "Project": "项目", - "Date Fixed": "修复日期", - "Description": "说明", - "Tags": "标签", - "Stacking Information": "堆栈信息", - "ExceptionType": "异常类型", - "Method": "方法", - "Chart Options": "图表选项", - "Show Average Value": "显示平均值", - "Average Value": "平均值", - "Show Value Sum": "显示合计值", - "Value Sum": "合计值", - "Recent Occurrences": "最近发生", - "Reference Links": "参考链接", - "Mark Fixed": "标记已修复", - "No stacks were found with the current filter.": "未找到满足当前筛选条件的堆栈。", - "No stacks were found.": "尚未发现任何堆栈。", - "Future Stack Occurrences are Not Critical": "此堆栈后续的事件为非关键事件", - "Future Stack Occurrences are Critical": "此堆栈后续的事件为关键事件", - "Enable Stack Notifications": "启用堆栈通知", - "Disable Stack Notifications": "禁用堆栈通知", - "Promote Stack To External": "推送此堆栈到外部系统", - "Add Stack Reference Link": "新增堆栈参考链接", - "Delete Stack": "删除堆栈", - "An error occurred while adding the reference link.": "在新增参考链接的过程中发生了错误。", - "Successfully promoted stack!": "成功将堆栈推送到外部系统!", - "Manage Integrations": "管理外部整合", - "An error occurred while promoting this stack.": "在将堆栈推送至外部系统的过程中发生了错误。", - "Are you sure you want to delete this reference link?": "您确认要删除此参考链接?", - "An error occurred while deleting the external reference link.": "在删除外部参考链接的过程中发生了错误。", - "Are you sure you want to delete this stack (includes all stack events)?": "您确认要删除此堆栈及其所有日志吗?", - "Successfully queued the stack for deletion.": "已将此堆栈放入队列等待删除。", - "An error occurred while deleting this stack.": "在删除堆栈的过程中发生了错误。", - "The average of all event values": "所有事件的平均值", - "The sum of all event values": "所有事件值的总和", - "Hide this stack from reports and mutes occurrence notifications": "在报表和事件通知中隐藏此堆栈", - "All future occurrences will be marked as critical": "后续的事件将会被标记为关键事件", - "Stop sending occurrence notifications for this stack": "停止发送此堆栈的通知", - "Used to promote stacks to external systems.": "用于将堆栈推送到外部系统。", - "Add a reference link to an external resource.": "为外部资源增加引用链接。", - "Delete this stack": "删除堆栈", - - "Summary": "概要", - "Created": "已创建", - "Duration": "持续时间", - "Loading...": "加载中...", - "Users": "用户量", - "Average Duration": "平均持续时间", - "Sessions and Users": "用户会话", - "View Active": "查看活动会话", - "Recent Sessions": "近期会话", - "No sessions were found with the current filter.": "未找到满足当前筛选条件的会话。", - "No sessions were found.": "尚未发现任何会话。", - - "Occurred On": "发生时间", - "ended": "结束于", - "Reference": "参考", - "Level": "日志等级", - "Event Type": "事件类型", - "Error Type": "错误类型", - "Source": "来源", - "Value": "值", - "Message": "消息", - "Version": "版本", - "Geo": "地理位置", - "URL": "请求URL", - "User Info": "用户信息", - "User Email": "用户邮箱", - "User Identity": "用户标识", - "User Name": "用户名", - "User Description": "用户说明", - "Stack Trace": "堆栈跟踪", - "Machine Name": "计算机名", - "IP Address": "IP地址", - "Processor Count": "处理器核数", - "Total Memory": "总内存", - "Available Memory": "可用内存", - "Process Memory": "已用内存", - "OS Name": "操作系统", - "OS Version": "系统版本", - "Architecture": "系统架构", - "Runtime Version": "运行时版本", - "Process ID": "进程标识", - "Process Name": "进程名称", - "Command Line": "命令行", - "Additional Data": "附加数据", - "Code": "错误编码", - "Submission Method": "提交方法", - "Loaded Modules": "已加载模块", - "Module Name": "模块名称", - "HTTP Method": "HTTP请求方法", - "Referrer": "访问来源", - "Client IP": "客户端IP", - "User Agent": "User Agent", - "Device": "设备", - "Browser": "浏览器", - "Browser OS": "浏览器系统", - "Post Data": "提交数据", - "Session Events": "会话事件", - "Event Occurrence": "发生事件", - "Go To Stack": "前往堆栈", - "Previous Occurrence": "上个事件", - "Next Occurrence": "下个事件", - "Toggle View": "切换视图", - "Promote to Tab": "提升为标签", - "Demote Tab": "降级标签", - "References": "参考", - "Overview": "概要", - "Exception": "异常", - "Environment": "环境", - "Extended Data": "扩展数据", - "Request": "请求", - "to enable sessions and other premium features!": "以启用会话及其他高级特性!", - "is attempting to use a premium feature.": "正在尝试使用高级特性。", - "View JSON": "查看 JSON", - "Copy Stack Trace to Clipboard": "将堆栈跟踪复制到剪贴板", - "Copy Event JSON to Clipboard": "将事件以JSON格式复制到剪贴板", - "Copy to Clipboard": "复制到剪贴板", - "Cookie Values": "Cookie值", - "Headers": "标头", - "No events were found with the current filter.": "未找到满足当前筛选条件的日志。", - "No events were found.": "尚未发现任何日志。", - "An error occurred promoting tab.": "提升标签的过程中发生了错误。", - "Cannot_Find_Event": "未找到事件\"{{eventId}}\"。", - "Online": "在线", - "Seconds": "{{duration}}秒", - - "User": "用户", - "Bulk Action": "批量操作", - "Mark Hidden": "标记隐藏", - "Sort Descending": "降序排序", - "Sort Ascending": "升序排序", - "Date": "日期", - "Please select one or more events": "请至少选择一条日志", - "Successfully queued the events for deletion.": "已将日志放入队列等待删除。", - "An error occurred while deleting the events.": "在删除日志的过程中发生了错误。", - "Are you sure you want to delete these events?": "您确认要删除这些日志吗?", - "DELETE EVENTS": "删除日志", - - "Are you sure you want to delete these stacks (includes all stack events)?": "您确认要删除这些堆栈及其所有日志吗?", - "DELETE STACKS": "删除堆栈", - "An error occurred while deleting the stacks.": "在删除堆栈的过程中发生了错误。", - "Successfully marked the stacks as fixed.": "成功将堆栈标记为固定。", - "An error occurred while marking stacks as fixed.": "在将堆栈标记为已修复的过程中发生了错误。", - "Successfully queued the stacks to be marked as hidden.": "已将堆栈放入队列等待被标记为隐藏。", - "An error occurred while marking stacks as hidden.": "在将堆栈标记为隐藏的过程中发生了错误。", - "Successfully queued the stacks for deletion.": "已将堆栈放入队列等待删除。", - "Please select one or more stacks": "请至少选择一个堆栈", - "Marks the stack as fixed. This will also prevent error occurrences from being displayed in the dashboard.": "标记此堆栈为已修复。此操作可以避免错误堆栈继续在概览视图显示。", - "Optional:": "可选:", - "Please enter the version in which the stack has been fixed. Any submitted occurrences with a lower version will not cause a regression.": "请输入此堆栈修复的版本号。任何低于此版本的应用程序所提交的可归类到此堆栈的日志不会被视为回归日志。", - "Versioning Documentation": "版本化说明文档", - "Optional Version (Example: 1.2.3)": "可选版本(例如:1.2.3)", - "Invalid version.": "无效的版本号。", - "Mark this stack as open": "将此堆栈标记为打开", - "Mark this stack as fixed": "标记此堆栈为已修复", - "Marking Not Fixed": "正在标记未修复", - "Marking Fixed": "正在标记已修复", - "Stack_Deleted": "堆栈\"{{stackId}}\"已被删除。", - "Cannot_Find_Stack": "无法找到堆栈\"{{stackId}}\"。", - "Error_Load_Stack": "在加载堆栈\"{{stackId}}\"的过程中发生了错误。", - "An error occurred while marking this stack as open.": "将该堆栈标记为打开时发生错误。", - "An error occurred while marking this stack as discarded.": "将该堆栈标记为已丢弃时发生错误。", - "Are you sure you want to all current stack events and discard any future stack events?": "您确定要所有当前的堆栈事件并丢弃以后的所有堆栈事件吗?", - "All future occurrences will be discarded and will not count against your event limit.": "将来所有出现的事件都将被丢弃,并且不会计入您的事件限制。", - "An error occurred while marking future occurrences as not critical.": "在将后续事件标记为非关键事件的过程中发生了错误。", - "An error occurred while marking future occurrences as critical.": "在将后续事件标记为关键事件的过程中发生了错误。", - "Successfully marked the stack as open.": "成功将堆栈标记为打开。", - "Successfully marked the stack as fixed.": "成功将堆栈标记为固定。", - "Successfully marked the stacks as ignored.": "成功将堆栈标记为已忽略。", - "An error occurred while marking this stack as fixed.": "在将队列标记为已修复的过程中发生了错误。", - "An error occurred while marking this stack as snoozed.": "将此堆栈标记为已延后时发生错误。", - "An error occurred while marking this stack as ignored.": "将该堆栈标记为已忽略时发生错误。", - "An error occurred while marking stacks as ignored.": "将堆栈标记为忽略时发生错误。", - "Successfully marked the stacks as open.": "成功将堆栈标记为打开。", - "An error occurred while marking stacks as open.": "将堆栈标记为打开时发生错误。", - "Successfully marked the stacks as discarded.": "成功将堆栈标记为已丢弃。", - "An error occurred while marking stacks as discarded.": "将堆栈标记为已丢弃时发生错误。", - - "Mark Open": "标记堆栈打开", - "Mark Stack Open": "标记堆栈打开", - "Mark Stack Discarded": "标记堆栈已舍弃", - "Mark Stack Fixed": "标记为已修复", - - "Fixed_With_Version": "在 {{fixed_in_version}} 版本中修复", - "Opening": "开场", - "Discard": "丢弃", - - "open": "打开", - "discarded": "舍弃", - "fixed": "已修复", - "regressed": "回归", - "snoozed": "延后", - "ignored": "被忽略", - "6 Hours": "6个小时", - "1 Day": "1天", - "1 Week": "1周", - "1 Month": "1个月", - - "All Projects": "所有项目", - "Loading Organizations...": "加载组织中...", - "Loading Projects...": "加载项目中...", - "Add New Project": "创建项目", - - "Last Hour": "最近 1 小时", - "Last 24 Hours": "最近 1 天", - "Last Week": "最近 7 天", - "Last 30 Days": "最近 30 天", - "All Time": "全部时间", - "Custom": "自定义", - "Select Date Range": "选择日期范围", - "First In Date Range": "在日期范围内", - "First Ever": "史无前例的", - "Last In Date Range": "最后一个日期范围", - "Last Ever": "最后一次", - - "DayFormat": "D日", - "DateFormat": "YYYY年M月D日", - "DateTimeFormat": "YYYY年M月D日 H:mm:ss", - "Filter": "过滤条件", - "Search Documentation": "说明文档", - - "My Projects": "我的项目", - "Project Information": "项目信息", - "Organization Name": "组织名称", - "New Organization Name": "新组织名称", - "Project Name": "项目名称", - "New Project Name": "新项目名称", - "Add Project": "创建项目", - "Manage Project": "管理项目", - "General": "常规", - "API Keys": "API 密钥", - "Settings": "设置", - "Client Configuration": "客户端配置", - "Integrations": "外部整合", - "Download & Configure Client": "下载并配置客户端", - "Manage Organization": "管理组织", - "Manage Notification Settings": "管理通知设置", - "Reset Project Data": "重置项目数据", - "Delete Project": "删除项目", - "API Key": "API 密钥", - "Enable": "启用", - "Disable": "禁用", - "Are you sure you want to enable the API key?": "您确定要启用API密钥吗?", - "ENABLE API KEY": "启用API密钥", - "An error occurred while enabling the API key.": "启用API密钥时发生错误。", - "Are you sure you want to disable the API key?": "您确定要禁用API密钥吗?", - "DISABLE API KEY": "禁用API密钥", - "An error occurred while disabling the API key.": "禁用API密钥时发生错误。", - "Notes": "备注", - "Payment Number": "付款编号", - "Actions": "操作", - "Status": "状态", - "This project does not have an API Key.": "此项目还没有 API 秘钥。", - "New API Key": "创建 API 密钥", - "New Client Configuration": "添加客户端配置", - "Event Types": "事件类型", - "Add Web Hook": "新增 Web Hook", - "Connect Zapier": "连接 Zapier", - "Data Exclusions": "数据排除", - "User Namespaces": "用户命名空间", - "Error Stacking": "错误堆栈", - "Common Methods": "常用方法", - "Spam Detection": "垃圾邮件检测", - "Please enter a configuration setting": "请输入客户端配置", - "Name": "名称", - "Organization": "组织", - "Stacks": "堆栈数", - "Events": "事件数", - "View Organization": "查看组织", - "Key": "键", - "Project Name is required.": "请填写项目名称", - "A project with this name already exists.": "已经存在相同名称的项目。", - "This project does not contain any custom configuration settings.": "此项目不包含任何自定义配置参数。", - "NOTICE_CONFIGURATION": "此页面中的配置参数将会被实时发送到 Exceptionless 客户端。这种方法可以免部署而动态改变您的应用程序的工作方式。", - "No projects were found with the current filter.": "未找到满足当前筛选条件的项目。", - "No projects were found.": "尚未发现任何项目。", - "Notice_Data_Exclusions": "使用逗号分隔的字段名称列表用于移除任何错误报告中的相关数据(例如扩展数据属性、表单字段、Cookie 和查询参数)。 您也可以设置一个带通配符(*)的字段名称匹配前缀、后缀或者包含,以提高安全性。", - "Automatically remove user identifiable information from events (e.g., Machine Name, User Information, IP Addresses and more...).": "自动从事件中删除用户可识别信息(例如,机器名称,用户信息,IP地址等等", - "Notice_Error_Stacking": "使用逗号分隔的命名空间列表,这些命名空间应该是您的应用程序代码中所定义的。在此值填写之后,只有在列表命名空间中的方法调用才会被包含在错误堆栈中。", - "Notice_Common_Methods": "使用逗号分隔的常用方法名称,这些方法不应该被包含在错误堆栈中。如果在您的代码包含一些公用方法,此功能在这些方法抛出大量异常的情况下非常有价值。", - "Notice_Spam_Detection": "使用逗号分隔的 HTTP 请求的 User Agent 列表,任何匹配列表中 User Agent 的请求都将会被忽略。", - "Example:": "例如:", - "Reduce noise by automatically hiding high volumes of events coming from a single client IP address.": "自动隐藏来自相同IP地址的高频事件以减少干扰。", - "Please enter a valid key.": "请输入有效的键", - "Please enter a valid value.": "请输入有效的值", - "Configuration Key": "配置键", - "Configuration Value": "配置值", - "to enable integrations!": "以启用外部整合!", - "Choose how often you want to receive slack notifications for event occurrences in this project.": "请选择您希望以何频率接收此项目中事件的Slack通知", - "(Coming soon!)": "(即将推出!)", - "Add Slack Notifications": "新增 Slack 通知", - "Remove Slack": "移除 Slack", - "Notice_Web_hooks": "此项目会调用下面表格中的Web Hook", - "Notice_Zapier": "Exceptionless具有本地Zapier集成。 您可以使用Zapier将您的Exceptionless帐户连接到3,000多个其他应用程序,而无需编写任何代码。", - "This project does not contain any integrations.": "此项目不包含任何外部整合。", - "An error occurred while loading your projects.": "在加载您的项目的过程中发生了错误。", - "Are you sure you want to delete this project?": "您确认要删除此项目?", - "Successfully queued the project for deletion.": "已将此项目放入队列等待删除。", - "An error occurred while trying to remove the project.": "在删除项目的过程中发生了错误。", - "Download_Configure_Project": "下载配置项目{{projectName}}的客户端", - "The Exceptionless client can be integrated into your project in just a few easy steps.": "通过以下几个简单的步骤就可以轻松地将 Exceptionless 客户端整合到您的项目中去。", - "Generate Sample Data": "生成示例数据", - "Generating Sample Data...": "正在生成示例数据...", - "Sample data generation has been queued. Events will appear shortly.": "示例数据生成已加入队列。事件稍后将显示。", - "An error occurred while generating sample data for your project.": "生成项目示例数据时发生错误。", - "Select your project type:": "请选择您的项目类型:", - "Please select a project type": "请选择项目类型", - "Execute the following in your shell:": "在您的shell中执行以下命令:", - "Add the script to your HTML page:": "将下面的脚本放到您的 HTML 页面中", - "Configure the ExceptionlessClient with your Exceptionless API key:": "通过您的 API 密钥配置 ExceptionlessClient :", - "Notice_Install_Nuget_Package": "在 Visual Studio 中的程序包管理器控制台运行如下命令可以将 Exceptionless 的 NuGet 程序包安装到您的项目中:", - "Notice_Install_Manually": "如果您希望手动安装或者从 CDN 引用,请仔细阅读 详细说明。", - "Notice_Install_Node_Package": "通过 Node 程序包管理器 安装 Exceptionless :", - "Notice_Update_ApiKey": "在项目中的 {{config}} 文件中找到 <exceptionless apiKey=\"API_KEY_HERE\" /> 部分并使用您的 Exceptionless 相应项目的 API 密钥替换:", - "Notice_Application_Startup_NetFx": "在您的应用程序的启动文件中引入 Exceptionless 命名空间,然后通过 API 密钥执行 ExceptionlessClient.Default.Startup(\"{{apiKey}}\") 方法。", - "Notice_Application_Startup_NetCore": "导入 Exceptionless 命名空间并在 ConfigureServices 方法中调用 services.AddExceptionless(\"{{apiKey}}\") 方法。 接下来,在应用程序启动期间调用配置中的 app.UseExceptionless() 方法。", - "Notice_Application_Startup_Nancy": "在您的 Nancy 引导程序中引入 Exceptionless 命名空间,然后在 ApplicationStartup 方法中通过 Nancy.Bootstrapper.IPipelines 类型的实例调用 Exceptionless.ExceptionlessClient.Default.RegisterNancy(pipelines) 方法。", - "Notice_Application_Startup_Windows": "在您的应用程序的启动文件中引入 Exceptionless 命名空间, 然后执行 ExceptionlessClient.Default.Register() 方法。", - "Notice_Application_Startup_WebApi": "在您的应用程序的启动文件中引入 Exceptionless 命名空间, 然后通过 HttpConfiguration 类型的实例调用 ExceptionlessClient.Default.RegisterWebApi(config) 方法。", - "Notice_Hosting_WebApi_In_AspNet": "如果将 Web API 程序托管在 ASP.NET 应用程序中,您需要这样注册 Exceptionless : Exceptionless.ExceptionlessClient.Default.RegisterWebApi(GlobalConfiguration.Configuration)", - "Notice_Unhandled_Exception": "如果您瞄准的是Net Standard或Portable Class Libraries(PCL),则可能需要连接到任何特定于您的项目类型的未处理异常事件,才能开始报告它们!请看 Exceptionless 文档以获取更多的信息。", - "That's it!": "就是这样!", - "Your project should now automatically be sending all unhandled exceptions to Exceptionless!": "现在您的项目可以自动将所有未处理异常发送到 Exceptionless 了!", - "Notice_Submit_Exception": "您也可以通过 {{sampleCode}} 向 Exceptionless 发送已处理的异常。", - "Notice_CommandLine_Application": "请查看 Exceptionless 使用说明以获取更多的信息和故障排除提示。", - "Notice_DotNet_Application": "请查看 Exceptionless 使用说明以获取更多的信息和故障排除提示。", - "Notice_JavaScript_Application": "请查看 Exceptionless 使用说明以获取更多的信息以及基于特定平台的集成方法(例如 AngularJS, Express.js, React, Vue)。", - "Copy not supported.": "不支持复制。", - "Press ⌘-C to copy.": "按 ⌘-C 键复制。", - "Press Ctrl-C to copy.": "按 Ctrl-C 键复制。", - "An error occurred while getting the API key for your project.": "在获取您的项目的 API 的密钥的过程中发生了错误。", - "Console and Service applications": "控制台和服务应用", - "Browser applications": "浏览器应用", - "Cannot_Find_Project": "无法找到项目\"{{projectId}}\"。", - "An error occurred while creating the organization.": "在创建组织的过程中发生了错误。", - "An error occurred while creating the project.": "在创建项目的过程中发生了错误。", - "": "<新组织>", - "Add to Slack": "新增 Slack 通知", - "Project Most Frequent": "高频日志", - "Please upgrade your plan to enable slack integration.": "请升级您的计划以启用 Slack 整合。", - "An error occurred while adding Slack to your project.": "在将 Slack 整合到您的项目的过程中发生了错误。", - "An error occurred while creating a new API key for your project.": "在为您的项目创建新的 API 密钥的过程中发生了错误。", - "An error occurred while saving the configuration setting.": "在保存配置参数的过程中发生了错误。", - "An error occurred loading the api keys.": "在加载 API 密钥的过程中发生了错误。", - "An error occurred loading the notification settings.": "在加载通知设置的过程中发生了错误。", - "An error occurred while loading the slack notification settings.": "在加载 Slack 通知设置的过程中发生了错误。", - "Are you sure you want to delete this configuration setting?": "您确认要删除此配置参数吗?", - "DELETE CONFIGURATION SETTING": "删除配置参数", - "An error occurred while trying to delete the configuration setting.": "在尝试删除配置参数的过程中发生了错误。", - "An error occurred while trying to delete the project.": "在尝试删除项目的过程中发生了错误。", - "Are you sure you want to remove slack support?": "您确认要删除对 Slack 的支持吗?", - "An error occurred while trying to remove slack.": "在删除 Slack 整合的过程中出现了错误。", - "Are you sure you want to delete this API key?": "您确认要删除此 API 密钥吗?", - "DELETE API KEY": "删除 API 密钥", - "An error occurred while trying to delete the API Key.": "在尝试删除 API 密钥的过程中发生了错误。", - "Are you sure you want to delete this web hook?": "您确认要删除此 Web Hook 吗?", - "DELETE WEB HOOK": "删除 Web Hook", - "An error occurred while trying to delete the web hook.": "在尝试删除 Web Hook 的过程中发生了错误。", - "Are you sure you want to reset the data for this project?": "您确认要重置此项目的数据?", - "RESET PROJECT DATA": "重置项目数据", - "An error occurred while resetting project data.": "在重置项目数据的过程中发生了错误。", - "An error occurred while saving the project.": "在保存项目的过程中发生了错误。", - "An error occurred while saving the API key note.": "在保存 API 密钥的备注的过程中发生了错误。", - "An error occurred while saving the common methods.": "在保存常用方法的过程中发生了错误。", - "An error occurred while saving the data exclusion.": "在保存数据排除的过程中发生了错误。", - "An error occurred while saving the include private information setting.": "保存包含私人信息设置时发生错误。", - "An error occurred while saving the user agents.": "在保存 User Agent 的过程中发生了错误。", - "An error occurred while saving the user namespaces.": "在保存用户命名空间的过程中发生了错误。", - "An error occurred while saving your slack notification settings.": "在保存您的 Slack 通知设置的过程中发生了错误。", - "Project_Deleted": "项目\"{{projectId}}\"已被删除。", - - "My Organizations": "我的组织", - "Plan": "计划", - "Projects": "项目", - "View Invoices": "查看发票", - "Leave Organization": "离开组织", - "New Organization": "新增组织", - "Monthly Usage": "月度用量", - "Invite User": "邀请用户", - "Delete Organization": "删除组织", - "UserManager": "用户管理", - "Billing": "在线支付", - "Total number of projects": "项目总数", - "Total number of stacks": "堆栈总数", - "Total number of events within the retention period": "保留期内事件总数", - "No organizations were found with the current filter.": "未找到满足当前筛选条件的组织。", - "No organizations were found.": "尚未发现任何组织。", - "View Payment": "查看款项", - "View Stripe Invoice": "查看Stripe发票", - "No invoices were found.": "尚未发现任何发票。", - "Organization Name is required.": "请填写组织名称。", - "A organizations with this name already exists.": "已经存在相同名称的组织。", - "The usage data above is refreshed periodically and may not reflect current totals.": "上述使用量数据为定期刷新结果,可能无法准确地反映当前的真实情况。", - "Click here to change your plan or billing information.": "点击这里更改您的计划或者在线支付信息。", - "Change your plan or billing information.": "更改您的计划或者在线支付信息。", - "Organization_Billing_Plan": "您当前在使用{{planName}}计划。", - "An error occurred while loading your organizations.": "在加载您的组织的过程中发生了错误。", - "Cannot_Find_Organization": "无法找到组织\"{{organizationId}}\"。", - "An error occurred while inviting the user.": "在邀请用户的过程中发生了错误。", - "Organization_Deleted": "组织\"{{organizationId}}\"已被删除。", - "Are you sure you want to leave this organization?": "您确定要离开此组织吗?", - "An error occurred while trying to leave the organization.": "在离开组织的过程中发生了错误。", - "Are you sure you want to delete this organization?": "您确认要删除此组织吗?", - "Successfully queued the organization for deletion.": "已将此组织放入队列等待删除。", - "An error occurred while trying to delete the organization.": "在删除组织的过程中发生了错误。", - "An error occurred while saving the organization.": "在保存组织的过程中发生了错误。", - "Configure_Project_Title": "配置{{projectName}}({{organizationName}})", - - "Invoice #": "发票编号", - "Invoice Date": "发票日期", - "Invoice": "发票", - "Receipt": "收据", - "Paid": "已支付", - "Unpaid": "未支付", - "Amount": "金额", - "Email:": "邮箱:", - "Website:": "网站:", - "Organization_Payment_Plan": "{{organization}}当前在使用{{planName}}计划。", - "Payment_Select_New_Plan": "选择新计划(查看计划详情)。所有的计划变更均会按使用比例计费。", - "Credit Card": "信用卡", - "Coupon code": "优惠码", - "Exceptionless Plan": "Exceptionless 计划", - "Organization to change": "变更计划的组织", - "Use a new credit card": "使用新信用卡", - "Card number": "卡号", - "Expires": "有效期", - "Name on card": "持卡人姓名", - "Card code": "安全码", - "Help us improve Exceptionless!": "请帮助我们改进 Exceptionless!", - "Contact Us": "联系我们", - "Changing Plan": "正在变更计划", - "Please contact support for more information.": "请联系客户服务以获取更多的信息。", - "Cannot_Find_Invoice": "无法找到发票\"{{invoiceId}}\"。", - "An error occurred while changing plans.": "再变更计划的过程中发生了错误。", - "Thanks! Your billing plan has been successfully changed.": "非常感谢!您的在线支付计划已经成功变更。", - "An error occurred while loading available billing plans.": "在加载有效的支付计划的过程中发生了错误。", - "An error occurred while loading your user account.": "在加载您的账号的过程中发生了错误。", - "Credit_Card_Ending": "使用以 {{card_last4}} 结尾的信用卡", - - "Email": "邮箱", - "Full Name": "姓名", - "Email Address": "邮箱地址", - "Email address (no spam)": "邮箱地址(无垃圾邮件)", - "Notifications": "通知", - "Password": "密码", - "Current Password": "当前密码", - "Current Password is required.": "请填写当前密码。", - "New Password": "新密码", - "New Password is required.": "请填写新密码。", - "New Password must be at least 6 characters long.": "新密码不能少于6个字符。", - "New Password must be less than 101 characters long.": "新密码不能多于101个字符。", - "Confirm password": "确认密码", - "New Password and Confirmation Password fields do not match.": "新密码与确认密码不一致。", - "Confirm Password is required.": "请填写确认密码。", - "Confirm Password must be at least 6 characters long.": "确认密码不能少于6个字符。", - "Confirm Password must be less than 101 characters long.": "确认密码不能多于101个字符。", - "Set Password": "设置密码", - "Changing Password": "修改密码", - "DELETE ACCOUNT": "删除帐户", - "External Logins": "外部账号", - "Add an external login": "新增外部账号", - "Existing external logins": "现有外部账号", - "Remove": "移除", - "No external logins were found.": "尚未发现外部账号。", - "Log in using your Microsoft account": "使用您的微软账号登录", - "Log in using your Google account": "使用您的谷歌账号登录", - "Log in using your Facebook account": "使用您的Facebook账号登录", - "Log in using your GitHub account": "使用您的Github账号登录", - "An error occurred while adding external login.": "在增加外部账号的过程中发生了错误。", - "An error occurred while loading the notification settings.": "在加载通知设置的过程中发生了错误。", - "An error occurred while loading the projects.": "在加载项目的过程中发生了错误。", - "An error occurred while loading your user profile.": "在加载您的个人资料的过程中发生了错误。", - "Are you sure you want to delete your account?": "您确认要删除您的账号?", - "Successfully removed your user account.": "已经成功移除了您的账号。", - "An error occurred while trying remove your user account.": "在移除您的账号的过程中发生了错误。", - "An error occurred while sending your verification email.": "在给您发送验证邮件的过程中发生了错误。", - "An error occurred while saving your email address.": "在保存您的邮箱地址的过程中发生了错误。", - "An error occurred while saving your notification settings.": "在保存您的通知设置的过程中发生了错误。", - "An error occurred while saving your email notification preferences.": "在保存您的邮件通知偏好的过程中发生了错误。", - "An error occurred while saving your full name.": "在保存您的姓名的过程中发生了错误。", - "An error occurred while removing the external login.": "在移除您的外部账号的过程中发生了错误。", - "Choose how often you want to receive notifications for event occurrences in this project.": "请选择您希望以什么样的频率接收此项目的通知。", - "Are you sure you want to add the admin role for this user?": "您确定要为此用户增加管理员角色?", - "Are you sure you want to remove the admin role from this user?": "您确认要移除此用户的管理员角色?", - "An error occurred while add the admin role.": "在增加管理员角色的过程中出现了错误。", - "An error occurred while remove the admin role.": "在移除管理员角色的过程中出现了错误。", - - "Log in": "登录", - "after": "后", - "before": "以前", - "with": "与", - "OR": "或者", - "Login with": "登录", - "Signup": "注册", - "Forgot password?": "忘记密码?", - "Create an account": "创建帐户", - "Create My Account": "创建我的帐户", - "Log In": "登录", - "Change password": "修改密码", - "Reset your password": "重置密码", - "Forgot Password": "忘记密码", - "NOTICE_SIGNINGUP": "阅读并接受隐私保护声明 及 服务条款。", - "Loggin_Failed_Message": "登录过程中出现错误。如需了解更多信息,请联系客户服务人员。", - "Login_Invalid_Credentials": "电子邮件或密码无效。", - "Unable_to_connect_to": "无法连接", - "ResetPassword_Failed_Message": "重置密码的过程中出现错误。", - "ResetPassword_Success_Message": "包含修改密码说明的邮件已经发送到您的邮箱。", - "An error occurred while signing up. Please contact support for more information.": "在注册的过程中发生了错误。如需了解更多信息,请联系客户服务人员。", - "You have successfully changed your password.": "已经成功地修改了您的密码。", - "An error occurred while trying to change your password.": "在修改您的密码的过程中发生了错误。", - "Successfully verified your account.": "您的账号已成功验证。", - "An error occurred while verifying your account.": "在验证您的账号的过程中发生了错误。", - - "AdminRole": "管理员", - "Invited": "已邀请", - "Resend": "重新发送", - "Your first and last name": "您的姓名", - "verification email.": "验证邮件。", - "Email not verified.": "邮箱地址尚未验证。", - "Email notifications are currently disabled.": "邮件通知已被禁用。", - "To enable email notifications you must first verify your email address.": "如需启用邮件通知,首先必须验证您的邮箱地址。", - "Enable email notifications": "启用邮件通知", - "Send daily project summary": "发送每日项目简报", - "Notify me on new errors": "通知我新的错误", - "Notify me on critical errors": "通知我严重错误", - "Notify me on error regressions": "通知我回归错误", - "Notify me on new events": "通知我新的事件", - "Notify me on critical events": "通知我关键事件", - "Upgrade now": "现在升级", - "to enable occurrence level notifications!": "以启用实时邮件通知!", - "Resend Invite Email": "重新发送邀请邮件", - "Revoke Invite": "撤销邀请", - "Remove User": "移除用户", - "Add Admin Role": "添加管理员角色", - "Remove Admin Role": "移除管理员角色", - "No users were found.": "找不到用户。", - "Are you sure you want to remove this user from your organization?": "您确认要从您的组织中删除此用户吗?", - "An error occurred while trying to remove the user.": "在尝试删除此用户的过程中发生了错误。", - "An error occurred while trying to resend the notification.": "在尝试重发通知的过程中发生了错误。", - - "Create New Web Hook": "新增Web Hook", - "Url": "链接地址", - "Enter the URL to call": "输入被调用的链接地址", - "URL is required.": "请输入链接地址。", - "Control when the web hook is called by choosing the event types below.": "选择下面的事件类型以控制何时对 Web Hook 进行调用。", - "Please choose one or more event types.": "请至少选择一个事件类型。", - "Create Web Hook": "创建 Web Hook", - "New Error": "新的错误", - "Occurs when a new error that has never been seen before is reported to your project.": "当您的项目中发生尚未出现过得错误的情况下触发。", - "Critical Error": "关键错误", - "Occurs when an error that has been marked as critical is reported to your project.": "当您的项目中的错误被标记为关键事件的情况下触发。", - "Regression": "回归事件", - "Occurs when an event that has been marked as fixed has reoccurred in your project.": "当您的项目中出现被标记为\"已修复\"的事件的情况下触发。", - "Occurs when a new event that has never been seen before is reported to your project.": "当您的项目中出现尚未被记录过的事件的情况下触发。", - "Critical Event": "关键事件", - "Occurs when an event that has been marked as critical is reported to your project.": "当您的项目中的事件被标记为关键事件的情况下触发。", - "Promoted": "外部推送", - "Used to promote event stacks to external systems.": "用于将堆栈推送到外部系统。", - - "Delete": "删除", - "Cancel": "取消", - "Apply": "确认", - "DateTime": "{{date | date: 'yyyy年M月d日 H:mm:ss'}}", - "Go To Most Frequent": "前往最常去的地方", - "Save": "保存", - "Edit": "修改", - "to": "至", - - "Email Address is required.": "请填写邮箱地址", - "Password is required": "请填写密码", - "Password must be at least 6 characters long": "请填写长度不小于6位的密码", - "Password must be less than 101 characters long": "请填写长度不大于101位的密码", - "Signup for a FREE account in seconds": "在几秒钟内注册一个免费帐户", - "Full Name is required.": "请填写姓名", - "A user already exists with this email address.": "邮箱已存在", - "Already have an account?": "已经有账户?", - "API rate limit exceeded": "API 调用频率超限", - "Your API rate limit has been exceeded. Please try again in 15 minutes.": "您的应用程序调用 API 的频率超限。请在15分钟之后重试。", - "to enable premium features and extra storage!": "以启用高级特性及更多的存储空间!", - "to enable search and other premium features!": "以启用搜索及其他高级特性!", - "is currently on a free plan.": "当前在使用免费计划。", - "has reached its monthly plan limit.": "已经达到了月度使用计划上限。", - "to continue receiving events.": "以继续接收日志。", - "to increase your limits.": "以提升您的上限。", - "Events are currently being throttled to prevent using up your plan limit in a small window of time.": "当前系统会进行日志量的节流控制,以避免在短时间内超出您的使用计划上限。", - "Events are currently being throttled for": "当前系统为这些组织进行了日志量的节流控制:", - "API requests are currently being throttled for": "当前系统为这些组织进行了 API 请求的节流控制:", - "Please note that while your account is suspended all new client events will be discarded.": "请注意您的账户已经被冻结,所有客户端发送的日志都会被丢弃。", - "We haven't received any data!": "我们还没接收到任何数据!", - "Setup your first project": "设置您的第一个项目", - "Copied!": "复制好了!", - "Message:": "错误消息:", - "Allowed": "容许量", - "Blocked": "阻滞量", - "Too Big": "超型量", - "Limit": "上限量", - "Allowed in Organization": "组织容许量", - "Occurrences": "数量", - "Log source": "日志来源", - "Add": "新增", - "Total": "总计", - - "Log Level": "日志级别", - "trace": "跟踪", - "debug": "除错", - "info": "信息", - "warn": "警告", - "error": "错误", - "fatal": "致命", - "off": "关", - "Default": "默认", - "Notice_Default_Log_Level": "默认日志级别控制日志事件应接受的最低日志级别。 日志级别也可以在日志堆栈级别覆盖。", - - "DIALOGS_ERROR": "错误", - "DIALOGS_ERROR_MSG": "出现未知错误。", - "DIALOGS_CLOSE": "关闭", - "DIALOGS_PLEASE_WAIT": "请稍候", - "DIALOGS_PLEASE_WAIT_ELIPS": "请稍候...", - "DIALOGS_PLEASE_WAIT_MSG": "请等待操作完成。", - "DIALOGS_PERCENT_COMPLETE": "% 已完成", - "DIALOGS_NOTIFICATION": "通知", - "DIALOGS_NOTIFICATION_MSG": "未知应用程序的通知。", - "DIALOGS_CONFIRMATION": "确认", - "DIALOGS_CONFIRMATION_MSG": "确认要求。", - "DIALOGS_JSON": "JSON", - "DIALOGS_OK": "确定", - "DIALOGS_YES": "确认", - "DIALOGS_NO": "取消", - - "NOTICE_GRAVATAR": "您的头像是根据下面所填写的邮箱地址调用 Gravatar 的服务生成。" -} diff --git a/src/Exceptionless.Web/ClientApp.angular/less/alerts.less b/src/Exceptionless.Web/ClientApp.angular/less/alerts.less deleted file mode 100644 index 3c94970bf9..0000000000 --- a/src/Exceptionless.Web/ClientApp.angular/less/alerts.less +++ /dev/null @@ -1,85 +0,0 @@ -.alert-banner { - border-radius: 0px; -} - -.system-notification-banner { - position: relative; - min-height: 44px; - padding: 11px 48px 11px 52px; - border-width: 0 0 1px; - box-shadow: none; - font-size: @font-size-base; - line-height: @line-height-computed; - - &.alert-info { - color: #245b30; - background-color: #e7f6e9; - border-color: #b8ddb9; - } - - .system-notification-icon { - position: absolute; - top: 50%; - left: 20px; - color: currentColor; - font-family: FontAwesome; - font-size: @font-size-md; - line-height: 1; - transform: translateY(-50%); - - &:before { - content: "\f05a"; - } - } - - &.alert-warning .system-notification-icon:before { - content: "\f071"; - } - - &.alert-danger .system-notification-icon:before { - content: "\f06a"; - } - - .close { - position: absolute; - top: 50%; - right: 17px; - color: currentColor; - line-height: 1; - transform: translateY(-50%); - } - - .system-notification-message { - > :first-child { - margin-top: 0; - } - - > :last-child { - margin-bottom: 0; - } - - // Messages support HTML and may include legacy alert markup. The banner - // already supplies the level styling, so avoid rendering an alert inside it. - > .alert { - margin: 0; - padding: 0; - border: 0; - background: transparent; - box-shadow: none; - color: inherit; - font-size: inherit; - } - } -} - -.alert-banner a { - text-decoration: underline; -} - -.panel-body .alert a { - text-decoration: underline; -} - -.alert p { - margin-bottom: 0px; -} diff --git a/src/Exceptionless.Web/ClientApp.angular/less/buttons.less b/src/Exceptionless.Web/ClientApp.angular/less/buttons.less deleted file mode 100644 index a716be805c..0000000000 --- a/src/Exceptionless.Web/ClientApp.angular/less/buttons.less +++ /dev/null @@ -1,154 +0,0 @@ -.btn { - font-weight: 500; - border-radius: @btn-border-radius; - outline: 0 !important; -} -.btn-link { - color: @text-color; - &.active { - webkit-box-shadow: none; - box-shadow: none; - } -} - -.btn-default { - .button-variant(#fff, @btn-default-color, @btn-default-color); -} - -.btn-primary { - .button-variant(#fff, @brand-primary, @brand-primary); -} - -.btn-success { - .button-variant(#fff, @brand-success, @brand-success); -} - -.btn-info { - .button-variant(#fff, @brand-info, @brand-info); -} - -.btn-warning { - .button-variant(#fff, @brand-warning, @brand-warning); -} - -.btn-danger { - .button-variant(#fff, @brand-danger, @brand-danger); -} - -.btn-dark { - .button-variant(#fff, @brand-dark, @brand-dark); -} - -.btn-black { - .button-variant(#fff, @brand-black, @brand-black); -} - -.btn-icon { - padding: 0 !important; - text-align: center; - width: 34px; - height: 34px; - i { - top: -1px; - position: relative; - line-height: 34px; - } - &.btn-sm { - width: 30px; - height: 30px; - i { - line-height: 30px; - } - } - &.btn-lg { - width: 45px; - height: 45px; - i { - line-height: 45px; - } - } -} - -.btn-rounded { - border-radius: 50px; - padding-left: 15px; - padding-right: 15px; - &.btn-lg { - padding-left: 25px; - padding-right: 25px; - } -} - -.btn { - > i { - &.pull-left, - &.pull-right { - line-height: @line-height-base; - } - } -} - -.btn-block { - padding-left: 12px; - padding-right: 12px; -} - -.btn-group-vertical > .btn:first-child:not(:last-child) { - border-top-right-radius: @border-radius-base; -} - -.btn-group-vertical > .btn:last-child:not(:first-child) { - border-bottom-left-radius: @border-radius-base; -} - -.btn-addon { - i { - margin: -7px -12px; - margin-right: 12px; - background-color: rgba(0, 0, 0, 0.1); - width: 34px; - height: 34px; - line-height: 34px; - text-align: center; - float: left; - position: relative; - border-radius: @btn-border-radius 0 0 @btn-border-radius; - &.pull-right { - margin-right: -12px; - margin-left: 12px; - border-radius: 0 @btn-border-radius @btn-border-radius 0; - } - } - &.btn-sm { - i { - margin: -6px -10px; - margin-right: 10px; - width: 30px; - height: 30px; - line-height: 30px; - &.pull-right { - margin-right: -10px; - margin-left: 10px; - } - } - } - &.btn-lg { - i { - margin: -11px -16px; - margin-right: 16px; - width: 45px; - height: 45px; - line-height: 45px; - &.pull-right { - margin-right: -16px; - margin-left: 16px; - } - } - } - &.btn-default { - i { - background-color: transparent; - border-right: 1px solid @border-color; - } - } -} diff --git a/src/Exceptionless.Web/ClientApp.angular/less/colors.less b/src/Exceptionless.Web/ClientApp.angular/less/colors.less deleted file mode 100644 index 783e4d43e1..0000000000 --- a/src/Exceptionless.Web/ClientApp.angular/less/colors.less +++ /dev/null @@ -1,121 +0,0 @@ -.bg-gd { - #gradient > .vertical(rgba(40,50,60,0), rgba(40,50,60,0.075), 0, 100%);; - filter: none; -} - -.bg-gd-dk { - #gradient > .vertical(rgba(40,50,60,0), rgba(40,50,60,0.5), 10%, 100%);; - filter: none; -} - -.bg-white-opacity { - background-color: rgba(255, 255, 255, 0.5); -} -.bg-black-opacity { - background-color: rgba(32, 43, 54, 0.5); -} - -.bg-light { - .color-variant(@brand-light, 2%, 3%, 3%, 5%); - color: @text-color; -} - -.bg-dark { - .color-variant(@brand-dark, 5%, 10%, 5%, 10%); - .font-variant(@brand-dark, lighten(@brand-dark,40%), lighten(@brand-dark,50%), #fff); -} - -.bg-dark .nav > li > a { - color: #e1e1e1; -} - -.bg-dark .nav > li:hover > a, -.bg-dark .nav > li:focus > a { - background-color: #4e8018; -} - -.bg-dark .nav > li.active > a { - background-color: #494545; -} - -.bg-dark .nav.nav-sub > li.active > a { - background-color: #64a500; -} - -.bg-black { - .color-variant(@brand-black, 5%, 10%, 5%, 10%); - .font-variant(@brand-black, lighten(@brand-black,40%), lighten(@brand-black,50%), #fff); -} - -.bg-primary { - .color-variant(@brand-primary, 5%, 10%, 5%, 10%); - .font-variant(@brand-primary, lighten(@brand-primary,40%), lighten(@brand-primary,50%), #fff); -} - -.bg-success { - .color-variant(@brand-success, 5%, 10%, 5%, 10%); - .font-variant(@brand-success, lighten(@brand-success,40%), lighten(@brand-success,50%), #fff); -} - -.bg-info { - .color-variant(@brand-info, 5%, 10%, 5%, 10%); - .font-variant(@brand-info, lighten(@brand-info,40%), lighten(@brand-info,50%), #fff); -} - -.bg-warning { - .color-variant(@brand-warning, 5%, 10%, 5%, 10%); - .font-variant(@brand-warning, lighten(@brand-warning,40%), lighten(@brand-warning,50%), #fff); -} - -.bg-danger { - .color-variant(@brand-danger, 5%, 10%, 5%, 10%); - .font-variant(@brand-danger, lighten(@brand-danger,40%), lighten(@brand-danger,50%), #fff); -} - -.bg-white { - background-color: #fff; - color: @text-color; - a { - color: @link-color; - &:hover { - color: darken(@link-color, 10%); - } - } - .text-muted { - color: @text-muted !important; - } -} -.bg-white-only { - background-color: #fff; -} - -a.bg-light { - &:hover { - color: @link-color; - } -} - -.text-wariant(@brand-primary, primary); -.text-wariant(@brand-info, info); -.text-wariant(@brand-success, success); -.text-wariant(@brand-warning, warning); -.text-wariant(@brand-danger, danger); -.text-wariant(@brand-dark, dark); -.text-wariant(@brand-black, black); - -.text-white { - color: #fff; -} -.text-muted { - color: @text-muted; -} - -.glyphicon.glyphicon-one-fine-dot:before { - content: "\25cf"; - font-size: 1.5em; - line-height: 1rem; -} - -.glyphicon-green { - color: #64a500; -} diff --git a/src/Exceptionless.Web/ClientApp.angular/less/components.less b/src/Exceptionless.Web/ClientApp.angular/less/components.less deleted file mode 100644 index e6bc5cfea2..0000000000 --- a/src/Exceptionless.Web/ClientApp.angular/less/components.less +++ /dev/null @@ -1,282 +0,0 @@ -.switch { - cursor: pointer; - position: relative; - display: inline-block; - width: @switch-width; - height: @switch-height; - border-radius: 30px; - background-color: @brand-success; - margin: 0; - input { - position: absolute; - .opacity(0); - &:checked { - + i { - &:before { - top: 50%; - bottom: 50%; - left: 50%; - right: 5px; - border-width: 0; - border-radius: 5px; - } - &:after { - margin-left: @switch-width - @switch-height + 1; - } - } - } - } - i { - &:before { - content: ""; - position: absolute; - top: -1px; - bottom: -1px; - left: -1px; - right: -1px; - background-color: #fff; - border: 1px solid #f0f0f0; - border-radius: 30px; - .transition(all 0.2s); - } - &:after { - content: ""; - position: absolute; - background-color: #fff; - width: @switch-height - 2; - top: 1px; - bottom: 1px; - border-radius: 50%; - .box-shadow(1px 1px 3px rgba(0, 0, 0, 0.25)); - .transition(margin-left 0.3s); - } - } -} - -.switch-md { - width: @switch-md-width; - height: @switch-md-height; - input { - &:checked { - + i { - &:after { - margin-left: @switch-md-width - @switch-md-height + 1; - } - } - } - } - i { - &:after { - width: @switch-md-height - 2; - } - } -} - -.switch-lg { - width: @switch-lg-width; - height: @switch-lg-height; - input { - &:checked { - + i { - &:after { - margin-left: @switch-lg-width - @switch-lg-height + 1; - } - } - } - } - i { - &:after { - width: @switch-lg-height - 2; - } - } -} - -.switch-text { - float: right !important; - margin-top: -1px; - padding-left: 7px; - cursor: pointer; -} - -.checks { - padding-left: 20px; - cursor: pointer; - input { - opacity: 0; - position: absolute; - margin-left: -20px; - &:checked + i { - border-color: @brand-info; - &:before { - left: 4px; - top: 4px; - width: 10px; - height: 10px; - background-color: @brand-info; - } - } - &:checked + span .active { - display: inherit; - } - &[type="radio"] + i { - &, - &:before { - border-radius: 50%; - } - } - &[type="checkbox"]:checked + i:before { - } - &[type="radio"]:checked + i:before { - } - &[disabled], - fieldset[disabled] & { - & + i { - border-color: lighten(@input-border, 5%); - &:before { - background-color: lighten(@input-border, 5%); - } - } - } - } - > i { - width: 20px; - height: 20px; - line-height: 1; - border: 1px solid @input-border; - background-color: #fff; - margin-left: -20px; - margin-top: -2px; - display: inline-block; - vertical-align: middle; - margin-right: 4px; - position: relative; - &:before { - content: ""; - position: absolute; - left: 10px; - top: 10px; - width: 0px; - height: 0px; - background-color: transparent; - .transition(all 0.2s); - } - } - > span { - margin-left: -20px; - .active { - display: none; - } - } -} - -// ui.bootstrap datepicker -.datepicker { - margin: 0 5px; -} -.datepicker .btn-default { - border-width: 0; - box-shadow: none; -} -.datepicker .btn[disabled] { - opacity: 0.4; -} -.datepicker .btn-info .text-info { - color: #fff !important; -} - -.pagination > li > a, -.pagination > li > span { - color: @brand-success; -} - -.pagination > .disabled > span, -.pagination > .disabled > span:hover, -.pagination > .disabled > span:focus, -.pagination > .disabled > a, -.pagination > .disabled > a:hover, -.pagination > .disabled > a:focus { - color: #d0d0d0; -} - -.pagination > li > a:hover, -.pagination > li > span:hover, -.pagination > li > a:focus, -.pagination > li > span:focus { - color: @brand-success; -} - -a.dashboard-block:hover, -a.dashboard-block:active, -a.dashboard-block:focus { - color: rgba(255, 255, 255, 0.8); -} - -.dashboard-block { - background-color: #6ebc1a; - position: relative; - display: block; - padding: 20px 20px; - opacity: 0.9; - border-radius: 5px; - overflow: hidden; - margin-bottom: 15px; - border: 1px solid @border-color; - color: #fff; - .rotate { - z-index: 8; - float: right; - height: 100%; - i { - color: rgba(20, 20, 20, 0.2); - position: absolute; - left: 0; - left: auto; - right: 10px; - bottom: 10px; - display: block; - } - } - .more { - position: absolute; - top: 10px; - right: 10px; - font-size: 16px; - color: rgba(0, 0, 0, 0.25) !important; - text-shadow: none; - } - .details { - position: relative; - z-index: 10; - float: left; - margin-top: -10px; - text-align: left; - } - .title { - display: block; - margin-bottom: 1em; - font-size: 13px; - text-transform: uppercase; - } - .sub { - display: block; - font-size: 32px; - } - .subAlternative { - display: block; - color: rgba(20, 20, 20, 0.5); - font-size: 18px; - padding-top: 10px; - - abbr[title] { - border-bottom: 1px dotted rgba(20, 20, 20, 0.8); - } - } - abbr[title] { - border-bottom: 1px dotted #fff; - } -} - -.scrollable-menu { - height: auto; - overflow-x: hidden; -} diff --git a/src/Exceptionless.Web/ClientApp.angular/less/forms.less b/src/Exceptionless.Web/ClientApp.angular/less/forms.less deleted file mode 100644 index 4afc09a27b..0000000000 --- a/src/Exceptionless.Web/ClientApp.angular/less/forms.less +++ /dev/null @@ -1,7 +0,0 @@ -.modal-body .checkbox-inline { - margin: inherit; -} - -.modal-body .checkbox-inline + .checkbox-inline { - margin: inherit; -} diff --git a/src/Exceptionless.Web/ClientApp.angular/less/labels.less b/src/Exceptionless.Web/ClientApp.angular/less/labels.less deleted file mode 100644 index 729fbfdb47..0000000000 --- a/src/Exceptionless.Web/ClientApp.angular/less/labels.less +++ /dev/null @@ -1,9 +0,0 @@ -.panel-heading .label { - display: inline-block; -} - -.label { - display: inline-block; - padding: 0.4em 0.6em 0.4em; - margin-bottom: 3px; -} diff --git a/src/Exceptionless.Web/ClientApp.angular/less/layout.less b/src/Exceptionless.Web/ClientApp.angular/less/layout.less deleted file mode 100644 index 9b740fd638..0000000000 --- a/src/Exceptionless.Web/ClientApp.angular/less/layout.less +++ /dev/null @@ -1,355 +0,0 @@ -/*layout*/ -html, -body { - width: 100%; - height: 100%; -} -body { - min-width: 380px; - overflow-y: scroll; -} - -.app { - height: auto; - min-height: 100%; - width: 100%; - position: relative; -} - -.app-header-fixed { - padding-top: @app-header-height; - .app-header { - position: fixed; - left: 0; - top: 0; - right: 0; - box-shadow: 0 2px 2px rgba(0, 0, 0, 0.25); - } -} - -.app-header { - z-index: 1025; - border-radius: 0; - min-width: 380px; - .user-select(none); -} - -// menu -.app-aside { - position: relative; - float: left; - &:before { - content: ""; - position: fixed; - width: inherit; - top: 0; - bottom: 0; - z-index: -1; - background-color: inherit; - border: inherit; - } -} - -.app-aside-right { - padding-bottom: @app-header-height; -} - -// content -.app-content { - height: 100%; - .clearfix(); -} - -.app-content-body { - padding-bottom: @app-header-height; - float: left; - width: 100%; -} - -// footer -.app-footer { - position: absolute; - bottom: 0; - left: 0; - right: 0; - z-index: 1005; -} - -.hbox { - display: table; - table-layout: fixed; - border-spacing: 0; - width: 100%; - height: 100%; - .col { - display: table-cell; - vertical-align: top; - height: 100%; - float: none; - } -} - -.v-middle { - vertical-align: middle !important; -} -.v-top { - vertical-align: top !important; -} -.v-bottom { - vertical-align: bottom !important; -} - -.vbox { - display: table; - border-spacing: 0; - position: relative; - width: 100%; - height: 100%; - min-height: 320px; - .row-row { - display: table-row; - height: 100%; - .cell { - position: relative; - height: 100%; - width: 100%; - -webkit-overflow-scrolling: touch; - overflow: auto; - .ie & { - display: table-cell; - } - .cell-inner { - position: absolute; - top: 0; - bottom: 0; - left: 0; - right: 0; - } - } - } -} - -// navbar - -.navbar { - .navbar-form-sm { - margin-top: 10px; - margin-bottom: 10px; - } - border: none !important; - margin: 0; -} - -.navbar-md { - min-height: @app-header-md-height; - .navbar-btn { - margin-top: 13px; - } - .navbar-form { - margin-top: 15px; - } - .navbar-nav > li > a { - padding-top: 20px; - padding-bottom: 20px; - } - .navbar-brand { - line-height: 60px; - } -} - -.navbar-header { - > button { - text-decoration: none; - line-height: 30px; - font-size: 16px; - padding: 10px 17px; - border: none; - background-color: transparent; - } -} - -.navbar-brand { - float: none; - text-align: center; - font-size: 20px; - font-weight: 700; - height: auto; - line-height: @app-header-height; - display: inline-block; - padding: 0 20px; - &:hover { - text-decoration: none; - } - img { - height: 38px; - margin-top: 7px; - margin-left: 0px; - vertical-align: middle; - margin-bottom: 0px; - } - img.icon { - margin-top: 5px; - height: 40px; - margin-left: 10px; - display: none; - } -} - -@media (min-width: 768px) { - .app-aside-folded .navbar-brand img.icon { - display: block; - } -} - -@media (min-width: 768px) { - body.container { - .box-shadow(0 3px 60px rgba(0,0,0,0.3)); - border-left: 1px solid darken(@border-color, 10%); - border-right: 1px solid darken(@border-color, 10%); - } - - .app-aside, - .navbar-header { - width: @app-aside-width; - } - .app-content, - .app-footer { - margin-left: @app-aside-width; - } - - .app-aside-right { - position: absolute; - top: @app-header-height; - bottom: 0; - right: 0; - z-index: 1000; - } - - .visible-folded { - display: none; - } - - .app-aside-folded { - .hidden-folded { - display: none !important; - } - .visible-folded { - display: inherit; - } - .text-center-folded { - text-align: center; - } - .pull-none-folded { - float: none !important; - } - .w-auto-folded { - width: auto; - } - - .app-aside, - .navbar-header { - width: @app-aside-folded-width; - } - .app-content, - .app-footer { - margin-left: @app-aside-folded-width; - } - .app-header { - .navbar-brand { - display: block; - padding: 0; - } - } - } - - .app-aside-fixed { - .app-header { - .navbar-header { - position: fixed; - } - .navbar-collapse { - padding-left: @app-aside-width; - } - } - .aside-wrap { - position: fixed; - overflow: hidden; - top: @app-header-height; - left: 0; - bottom: 0; - width: @app-aside-width; - z-index: 1000; - .navi-wrap { - width: @app-aside-width + @scroll-bar-width; - position: relative; - height: 100%; - overflow-x: hidden; - overflow-y: scroll; - -webkit-overflow-scrolling: touch; - &::-webkit-scrollbar { - -webkit-appearance: none; - } - &::-webkit-scrollbar:vertical { - width: @scroll-bar-width; - } - } - .smart & .navi-wrap { - width: @app-aside-width; - } - } - &.app-aside-folded { - .app-header { - .navbar-collapse { - padding-left: @app-aside-folded-width; - } - } - .aside-wrap { - width: @app-aside-folded-width; - .navi-wrap { - width: @app-aside-folded-width + @scroll-bar-width; - } - .smart & .navi-wrap { - width: @app-aside-folded-width; - } - } - } - } - - .bg-auto { - &:before { - content: ""; - position: absolute; - width: inherit; - top: 0; - bottom: 0; - z-index: -2; - background-color: inherit; - border: inherit; - } - &.bg-auto-left { - &:before { - left: @app-aside-width; - .app-aside-folded & { - left: @app-aside-folded-width; - } - } - } - &.bg-auto-right { - &:before { - right: 0; - } - } - } - - .col.show { - display: table-cell !important; - } -} - -@media screen and (max-width: 767px) { - input, - select, - textarea { - font-size: 16px !important; - } -} diff --git a/src/Exceptionless.Web/ClientApp.angular/less/list-group.less b/src/Exceptionless.Web/ClientApp.angular/less/list-group.less deleted file mode 100644 index b6e9568b98..0000000000 --- a/src/Exceptionless.Web/ClientApp.angular/less/list-group.less +++ /dev/null @@ -1,10 +0,0 @@ -.list-group-footer { - border-color: @panel-heading-border; - border-radius: 0 0 @panel-border-radius @panel-border-radius; - background-color: @panel-footer-bg; -} - -.app-header .bg-dark .list-group-footer { - border-top: 2px solid #6b6b6b; - background-color: #494545; -} diff --git a/src/Exceptionless.Web/ClientApp.angular/less/mixins.less b/src/Exceptionless.Web/ClientApp.angular/less/mixins.less deleted file mode 100644 index 9347948f36..0000000000 --- a/src/Exceptionless.Web/ClientApp.angular/less/mixins.less +++ /dev/null @@ -1,504 +0,0 @@ -.color-variant(@bg-color: #555, @lt-percent: 10%, @lter-percent: 15%, @dk-percent: 10%, @dker-percent: 15%) { - background-color: @bg-color; - &.lt, - .lt { - .color-schema(@bg-color, -@lt-percent, -2.5%); - } - &.lter, - .lter { - .color-schema(@bg-color, -@lter-percent, -5%); - } - &.dk, - .dk { - .color-schema(@bg-color, @dk-percent, 2.5%); - } - &.dker, - .dker { - .color-schema(@bg-color, @dker-percent, 5%); - } - &.bg, - .bg { - background-color: @bg-color; - } -} - -.color-schema(@bg-color: #555, @percent: 15%, @sat-percent) { - background-color: saturate(darken(@bg-color, @percent), @sat-percent); -} - -.font-variant(@bg-color, @font-color, @link-color: #fff, @hover-color: #fff) { - color: @font-color; - a { - color: @link-color; - &:hover { - color: @hover-color; - } - &.list-group-item { - &:hover, - &:focus { - background-color: inherit; - } - } - } - .nav { - > li { - &:hover, - &:focus, - &.active { - > a { - color: @hover-color; - .color-schema(@bg-color, 5%, 2.5%); - } - } - > a { - color: darken(@link-color, 5%); - &:hover, - &:focus { - .color-schema(@bg-color, 3%, 2.5%); - } - } - } - .open > a { - .color-schema(@bg-color, 5%, 2.5%); - } - } - .caret { - border-top-color: @font-color; - border-bottom-color: @font-color; - } - &.navbar .nav { - > li.active > a { - color: @hover-color; - .color-schema(@bg-color, 5%, 2.5%); - } - } - .open > a { - &, - &:hover, - &:focus { - color: @hover-color; - } - } - .text-muted { - color: darken(@font-color, 10%) !important; - } - .text-lt { - color: lighten(@font-color, 25%) !important; - } - &.auto, - .auto { - .list-group-item { - border-color: darken(@bg-color, 5%) !important; - background-color: transparent; - &:hover, - &:focus, - &:active, - &.active { - .color-schema(@bg-color, 5%, 2.5%) !important; - } - } - } -} - -.text-wariant(@bg-color, @name) { - a.bg-@{name}:hover { - background-color: darken(@bg-color, 5%); - } - a.text-@{name}:hover { - color: darken(@bg-color, 5%); - } - .text-@{name} { - color: @bg-color; - } - .text-@{name}-lt { - color: lighten(@bg-color, 5%); - } - .text-@{name}-lter { - color: lighten(@bg-color, 10%); - } - .text-@{name}-dk { - color: darken(@bg-color, 5%); - } - .text-@{name}-dker { - color: darken(@bg-color, 10%); - } -} - -.clearfix() { - &:before, - &:after { - content: " "; - display: table; - } - &:after { - clear: both; - } -} - -// Button variants -// ------------------------- -// Easily pump out default styles, as well as :hover, :focus, :active, -// and disabled options for all buttons -.button-variant(@color; @background; @border) { - color: @color !important; - background-color: @background; - border-color: @border; - - &:hover, - &:focus, - &:active, - &.active, - .open .dropdown-toggle& { - color: @color !important; - background-color: darken(@background, 5%); - border-color: darken(@border, 8%); - } - &:active, - &.active, - .open .dropdown-toggle& { - background-image: none; - } - &.disabled, - &[disabled], - fieldset[disabled] & { - &, - &:hover, - &:focus, - &:active, - &.active { - background-color: @background; - border-color: @border; - } - } -} - -.translateZ(@z) { - -webkit-transform: translateZ(@z); - -ms-transform: translateZ(@z); - -o-transform: translateZ(@z); - transform: translateZ(@z); -} - -// CSS3 PROPERTIES -// -------------------------------------------------- - -// Single side border-radius -.border-top-radius(@radius) { - border-top-right-radius: @radius; - border-top-left-radius: @radius; -} -.border-right-radius(@radius) { - border-bottom-right-radius: @radius; - border-top-right-radius: @radius; -} -.border-bottom-radius(@radius) { - border-bottom-right-radius: @radius; - border-bottom-left-radius: @radius; -} -.border-left-radius(@radius) { - border-bottom-left-radius: @radius; - border-top-left-radius: @radius; -} - -// Drop shadows -.box-shadow(@shadow) { - -webkit-box-shadow: @shadow; // iOS <4.3 & Android <4.1 - box-shadow: @shadow; -} - -// Transitions -.transition(@transition) { - -webkit-transition: @transition; - transition: @transition; -} -.transition-delay(@transition-delay) { - -webkit-transition-delay: @transition-delay; - transition-delay: @transition-delay; -} -.transition-duration(@transition-duration) { - -webkit-transition-duration: @transition-duration; - transition-duration: @transition-duration; -} -.transition-transform(@transition) { - -webkit-transition: -webkit-transform @transition; - -moz-transition: -moz-transform @transition; - -o-transition: -o-transform @transition; - transition: transform @transition; -} - -// Transformations -.rotate(@degrees) { - -webkit-transform: rotate(@degrees); - -ms-transform: rotate(@degrees); // IE9+ - transform: rotate(@degrees); -} -.scale(@ratio) { - -webkit-transform: scale(@ratio); - -ms-transform: scale(@ratio); // IE9+ - transform: scale(@ratio); -} -.translate(@x, @y) { - -webkit-transform: translate(@x, @y); - -ms-transform: translate(@x, @y); // IE9+ - transform: translate(@x, @y); -} -.skew(@x, @y) { - -webkit-transform: skew(@x, @y); - -ms-transform: skewX(@x) skewY(@y); // See https://github.com/twbs/bootstrap/issues/4885; IE9+ - transform: skew(@x, @y); -} -.translate3d(@x, @y, @z) { - -webkit-transform: translate3d(@x, @y, @z); - transform: translate3d(@x, @y, @z); -} - -// Backface visibility -// Prevent browsers from flickering when using CSS 3D transforms. -// Default value is `visible`, but can be changed to `hidden` -// See git pull https://github.com/dannykeane/bootstrap.git backface-visibility for examples -.backface-visibility(@visibility) { - -webkit-backface-visibility: @visibility; - -moz-backface-visibility: @visibility; - backface-visibility: @visibility; -} - -// Box sizing -.box-sizing(@boxmodel) { - -webkit-box-sizing: @boxmodel; - -moz-box-sizing: @boxmodel; - box-sizing: @boxmodel; -} - -// User select -// For selecting text on the page -.user-select(@select) { - -webkit-user-select: @select; - -moz-user-select: @select; - -ms-user-select: @select; // IE10+ - -o-user-select: @select; - user-select: @select; -} - -// Resize anything -.resizable(@direction) { - resize: @direction; // Options: horizontal, vertical, both - overflow: auto; // Safari fix -} - -// CSS3 Content Columns -.content-columns(@column-count, @column-gap: @grid-gutter-width) { - -webkit-column-count: @column-count; - -moz-column-count: @column-count; - column-count: @column-count; - -webkit-column-gap: @column-gap; - -moz-column-gap: @column-gap; - column-gap: @column-gap; -} - -// Optional hyphenation -.hyphens(@mode: auto) { - word-wrap: break-word; - -webkit-hyphens: @mode; - -moz-hyphens: @mode; - -ms-hyphens: @mode; // IE10+ - -o-hyphens: @mode; - hyphens: @mode; -} - -// Opacity -.opacity(@opacity) { - opacity: @opacity; - // IE8 filter - @opacity-ie: (@opacity * 100); - filter: ~"alpha(opacity=@{opacity-ie})"; -} - -// GRADIENTS -// -------------------------------------------------- - -#gradient { - // Horizontal gradient, from left to right - // - // Creates two color stops, start and end, by specifying a color and position for each color stop. - // Color stops are not available in IE9 and below. - .horizontal(@start-color: #555, @end-color: #333, @start-percent: 0%, @end-percent: 100%) { - background-image: -webkit-gradient( - linear, - @start-percent top, - @end-percent top, - from(@start-color), - to(@end-color) - ); // Safari 4+, Chrome 2+ - background-image: -webkit-linear-gradient( - left, - color-stop(@start-color @start-percent), - color-stop(@end-color @end-percent) - ); // Safari 5.1+, Chrome 10+ - background-image: -moz-linear-gradient(left, @start-color @start-percent, @end-color @end-percent); // FF 3.6+ - background-image: linear-gradient( - to right, - @start-color @start-percent, - @end-color @end-percent - ); // Standard, IE10 - background-repeat: repeat-x; - filter: e( - %( - "progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=1)", - argb(@start-color), - argb(@end-color) - ) - ); // IE9 and down - } - - // Vertical gradient, from top to bottom - // - // Creates two color stops, start and end, by specifying a color and position for each color stop. - // Color stops are not available in IE9 and below. - .vertical(@start-color: #555, @end-color: #333, @start-percent: 0%, @end-percent: 100%) { - background-image: -webkit-gradient( - linear, - left @start-percent, - left @end-percent, - from(@start-color), - to(@end-color) - ); // Safari 4+, Chrome 2+ - background-image: -webkit-linear-gradient( - top, - @start-color, - @start-percent, - @end-color, - @end-percent - ); // Safari 5.1+, Chrome 10+ - background-image: -moz-linear-gradient(top, @start-color @start-percent, @end-color @end-percent); // FF 3.6+ - background-image: linear-gradient( - to bottom, - @start-color @start-percent, - @end-color @end-percent - ); // Standard, IE10 - background-repeat: repeat-x; - filter: e( - %( - "progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)", - argb(@start-color), - argb(@end-color) - ) - ); // IE9 and down - } - - .directional(@start-color: #555, @end-color: #333, @deg: 45deg) { - background-repeat: repeat-x; - background-image: -webkit-linear-gradient(@deg, @start-color, @end-color); // Safari 5.1+, Chrome 10+ - background-image: -moz-linear-gradient(@deg, @start-color, @end-color); // FF 3.6+ - background-image: linear-gradient(@deg, @start-color, @end-color); // Standard, IE10 - } - .horizontal-three-colors(@start-color: #00b3ee, @mid-color: #7a43b6, @color-stop: 50%, @end-color: #c3325f) { - background-image: -webkit-gradient( - left, - linear, - 0 0, - 0 100%, - from(@start-color), - color-stop(@color-stop, @mid-color), - to(@end-color) - ); - background-image: -webkit-linear-gradient(left, @start-color, @mid-color @color-stop, @end-color); - background-image: -moz-linear-gradient(left, @start-color, @mid-color @color-stop, @end-color); - background-image: linear-gradient(to right, @start-color, @mid-color @color-stop, @end-color); - background-repeat: no-repeat; - filter: e( - %( - "progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=1)", - argb(@start-color), - argb(@end-color) - ) - ); // IE9 and down, gets no color-stop at all for proper fallback - } - .vertical-three-colors(@start-color: #00b3ee, @mid-color: #7a43b6, @color-stop: 50%, @end-color: #c3325f) { - background-image: -webkit-gradient( - linear, - 0 0, - 0 100%, - from(@start-color), - color-stop(@color-stop, @mid-color), - to(@end-color) - ); - background-image: -webkit-linear-gradient(@start-color, @mid-color @color-stop, @end-color); - background-image: -moz-linear-gradient(top, @start-color, @mid-color @color-stop, @end-color); - background-image: linear-gradient(@start-color, @mid-color @color-stop, @end-color); - background-repeat: no-repeat; - filter: e( - %( - "progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)", - argb(@start-color), - argb(@end-color) - ) - ); // IE9 and down, gets no color-stop at all for proper fallback - } - .radial(@inner-color: #555, @outer-color: #333) { - background-image: -webkit-gradient( - radial, - center center, - 0, - center center, - 460, - from(@inner-color), - to(@outer-color) - ); - background-image: -webkit-radial-gradient(circle, @inner-color, @outer-color); - background-image: -moz-radial-gradient(circle, @inner-color, @outer-color); - background-image: radial-gradient(circle, @inner-color, @outer-color); - background-repeat: no-repeat; - } - .striped(@color: #555, @angle: 45deg) { - background-image: -webkit-gradient( - linear, - 0 100%, - 100% 0, - color-stop(0.25, rgba(255, 255, 255, 0.15)), - color-stop(0.25, transparent), - color-stop(0.5, transparent), - color-stop(0.5, rgba(255, 255, 255, 0.15)), - color-stop(0.75, rgba(255, 255, 255, 0.15)), - color-stop(0.75, transparent), - to(transparent) - ); - background-image: -webkit-linear-gradient( - @angle, - rgba(255, 255, 255, 0.15) 25%, - transparent 25%, - transparent 50%, - rgba(255, 255, 255, 0.15) 50%, - rgba(255, 255, 255, 0.15) 75%, - transparent 75%, - transparent - ); - background-image: -moz-linear-gradient( - @angle, - rgba(255, 255, 255, 0.15) 25%, - transparent 25%, - transparent 50%, - rgba(255, 255, 255, 0.15) 50%, - rgba(255, 255, 255, 0.15) 75%, - transparent 75%, - transparent - ); - background-image: linear-gradient( - @angle, - rgba(255, 255, 255, 0.15) 25%, - transparent 25%, - transparent 50%, - rgba(255, 255, 255, 0.15) 50%, - rgba(255, 255, 255, 0.15) 75%, - transparent 75%, - transparent - ); - } -} - -// Reset filters for IE -// -// When you need to remove a gradient background, don't forget to use this to reset -// the IE filter for IE9 and below. -.reset-filter() { - filter: e(%("progid:DXImageTransform.Microsoft.gradient(enabled = false)")); -} diff --git a/src/Exceptionless.Web/ClientApp.angular/less/nav.less b/src/Exceptionless.Web/ClientApp.angular/less/nav.less deleted file mode 100644 index 967858c854..0000000000 --- a/src/Exceptionless.Web/ClientApp.angular/less/nav.less +++ /dev/null @@ -1,253 +0,0 @@ -.nav-sub { - opacity: 0; - height: 0; - overflow: hidden; - margin-left: -20px; - .transition(all 0.2s ease-in-out 0s); - - .active &, - .app-aside-folded li:hover &, - .app-aside-folded li:focus &, - .app-aside-folded li:active & { - opacity: 1; - margin-left: 0; - height: auto !important; - overflow: auto; - } -} - -.nav-sub-divider { - height: 1px; - background-color: #777777; - margin-top: 10px; - margin-bottom: 10px; - overflow: hidden; -} - -.nav-sub-header { - display: none !important; - a { - padding: floor(((@app-aside-folded-nav-height - @line-height-computed) / 2)) 20px; - } -} - -.navi ul.nav.nav-sub li a > i { - font-size: 17px; -} - -.navi ul.nav li a > i { - font-size: 20px; -} - -.navi ul.nav li { - font-width: normal; -} - -.aside-wrap .nav { - margin-top: 10px; -} - -.aside-wrap .nav .nav-sub { - margin-top: 0px; -} - -.navi { - ul.nav { - li { - display: block; - position: relative; - li { - a { - padding-left: 35px; - } - } - a { - font-weight: normal; - text-transform: none; - display: block; - padding: floor(((@app-aside-nav-height - @line-height-computed)/2)) 20px; - position: relative; - .transition(background-color 0.2s ease-in-out 0s); - .badge, - .label { - font-size: 11px; - padding: 2px 5px; - margin-top: 2px; - } - > i { - margin: floor((-(@app-aside-nav-height - @line-height-computed)/2)) -10px; - line-height: @app-aside-nav-height; - width: @app-aside-nav-height; - float: left; - margin-right: 5px; - text-align: center; - position: relative; - overflow: hidden; - &:before { - position: relative; - z-index: 2; - } - } - } - } - } -} - -@media (min-width: 768px) { - .app-aside-folded { - .nav-sub-header { - display: block !important; - background-color: rgb(84, 84, 84); - padding: 2px; - padding-left: 10px; - margin: 5px; - cursor: default; - font-size: 14px; - border-radius: 3px; - a { - padding: floor(((@app-aside-folded-nav-height - @line-height-computed)/2)) 20px !important; - } - } - .navi { - > ul { - > li { - > a { - position: relative; - padding: 0; - font-size: 11px; - text-align: center; - height: @app-aside-folded-nav-height; - border: none; - span { - display: none; - &.pull-right { - display: none !important; - } - } - i { - width: auto; - float: none; - display: block; - font-size: 22px; - margin: 0 0 0 -3px; - line-height: @app-aside-folded-nav-height; - border: none !important; - .transition(margin-top 0.2s); - b { - left: 0 !important; - } - } - .badge, - .label { - position: absolute; - right: 12px; - top: 8px; - z-index: 3; - } - } - } - ul { - height: 0 !important; - position: absolute; - left: 100%; - top: 0 !important; - z-index: 1050; - width: @app-aside-width; - .box-shadow(0 2px 6px rgba(0, 0, 0, 0.1)); - } - } - li { - li { - a { - padding-left: 20px !important; - } - } - } - } - } - - .app-aside-folded.app-aside-fixed .app-aside { - > ul.nav { - &:before { - content: ""; - width: @app-aside-folded-width; - height: @app-aside-folded-nav-height; - position: absolute; - left: -@app-aside-folded-width; - top: 0; - } - z-index: 1010; - opacity: 1; - height: auto; - overflow: visible; - overflow-y: auto; - display: block; - width: @app-aside-width + @app-aside-folded-width; - left: @app-aside-folded-width + 20; - position: fixed; - border-radius: 0px 4px 4px 0px; - -webkit-overflow-scrolling: touch; - a { - padding-left: 20px !important; - padding-right: 20px !important; - } - } - } -} - -@media (max-width: 767px) { - .navbar-default .navbar-collapse, - .navbar-default .navbar-form { - // border: 0; - } - - .app { - overflow-x: hidden; - } - - .app-content { - .transition-transform(0.2s ease); - } - - .off-screen { - position: absolute; - top: 50px; - bottom: 0; - width: 75%; - display: block !important; - visibility: visible; - overflow-x: hidden; - overflow-y: auto; - -webkit-overflow-scrolling: touch; - z-index: 1010; - + * { - background-color: @body-bg; - .transition-transform(0.2s ease); - .backface-visibility(hidden); - .translate3d(75%, 0px, 0px); - overflow: hidden; - position: absolute; - width: 100%; - top: 0; - bottom: 0; - left: 0; - right: 0; - z-index: 1015; - padding-top: 50px; - .off-screen-toggle { - display: block !important; - position: absolute; - left: 0; - right: 0; - top: 0; - bottom: 0; - z-index: 1020; - } - } - } -} - -.navbar-default .navbar-collapse, -.navbar-default .navbar-form { - border-color: #656464; -} diff --git a/src/Exceptionless.Web/ClientApp.angular/less/ng.less b/src/Exceptionless.Web/ClientApp.angular/less/ng.less deleted file mode 100644 index e9ce71521f..0000000000 --- a/src/Exceptionless.Web/ClientApp.angular/less/ng.less +++ /dev/null @@ -1,121 +0,0 @@ -.form-validation { - .form-control { - &.ng-dirty.ng-invalid { - border-color: @brand-danger; - } - &.ng-dirty.ng-valid { - &, - &:focus { - border-color: @brand-success; - } - } - } - - .checks { - .ng-invalid.ng-dirty + i { - border-color: @brand-danger; - } - } -} - -.ng-animate .bg-auto:before { - display: none; -} - -[ui-view].ng-leave { - display: none; -} - -[ui-view].ng-leave.smooth { - display: block; -} - -.smooth.ng-animate { - position: absolute; - width: 100%; - height: 100%; - overflow: hidden; -} - -// big animation -.fade-in-right-big.ng-enter { - -webkit-animation: fadeInRightBig 0.5s; - animation: fadeInRightBig 0.5s; -} -.fade-in-right-big.ng-leave { - -webkit-animation: fadeOutLeftBig 0.5s; - animation: fadeOutLeftBig 0.5s; -} - -.fade-in-left-big.ng-enter { - -webkit-animation: fadeInLeftBig 0.5s; - animation: fadeInLeftBig 0.5s; -} -.fade-in-left-big.ng-leave { - -webkit-animation: fadeOutRightBig 0.5s; - animation: fadeOutRightBig 0.5s; -} - -.fade-in-up-big.ng-enter { - -webkit-animation: fadeInUpBig 0.5s; - animation: fadeInUpBig 0.5s; -} -.fade-in-up-big.ng-leave { - -webkit-animation: fadeOutUpBig 0.5s; - animation: fadeOutUpBig 0.5s; -} - -.fade-in-down-big.ng-enter { - -webkit-animation: fadeInDownBig 0.5s; - animation: fadeInDownBig 0.5s; -} -.fade-in-down-big.ng-leave { - -webkit-animation: fadeOutDownBig 0.5s; - animation: fadeOutDownBig 0.5s; -} - -// small -.fade-in.ng-enter { - -webkit-animation: fadeIn 0.5s; - animation: fadeIn 0.5s; -} -.fade-in.ng-leave { - -webkit-animation: fadeOut 0.5s; - animation: fadeOut 0.5s; -} - -.fade-in-right.ng-enter { - -webkit-animation: fadeInRight 0.3s; - animation: fadeInRight 0.3s; -} -.fade-in-right.ng-leave { - -webkit-animation: fadeOutLeft 0.5s; - animation: fadeOutLeft 0.5s; -} - -.fade-in-left.ng-enter { - -webkit-animation: fadeInLeft 0.5s; - animation: fadeInLeft 0.5s; -} -.fade-in-left.ng-leave { - -webkit-animation: fadeOutRight 0.5s; - animation: fadeOutRight 0.5s; -} - -.fade-in-up.ng-enter { - -webkit-animation: fadeInUp 0.3s; - animation: fadeInUp 0.3s; -} -.fade-in-up.ng-leave { - -webkit-animation: fadeOutUp 0.2s; - animation: fadeOutUp 0.2s; -} - -.fade-in-down.ng-enter { - -webkit-animation: fadeInDown 0.5s; - animation: fadeInDown 0.5s; -} -.fade-in-down.ng-leave { - -webkit-animation: fadeOutDown 0.5s; - animation: fadeOutDown 0.5s; -} diff --git a/src/Exceptionless.Web/ClientApp.angular/less/reset.less b/src/Exceptionless.Web/ClientApp.angular/less/reset.less deleted file mode 100644 index f89b07281e..0000000000 --- a/src/Exceptionless.Web/ClientApp.angular/less/reset.less +++ /dev/null @@ -1,728 +0,0 @@ -// reset -html { - background-color: @body-bg; -} -body { - font-family: @font-family-base; - font-size: @font-size-base; - color: @text-color; - background-color: transparent; - -webkit-font-smoothing: antialiased; - line-height: @line-height-base; -} - -*:focus { - outline: 0 !important; -} - -.h1, -.h2, -.h3, -.h4, -.h5, -.h6 { - margin: 0; -} - -// Links -// ------------------------- - -a { - color: @link-color; - text-decoration: underline; - cursor: pointer; -} - -a.btn, -.nav a, -.table-clickable a, -.dropdown-menu a { - text-decoration: none; -} - -a:hover, -a:focus { - color: @link-hover-color; - text-decoration: underline; -} - -abbr[title] { - border-bottom: 1px dotted; - text-decoration: none; -} - -label { - font-weight: normal; -} -small, -.small { - font-size: @font-size-sm; -} -.label { - font-size: 90%; - margin-right: 5px; -} -.badge, -.label { - font-weight: bold; - border-radius: 3px; -} -.badge.bg-light, -.label.bg-light { - text-shadow: none; -} -.badge { - background-color: @badge-bg; - &.up { - position: relative; - top: -10px; - padding: 3px 6px; - margin-left: -10px; - } -} - -.label-success { - background-color: #4e8018; - font-weight: normal; -} - -.label-info { - background-color: #9e9e9e; - font-weight: normal; -} - -.badge-sm { - font-size: 85%; - padding: 2px 5px !important; -} - -.label-sm { - padding-top: 0; - padding-bottom: 1px; -} - -.badge-white { - background-color: transparent; - border: 1px solid rgba(255, 255, 255, 0.35); - padding: 2px 6px; -} - -.badge-empty { - background-color: transparent; - border: 1px solid rgba(0, 0, 0, 0.15); - color: inherit; -} - -blockquote { - border-color: @border-color; -} - -.caret-white { - border-top-color: #fff; - border-top-color: rgba(255, 255, 255, 0.65); - a:hover & { - border-top-color: #fff; - } -} - -.thumbnail { - border-color: @border-color; -} - -.progress { - background-color: @brand-light; -} -.progress-xxs { - height: 2px; -} -.progress-xs { - height: 6px; -} -.progress-sm { - height: 12px; - .progress-bar { - font-size: 10px; - line-height: 1em; - } -} - -.progress, -.progress-bar { - .box-shadow(none); -} - -.progress-bar-primary { - background-color: @brand-primary; -} -.progress-bar-info { - background-color: @brand-info; -} -.progress-bar-success { - background-color: @brand-success; -} -.progress-bar-warning { - background-color: @brand-warning; -} -.progress-bar-danger { - background-color: @brand-danger; -} -.progress-bar-black { - background-color: @brand-black; -} -.progress-bar-white { - background-color: #fff; -} - -.accordion-group, -.accordion-inner { - border-color: @border-color; - border-radius: @border-radius-base; -} - -.alert { - font-size: @font-size-sm; - box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2); - .close i { - font-size: 12px; - font-weight: normal; - display: block; - } -} - -.form-control { - border-color: @input-border; - border-radius: @input-border-radius; - &, - &:focus { - .box-shadow(none); - } - &:focus { - border-color: @input-border-focus; - } -} - -.form-control-spin { - position: absolute; - z-index: 2; - right: 10px; - top: 50%; - margin-top: -7px; -} - -.input-lg { - height: 45px; -} - -.input-group-addon { - border-color: @input-border; - background-color: @brand-light; -} - -.navbar-nav > li > .dropdown-menu { - margin-top: -1px; -} - -.app-header .panel.bg-dark > .list-group .panel-footer { - border-top: 2px solid #6b6b6b; - background-color: #494545; -} - -.app-header .panel.bg-dark > .list-group:first-child .list-group-item:first-child, -.app-header .panel.bg-dark > .panel-collapse > .list-group:first-child .list-group-item:first-child { - border: none; - border-radius: 0; -} - -.app-header .bg-dark .list-group-item { - background-color: #545454; - border: none; - color: #ffffff; -} - -.app-header .bg-dark a.list-group-item:hover, -.app-header .bg-dark a.list-group-item:active { - background-color: #4e8018; -} - -.list-group { - border-radius: @border-radius-base; - &.no-radius { - .list-group-item { - border-radius: 0 !important; - } - } - &.no-borders { - .list-group-item { - border: none; - } - } - &.no-border { - .list-group-item { - border-width: 1px 0; - } - } - &.no-bg { - .list-group-item { - background-color: transparent; - } - } -} - -.list-group-item { - border-color: @list-group-item-border; - padding-right: 15px; - a& { - &:hover, - &:focus { - background-color: @list-group-item-hover; - } - } - &.media { - margin-top: 0; - } - &.active { - color: #fff; - border-color: @list-group-active-color !important; - background-color: @list-group-active-color !important; - .text-muted { - color: lighten(@list-group-active-color, 30%) !important; - } - a { - color: #fff; - } - } - &.select { - background-color: @list-group-select-color; - } - - .list-group-alt & { - &:nth-child(2n + 2) { - background-color: rgba(0, 0, 0, 0.02) !important; - } - } - .list-group-lg & { - padding-top: 15px; - padding-bottom: 15px; - } - .list-group-sm & { - padding: 6px 10px; - } - .list-group-sp & { - margin-bottom: 5px; - border-radius: 3px; - } - > .badge { - margin-right: 0; - } - > .fa-chevron-right { - float: right; - margin-top: 4px; - margin-right: -5px; - & + .badge { - margin-right: 5px; - } - } -} - -.nav-pills { - &.no-radius { - > li { - > a { - border-radius: 0; - } - } - } - > li { - &.active { - > a { - color: #fff !important; - background-color: @brand-info; - &:hover, - &:active { - background-color: darken(@brand-info, 5%); - } - } - } - } -} - -.primary-underline { - box-shadow: 0 5px 5px rgba(0, 0, 0, 0.25); - border-bottom: 2px solid @brand-primary !important; -} - -.nav { - > li { - > a { - &:hover, - &:focus { - background-color: @nav-bg; - } - } - } - &.nav-md { - > li > a { - padding: 10px 20px; - } - } - &.nav-sm { - > li > a { - padding: 8px 12px; - } - } - &.nav-xs { - > li > a { - padding: 6px 10px; - } - } - .open { - > a { - &, - &:hover, - &:focus { - background-color: @nav-bg; - } - } - } -} - -.nav-tabs { - border-color: @border-color; - > li { - > a { - border-radius: @panel-border-radius @panel-border-radius 0 0; - border-bottom-color: @border-color; - } - &:hover > a, - &.active > a, - &.active > a:hover { - border-color: @border-color; - } - &.active > a { - border-bottom-color: #fff !important; - } - } - .nav-tabs-alt & { - &.nav-justified { - > li { - display: table-cell; - width: 1%; - } - } - > li { - > a { - border-radius: 0; - border-color: transparent !important; - background: transparent !important; - border-bottom-color: @border-color !important; - } - &.active { - > a { - border-bottom-color: @brand-info !important; - } - } - } - } -} - -.tab-container { - margin-bottom: 15px; - .tab-content { - padding: 15px; - background-color: #fff; - border: 1px solid @border-color; - border-top-width: 0; - border-radius: 0 0 @panel-border-radius @panel-border-radius; - } -} - -.pagination { - > li { - > a { - border-color: @border-color; - &:hover, - &:focus { - border-color: @border-color; - background-color: @brand-light; - } - } - } -} - -.panel { - border-radius: @panel-border-radius; - box-shadow: none; - .accordion-toggle { - font-size: 14px; - display: block; - cursor: pointer; - } - .list-group-item { - border-color: @panel-list-group-border; - } - &.no-borders { - border-width: 0; - .panel-heading, - .panel-footer { - border-width: 0; - } - } -} - -.panel-heading { - font-size: 15px; - color: @text-muted; - .fa { - margin-right: 5px; - font-size: 18px; - } - .panel-default & { - background-color: @panel-heading-bg; - } - border-radius: @panel-border-radius @panel-border-radius 0 0; - &.no-border { - margin: -1px -1px 0 -1px; - border: none; - } - .nav { - margin: -10px -15px; - } - .list-group { - background: transparent; - } - .btn-group { - margin-top: -5px; - } - .labels { - margin-left: 3em; - .label { - font-size: 90%; - } - } -} - -.panel-footer { - border-color: @panel-heading-border; - border-radius: 0 0 @panel-border-radius @panel-border-radius; - background-color: @panel-footer-bg; -} - -.panel-default > .panel-heading { - color: @text-muted; -} - -.panel-default { - border-color: @panel-border; - > .panel-heading, - > .panel-footer { - border-color: @panel-heading-border; - } -} - -.panel-group .panel-heading + .panel-collapse .panel-body { - border-top: 1px solid #eaedef; -} - -.table { - > tbody, - > tfoot { - > tr { - > td { - padding: 8px 15px; - border-top: 1px solid @table-border-color; - } - } - } - > thead > tr > th { - padding: 8px 15px; - border-bottom: 1px solid @table-border-color; - } -} -.table-bordered { - border-color: @table-border-color; - > tbody { - > tr { - > td, - > th { - border-color: @table-border-color; - } - } - } - > thead > tr > th { - border-color: @table-border-color; - } -} -.table-striped { - > tbody { - > tr { - &:nth-child(odd) { - > td, - > th { - background-color: @table-striped-color; - } - } - } - } - > thead { - > th { - background-color: @table-striped-color; - border-right: 1px solid @table-border-color; - &:last-child { - border-right: none; - } - } - } -} - -.table-hover.table-clickable tbody tr:hover td, -.table-hover tbody tr:hover th { - background-color: #edf9e1; -} - -.well, -pre { - background-color: @brand-light; - border-color: @border-color; -} - -.dropdown-menu { - border-radius: @border-radius-base; - .box-shadow(0 2px 6px rgba(0, 0, 0, 0.1)); - border: 1px solid @border-color; - border: 1px solid rgba(0, 0, 0, 0.1); - &.pull-left { - left: 100%; - } - > .panel { - border: none; - margin: -5px 0; - } - > li > a { - padding: 5px 15px; - } - > li > a:hover, - > li > a:focus, - > .active > a, - > .active > a:hover, - > .active > a:focus { - background-image: none; - filter: none; - background-color: @brand-light !important; - color: @link-hover-color; - } -} - -.dropdown-header { - padding: 5px 15px; -} - -.dropdown-submenu { - position: relative; - &:hover, - &:focus { - > a { - background-color: @brand-light !important; - color: @text-color; - } - > .dropdown-menu { - display: block; - } - } - &.pull-left { - float: none !important; - > .dropdown-menu { - left: -100%; - margin-left: 10px; - } - } - .dropdown-menu { - left: 100%; - top: 0; - margin-top: -6px; - margin-left: -1px; - } - - .dropup & { - > .dropdown-menu { - top: auto; - bottom: 0; - } - } -} - -.btn-group > .btn { - margin-left: -1px; -} - -/*cols*/ -.col-lg-2-4 { - position: relative; - min-height: 1px; - padding-left: 15px; - padding-right: 15px; -} - -.col-0 { - clear: left; -} - -.row.no-gutter { - margin-left: 0; - margin-right: 0; -} - -.no-gutter [class*="col"] { - padding: 0; -} - -.row-sm { - margin-left: -10px; - margin-right: -10px; - > div { - padding-left: 10px; - padding-right: 10px; - } -} - -.modal-backdrop { - background-color: @brand-dark; - &.in { - opacity: 0.8; - filter: alpha(opacity=80); - } -} - -.modal-over { - left: 0; - right: 0; - top: 0; - bottom: 0; - position: fixed; -} - -.modal-center { - position: absolute; - left: 50%; - top: 50%; -} - -.modal-dialog { - margin: 25px auto; - - .modal-content { - border: none; - } - .modal-header { - background-color: #7cc231; - border-bottom: none; - border-radius: 5px 5px 0px 0px; - h4 { - color: #fff; - } - } -} - -.dropdown-menu { - // make sure z-index is higher than footer - z-index: 1006; -} diff --git a/src/Exceptionless.Web/ClientApp.angular/less/tables.less b/src/Exceptionless.Web/ClientApp.angular/less/tables.less deleted file mode 100644 index ad8d4eff73..0000000000 --- a/src/Exceptionless.Web/ClientApp.angular/less/tables.less +++ /dev/null @@ -1,145 +0,0 @@ -.row-clickable { - cursor: pointer; -} - -.table-fixed { - table-layout: fixed; - word-wrap: break-word; -} - -.table th.clickable { - cursor: pointer; - cursor: hand; -} - -.table th.action { - width: 78px; -} - -.table th.action-lg { - width: 136px; -} - -.table th.action-xl { - width: 175px; -} - -.table th.action-xxl { - width: 210px; -} - -.table th.date { - white-space: nowrap; - width: 140px; -} - -.table th.relative-date { - white-space: nowrap; - width: 155px; -} - -.table th.number { - white-space: nowrap; - width: 88px; -} - -.table th.percentage { - white-space: nowrap; - width: 74px; -} - -td.number { - text-align: right; -} - -.table th.selection { - width: 50px; -} - -.table-key-value td + td, -.table-key-value th + td { - width: 100%; -} - -.table-key-value th:first-child, -.table-key-value td:first-child { - @media (max-width: @screen-xs-max) { - width: 120px; - } - - @media (min-width: @screen-sm-min) and (max-width: @screen-sm-max) { - width: 120px; - } - - @media (min-width: @screen-md-min) and (max-width: @screen-md-max) { - width: 140px; - } - - @media (min-width: @screen-lg-min) { - width: 140px; - } -} - -.table-selectable th:nth-child(2), -.table-selectable td:nth-child(2) { - @media (max-width: @screen-xs-max) { - border-left: none; - } -} - -.table-key-value td > ul { - padding-left: 15px; -} - -.table-footer { - padding: 10px 15px; - background-color: #fff; - border-top: 1px solid #ddd; - border-bottom-right-radius: 5px; - border-bottom-left-radius: 5px; -} - -.tab-pane .table-footer { - padding: inherit; - border-top: inherit; -} - -.tab-content .table { - @media (min-width: @screen-sm-min) and (max-width: @screen-sm-max) { - margin-bottom: 15px; - } - - @media (min-width: @screen-md-min) and (max-width: @screen-md-max) { - margin-bottom: 15px; - } - - @media (min-width: @screen-lg-min) { - margin-bottom: 15px; - } -} - -// Override default styles. This fixes dropdowns not being cut off and long content not running over other td's. -.table-responsive { - overflow-x: inherit; - - @media screen and (max-width: @screen-xs-max) { - overflow-y: inherit; - - > .table { - > thead, - > tbody, - > tfoot { - > tr { - > th, - > td { - white-space: inherit; - } - } - } - } - } -} - -.editable-wrap { - width: 100%; -} diff --git a/src/Exceptionless.Web/ClientApp.angular/less/utilities.less b/src/Exceptionless.Web/ClientApp.angular/less/utilities.less deleted file mode 100644 index 8b7812c1ba..0000000000 --- a/src/Exceptionless.Web/ClientApp.angular/less/utilities.less +++ /dev/null @@ -1,831 +0,0 @@ -.pos-rlt { - position: relative; -} -.pos-stc { - position: static; -} -.pos-abt { - position: absolute; -} -.pos-fix { - position: fixed; -} - -.line { - width: 100%; - height: 2px; - margin: 10px 0; - font-size: 0; - overflow: hidden; -} -.line-xs { - margin: 0; -} -.line-lg { - margin-top: 15px; - margin-bottom: 15px; -} -.line-dashed { - border-style: dashed !important; - background-color: transparent; - border-width: 0; -} -.no-line { - border-width: 0; -} -.no-border, -.no-borders { - border-color: transparent; - border-width: 0; -} -.no-radius { - border-radius: 0; -} -.block { - display: block; -} -.block.hide { - display: none; -} -.inline { - display: inline-block !important; -} -.none { - display: none; -} -.pull-none { - float: none; -} -.rounded { - border-radius: 500px; -} -.clear { - display: block; - overflow: hidden; -} -.no-bg { - background-color: transparent; - color: inherit; -} - -.l-h { - line-height: @line-height-base; -} -.l-h-1x { - line-height: 1.2; -} -.l-h-2x { - line-height: 2em; -} - -.l-s-1x { - letter-spacing: 1; -} -.l-s-2x { - letter-spacing: 2; -} -.l-s-3x { - letter-spacing: 3; -} - -.font-normal { - font-weight: normal; -} -.font-thin { - font-weight: 300; -} -.font-bold { - font-weight: 700; -} - -.text-3x { - font-size: 3em; -} -.text-2x { - font-size: 2em; -} -.text-lg { - font-size: @font-size-lg; -} -.text-md { - font-size: @font-size-md; -} -.text-base { - font-size: @font-size-base; -} -.text-sm { - font-size: @font-size-sm; -} -.text-xs { - font-size: @font-size-xs; -} -.text-xxs { - text-indent: -9999px; -} -.text-ellipsis { - display: block; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} -.text-u-c { - text-transform: uppercase; -} -.text-l-t { - text-decoration: line-through; -} -.text-u-l { - text-decoration: underline; -} -.text-active, -.active > .text, -.active > .auto .text { - display: none !important; -} -.active > .text-active, -.active > .auto .text-active { - display: inline-block !important; -} -.box-shadow { - box-shadow: - 0 2px 2px rgba(0, 0, 0, 0.05), - 0 1px 0 rgba(0, 0, 0, 0.05); -} -.box-shadow-lg { - box-shadow: 5px 5px 10px rgba(0, 0, 0, 0.05); -} -.text-shadow { - font-size: 170px; - text-shadow: - 0 1px 0 @border-color, - 0 2px 0 lighten(@border-color, 10%), - 0 5px 10px rgba(0, 0, 0, 0.125), - 0 10px 20px rgba(0, 0, 0, 0.2); -} -.no-shadow { - -webkit-box-shadow: none !important; - box-shadow: none !important; -} - -.wrapper-xs { - padding: 5px; -} -.wrapper-sm { - padding: 10px; -} -.wrapper { - padding: 15px; -} -.wrapper-md { - padding: 20px; -} -.wrapper-lg { - padding: 30px; -} -.wrapper-xl { - padding: 50px; -} -.padder-lg { - padding-left: 30px; - padding-right: 30px; -} -.padder-md { - padding-left: 20px; - padding-right: 20px; -} -.padder { - padding-left: 15px; - padding-right: 15px; -} -.padder-v { - padding-top: 15px; - padding-bottom: 15px; -} -.no-padder { - padding: 0 !important; -} -.pull-in { - margin-left: -15px; - margin-right: -15px; -} -.pull-out { - margin: -10px -15px; -} - -.b { - border: 1px solid rgba(0, 0, 0, 0.05); -} -.b-a { - border: 1px solid @border-color; -} -.b-t { - border-top: 1px solid @border-color; -} -.b-r { - border-right: 1px solid @border-color; -} -.b-b { - border-bottom: 1px solid @border-color; -} -.b-l { - border-left: 1px solid @border-color; -} -.b-light { - border-color: @brand-light; -} -.b-dark { - border-color: @brand-dark; -} -.b-black { - border-color: @brand-dark; -} -.b-primary { - border-color: @brand-primary; -} -.b-success { - border-color: @brand-success; -} -.b-info { - border-color: @brand-info; -} -.b-warning { - border-color: @brand-warning; -} -.b-danger { - border-color: @brand-danger; -} -.b-white { - border-color: #fff; -} -.b-dashed { - border-style: dashed !important; -} - -.b-l-light { - border-left-color: @brand-light; -} -.b-l-dark { - border-left-color: @brand-dark; -} -.b-l-black { - border-left-color: @brand-dark; -} -.b-l-primary { - border-left-color: @brand-primary; -} -.b-l-success { - border-left-color: @brand-success; -} -.b-l-info { - border-left-color: @brand-info; -} -.b-l-warning { - border-left-color: @brand-warning; -} -.b-l-danger { - border-left-color: @brand-danger; -} -.b-l-white { - border-left-color: #fff; -} - -.b-l-2x { - border-left-width: 2px; -} -.b-l-3x { - border-left-width: 3px; -} -.b-l-4x { - border-left-width: 4px; -} -.b-l-5x { - border-left-width: 5px; -} - -.b-2x { - border-width: 2px; -} -.b-3x { - border-width: 3px; -} -.b-4x { - border-width: 4px; -} -.b-5x { - border-width: 5px; -} - -.r { - border-radius: @border-radius-base @border-radius-base @border-radius-base @border-radius-base; -} - -.r-2x { - border-radius: @border-radius-base * 2; -} - -.r-3x { - border-radius: @border-radius-base * 3; -} - -.r-l { - border-radius: @border-radius-base 0 0 @border-radius-base; -} - -.r-r { - border-radius: 0 @border-radius-base @border-radius-base 0; -} - -.r-t { - border-radius: @border-radius-base @border-radius-base 0 0; -} - -.r-b { - border-radius: 0 0 @border-radius-base @border-radius-base; -} - -.m-xxs { - margin: 2px 4px; -} -.m-xs { - margin: 5px; -} -.m-sm { - margin: 10px; -} -.m { - margin: 15px; -} -.m-md { - margin: 20px; -} -.m-lg { - margin: 30px; -} -.m-xl { - margin: 50px; -} -.m-n { - margin: 0 !important; -} - -.m-l-none { - margin-left: 0; -} -.m-l-xs { - margin-left: 5px; -} -.m-l-sm { - margin-left: 10px; -} -.m-l { - margin-left: 15px; -} -.m-l-md { - margin-left: 20px; -} -.m-l-lg { - margin-left: 30px; -} -.m-l-xl { - margin-left: 40px; -} -.m-l-xxl { - margin-left: 50px; -} - -.m-l-n-xxs { - margin-left: -1px; -} -.m-l-n-xs { - margin-left: -5px; -} -.m-l-n-sm { - margin-left: -10px; -} -.m-l-n { - margin-left: -15px; -} -.m-l-n-md { - margin-left: -20px; -} -.m-l-n-lg { - margin-left: -30px; -} -.m-l-n-xl { - margin-left: -40px; -} -.m-l-n-xxl { - margin-left: -50px; -} - -.m-t-none { - margin-top: 0; -} -.m-t-xxs { - margin-top: 1px; -} -.m-t-xs { - margin-top: 5px; -} -.m-t-sm { - margin-top: 10px; -} -.m-t { - margin-top: 15px; -} -.m-t-md { - margin-top: 20px; -} -.m-t-lg { - margin-top: 30px; -} -.m-t-xl { - margin-top: 40px; -} -.m-t-xxl { - margin-top: 50px; -} - -.m-t-n-xxs { - margin-top: -1px; -} -.m-t-n-xs { - margin-top: -5px; -} -.m-t-n-sm { - margin-top: -10px; -} -.m-t-n { - margin-top: -15px; -} -.m-t-n-md { - margin-top: -20px; -} -.m-t-n-lg { - margin-top: -30px; -} -.m-t-n-xl { - margin-top: -40px; -} -.m-t-n-xxl { - margin-top: -50px; -} - -.m-r-none { - margin-right: 0; -} -.m-r-xxs { - margin-right: 1px; -} -.m-r-xs { - margin-right: 5px; -} -.m-r-sm { - margin-right: 10px; -} -.m-r { - margin-right: 15px; -} -.m-r-md { - margin-right: 20px; -} -.m-r-lg { - margin-right: 30px; -} -.m-r-xl { - margin-right: 40px; -} -.m-r-xxl { - margin-right: 50px; -} - -.m-r-n-xxs { - margin-right: -1px; -} -.m-r-n-xs { - margin-right: -5px; -} -.m-r-n-sm { - margin-right: -10px; -} -.m-r-n { - margin-right: -15px; -} -.m-r-n-md { - margin-right: -20px; -} -.m-r-n-lg { - margin-right: -30px; -} -.m-r-n-xl { - margin-right: -40px; -} -.m-r-n-xxl { - margin-right: -50px; -} - -.m-b-none { - margin-bottom: 0; -} -.m-b-xxs { - margin-bottom: 1px; -} -.m-b-xs { - margin-bottom: 5px; -} -.m-b-sm { - margin-bottom: 10px; -} -.m-b { - margin-bottom: 15px; -} -.m-b-md { - margin-bottom: 20px; -} -.m-b-lg { - margin-bottom: 30px; -} -.m-b-xl { - margin-bottom: 40px; -} -.m-b-xxl { - margin-bottom: 50px; -} - -.m-b-n-xxs { - margin-bottom: -1px; -} -.m-b-n-xs { - margin-bottom: -5px; -} -.m-b-n-sm { - margin-bottom: -10px; -} -.m-b-n { - margin-bottom: -15px; -} -.m-b-n-md { - margin-bottom: -20px; -} -.m-b-n-lg { - margin-bottom: -30px; -} -.m-b-n-xl { - margin-bottom: -40px; -} -.m-b-n-xxl { - margin-bottom: -50px; -} - -.w-xxs { - width: 60px; -} -.w-xs { - width: 90px; -} -.w-sm { - width: 150px; -} -.w { - width: 200px; -} -.w-md { - width: 240px; -} -.w-lg { - width: 280px; -} -.w-xl { - width: 320px; -} -.w-xxl { - width: 360px; -} -.w-full { - width: 100%; -} -.w-auto { - width: auto; -} -.h-auto { - height: auto; -} -.h-full { - height: 100%; -} - -.thumb-xl { - width: 128px; - display: inline-block; -} -.thumb-lg { - width: 96px; - display: inline-block; -} -.thumb-md { - width: 64px; - display: inline-block; -} -.thumb { - width: 50px; - display: inline-block; -} -.thumb-sm { - width: 40px; - display: inline-block; -} -.thumb-xs { - width: 34px; - display: inline-block; -} -.thumb-xxs { - width: 30px; - display: inline-block; -} -.thumb-wrapper { - padding: 2px; - border: 1px solid @border-color; -} -.thumb, -.thumb-xs, -.thumb-sm, -.thumb-md, -.thumb-lg, -.thumb-btn { - img { - height: auto; - max-width: 100%; - vertical-align: middle; - } -} -.img-full { - width: 100%; - img { - width: 100%; - } -} - -.scrollable { - overflow-x: hidden; - overflow-y: auto; - -webkit-overflow-scrolling: touch; -} - -.no-touch { - .scrollable.hover { - overflow-y: hidden; - &:hover { - overflow: visible; - overflow-y: auto; - } - } -} -.scroll-x, -.scroll-y { - overflow: hidden; - -webkit-overflow-scrolling: touch; -} -.scroll-y { - overflow-y: auto; -} -.scroll-x { - overflow-x: auto; -} -.no-touch { - .scroll-x, - .scroll-y { - overflow: hidden; - } - .scroll-x { - &:hover, - &:focus, - &:active { - overflow-x: auto; - } - } - .scroll-y { - &:hover, - &:focus, - &:active { - overflow-y: auto; - } - } -} - -.hover-action { - display: none; -} -.hover-rotate { - .transition(all 0.2s ease-in-out 0.1s); -} -.hover:hover .hover:focus, -.hover:active { - .hover-action { - display: inherit; - } - .hover-rotate { - .rotate(90deg); - } -} - -.backdrop { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - z-index: 1050; - &.fade { - opacity: 0; - filter: alpha(opacity=0); - } - &.in { - opacity: 0.8; - filter: alpha(opacity=80); - } -} - -/*desktop*/ -@media screen and (min-width: 992px) { - .col-lg-2-4 { - width: 20%; - float: left; - } -} - -// sm -@media (min-width: 768px) and (max-width: 991px) { - .hidden-sm.show { - display: inherit !important; - } -} - -/*phone*/ -@media (max-width: 767px) { - .w-auto-xs { - width: auto; - } - .shift { - display: none !important; - } - .shift.in { - display: block !important; - } - .row-2 [class*="col"] { - width: 50%; - float: left; - } - .row-2 .col-0 { - clear: none; - } - .row-2 li:nth-child(odd) { - clear: left; - margin-left: 0; - } - .text-center-xs { - text-align: center; - } - .text-left-xs { - text-align: left; - } - .text-right-xs { - text-align: right; - } - .no-border-xs { - border-width: 0; - } - .pull-none-xs { - float: none !important; - } - .pull-right-xs { - float: right !important; - } - .pull-left-xs { - float: left !important; - } - .dropdown-menu.pull-none-xs { - left: 0; - } - .hidden-xs.show { - display: inherit !important; - } - .wrapper-lg, - .wrapper-md { - padding: 15px; - } - .padder-lg, - .padder-md { - padding-left: 15px; - padding-right: 15px; - } -} - -.fa .fa-size-sm { - font-size: 1em; -} - -.panel-heading { - .fa-size-sm { - font-size: 1em; - } -} diff --git a/src/Exceptionless.Web/ClientApp.angular/less/variables.less b/src/Exceptionless.Web/ClientApp.angular/less/variables.less deleted file mode 100644 index 7e4b58e448..0000000000 --- a/src/Exceptionless.Web/ClientApp.angular/less/variables.less +++ /dev/null @@ -1,84 +0,0 @@ -@import "../node_modules/bootstrap/less/variables.less"; - -/* Variables */ - -@brand-primary: #5e9a00; -@brand-info: #6ebc1a; -@brand-success: #6ebc1a; -@brand-warning: #fa810b; -@brand-danger: #bb423f; -@brand-light: #f7f7f7; -@brand-dark: #545454; -@brand-black: #2c2c2c; - -@body-bg: lighten(@brand-light, 1%); -@text-color: #2f2f2f; -@text-muted: lighten(@text-color, 25%); - -@font-family-sans-serif: "Source Sans Pro", "Helvetica Neue", Helvetica, Arial, sans-serif; -@font-family-serif: Georgia, "Times New Roman", Times, serif; -@font-family-base: @font-family-sans-serif; - -@font-size-base: 14px; -@font-size-lg: ceil(@font-size-base * 1.23); // ~20px -@font-size-md: ceil(@font-size-base * 1.14); // ~16px -@font-size-sm: ceil(@font-size-base * 0.92); // ~13px -@font-size-xs: ceil(@font-size-base * 0.85); // ~12px - -@link-color: darken(@text-color, 15%); -@link-hover-color: #578f01; - -@border-radius-base: 5px; -@border-color: #d5d5d5; - -@line-height-base: 1.42857143; // 20/14 -@line-height-computed: floor(@font-size-base * @line-height-base); // ~20px -@icon-css-prefix: i; - -@btn-default-color: #999999; -@btn-border-radius: @border-radius-base; - -@input-border: darken(@border-color, 5%); -@input-border-focus: @brand-primary; -@input-border-radius: @border-radius-base; - -@panel-bg: #fff; -@panel-border: @border-color; -@panel-heading-border: lighten(@border-color, 5%); -@panel-list-group-border: lighten(@border-color, 5%); -@panel-border-radius: @border-radius-base; -@panel-heading-bg: #f0f0f0; -@panel-footer-bg: @panel-heading-bg; - -@nav-bg: lighten(@brand-light, 3%); -@badge-bg: darken(@brand-light, 10%); - -@list-group-item-border: lighten(@border-color, 3%); -@list-group-item-hover: lighten(@brand-light, 3%); -@list-group-select-color: lighten(@brand-light, 2%); -@list-group-active-color: @brand-primary; - -@table-border-color: lighten(@border-color, 4%); -@table-striped-color: #f9f9f9; - -@arrow-width: 8px; -@arrow-color: #fff; -@arrow-outer-width: (@arrow-width + 1); -@arrow-outer-color: rgba(0, 0, 0, 0.1); - -@switch-width: 35px; -@switch-height: 20px; -@switch-md-width: 40px; -@switch-md-height: 24px; -@switch-lg-width: 50px; -@switch-lg-height: 30px; - -@app-aside-width: 200px; -@app-aside-nav-height: 40px; -@app-aside-folded-width: 60px; -@app-aside-folded-nav-height: 50px; - -@app-header-height: 50px; -@app-header-md-height: 60px; - -@scroll-bar-width: 17px; diff --git a/src/Exceptionless.Web/ClientApp.angular/package-lock.json b/src/Exceptionless.Web/ClientApp.angular/package-lock.json deleted file mode 100644 index 2865348065..0000000000 --- a/src/Exceptionless.Web/ClientApp.angular/package-lock.json +++ /dev/null @@ -1,9497 +0,0 @@ -{ - "name": "exceptionless", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "exceptionless", - "dependencies": { - "angular": "1.8.3", - "angular-animate": "1.8.3", - "angular-clipboard": "1.7.0", - "angular-dialog-service": "5.3.0", - "angular-filter": "0.5.17", - "angular-gravatar": "0.4.2", - "angular-hotkeys": "1.7.0", - "angular-input-match": "0.0.3", - "angular-intercom": "2.1.1", - "angular-loading-bar": "0.9.0", - "angular-locker": "2.0.5", - "angular-messages": "1.8.3", - "angular-mocks": "1.8.3", - "angular-payments": "*", - "angular-resource": "1.8.3", - "angular-sanitize": "1.8.3", - "angular-stripe": "5.0.0", - "angular-translate": "2.19.0", - "angular-translate-loader-static-files": "2.19.0", - "angular-ui-bootstrap": "2.5.6", - "angular-ui-router": "0.4.2", - "angular-xeditable": "0.10.2", - "angularjs-toaster": "3.0.0", - "angulartics": "1.4.0", - "animate.css": "4.1.1", - "bootstrap": "3.4.1", - "bootstrap-daterangepicker": "2.1.27", - "checklist-model": "1.0.0", - "d3": "3.5.17", - "exceptionless": "1.6.4", - "font-awesome": "4.7.0", - "handlebars": "4.7.7", - "jquery": "3.7.0", - "less": "4.1.3", - "li": "1.3.0", - "lodash": "4.17.21", - "moment": "2.29.4", - "mousetrap": "1.6.5", - "ng-debounce": "1.0.1", - "ng-filters": "1.3.1", - "query-string": "4.2.3", - "restangular": "1.6.1", - "rickshaw": "1.7.1", - "satellizer": "0.15.5", - "trunk8": "0.0.1", - "twix": "1.3.0" - }, - "devDependencies": { - "cross-env": "7.0.3", - "eslint": "^8.45.0", - "eslint-config-airbnb-base": "^15.0.0", - "eslint-config-prettier": "^8.8.0", - "eslint-plugin-import": "^2.27.5", - "grunt": "1.6.1", - "grunt-angular-templates": "1.2.0", - "grunt-browser-output": "1.0.3", - "grunt-cache-bust": "1.7.0", - "grunt-connect-proxy2": "2.0.0", - "grunt-contrib-clean": "2.0.1", - "grunt-contrib-concat": "2.1.0", - "grunt-contrib-connect": "^4.0.0", - "grunt-contrib-copy": "1.0.0", - "grunt-contrib-cssmin": "5.0.0", - "grunt-contrib-htmlmin": "3.1.0", - "grunt-contrib-less": "3.0.0", - "grunt-contrib-uglify": "5.2.2", - "grunt-contrib-watch": "1.1.0", - "grunt-dom-munger": "^4.0.0", - "grunt-eslint": "^24.3.0", - "grunt-html-angular-validate": "^0.6.1", - "grunt-ng-annotate": "^4.0.0", - "grunt-replace": "^2.0.2", - "load-grunt-config": "^4.0.1", - "prettier": "^3.0.0" - } - }, - "node_modules/@aashutoshrathi/word-wrap": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", - "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/@eslint-community/eslint-utils": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", - "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", - "dev": true, - "dependencies": { - "eslint-visitor-keys": "^3.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" - } - }, - "node_modules/@eslint-community/regexpp": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.5.1.tgz", - "integrity": "sha512-Z5ba73P98O1KUYCCJTUeVpja9RcGoMdncZ6T49FCUl2lN38JtCJ+3WgIDBv0AuY4WChU5PmtJmOCTlN6FZTFKQ==", - "dev": true, - "engines": { - "node": "^12.0.0 || ^14.0.0 || >=16.0.0" - } - }, - "node_modules/@eslint/eslintrc": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.0.tgz", - "integrity": "sha512-Lj7DECXqIVCqnqjjHMPna4vn6GJcMgul/wuS0je9OZ9gsL0zzDpKPVtcG1HaDVc+9y+qgXneTeUMbCqXJNpH1A==", - "dev": true, - "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^9.6.0", - "globals": "^13.19.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@eslint/eslintrc/node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "node_modules/@eslint/eslintrc/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/@eslint/eslintrc/node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/@eslint/eslintrc/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "node_modules/@eslint/js": { - "version": "8.44.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.44.0.tgz", - "integrity": "sha512-Ag+9YM4ocKQx9AarydN0KY2j0ErMHNIocPDrVo8zAE44xLTjEtz81OdR68/cydGtk6m6jDb5Za3r2useMzYmSw==", - "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/@humanwhocodes/config-array": { - "version": "0.11.10", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.10.tgz", - "integrity": "sha512-KVVjQmNUepDVGXNuoRRdmmEjruj0KfiGSbS8LVc12LMsWDQzRXJ0qdhN8L8uUigKpfEHRhlaQFY0ib1tnUbNeQ==", - "dev": true, - "dependencies": { - "@humanwhocodes/object-schema": "^1.2.1", - "debug": "^4.1.1", - "minimatch": "^3.0.5" - }, - "engines": { - "node": ">=10.10.0" - } - }, - "node_modules/@humanwhocodes/config-array/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/@humanwhocodes/config-array/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "node_modules/@humanwhocodes/module-importer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", - "dev": true, - "engines": { - "node": ">=12.22" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@humanwhocodes/object-schema": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", - "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", - "dev": true - }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@types/json5": { - "version": "0.0.29", - "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", - "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", - "dev": true - }, - "node_modules/@types/minimatch": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.5.tgz", - "integrity": "sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==", - "dev": true - }, - "node_modules/abbrev": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", - "dev": true - }, - "node_modules/accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", - "dev": true, - "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/acorn-node": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/acorn-node/-/acorn-node-1.8.2.tgz", - "integrity": "sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A==", - "dependencies": { - "acorn": "^7.0.0", - "acorn-walk": "^7.0.0", - "xtend": "^4.0.2" - } - }, - "node_modules/acorn-walk": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", - "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/agent-base": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz", - "integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==", - "dev": true, - "dependencies": { - "es6-promisify": "^5.0.0" - }, - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/alter": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/alter/-/alter-0.2.0.tgz", - "integrity": "sha512-Wuss6JIZ6h4j2+NgU2t+9mSwS7gBSZJbU4Dg8xETguAD2veJUSuCrvTIiC78QgZE7/zX7h6OnXw2PiiCBirEGw==", - "dev": true, - "dependencies": { - "stable": "~0.1.3" - } - }, - "node_modules/angular": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/angular/-/angular-1.8.3.tgz", - "integrity": "sha512-5qjkWIQQVsHj4Sb5TcEs4WZWpFeVFHXwxEBHUhrny41D8UrBAd6T/6nPPAsLngJCReIOqi95W3mxdveveutpZw==", - "deprecated": "For the actively supported Angular, see https://www.npmjs.com/package/@angular/core. AngularJS support has officially ended. For extended AngularJS support options, see https://goo.gle/angularjs-path-forward." - }, - "node_modules/angular-animate": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/angular-animate/-/angular-animate-1.8.3.tgz", - "integrity": "sha512-/LtTKvy5sD6MZbV0v+nHgOIpnFF0mrUp+j5WIxVprVhcrJriYpuCZf4S7Owj1o76De/J0eRzANUozNJ6hVepnQ==", - "deprecated": "For the actively supported Angular, see https://www.npmjs.com/package/@angular/core. AngularJS support has officially ended. For extended AngularJS support options, see https://goo.gle/angularjs-path-forward." - }, - "node_modules/angular-assert-q-constructor": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/angular-assert-q-constructor/-/angular-assert-q-constructor-1.0.1.tgz", - "integrity": "sha512-C82/LM77Q4zn7kbi9aPeliCFp3h26dZJUxeDpr7HYNIJEpKuYIk5ylJyBEZlgl2SEHWwbbiBt6yRBiI66GKLJQ==" - }, - "node_modules/angular-clipboard": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/angular-clipboard/-/angular-clipboard-1.7.0.tgz", - "integrity": "sha512-4/eg3zZw1MJpIsMc+mWzeVNyWBu8YWpXPTdmbgyPRp/6f0xB6I3XR2iC6Mb4mg/5E9q6exCd0sX2yiIsw+ZLJw==", - "peerDependencies": { - "angular": "^1.3.0" - } - }, - "node_modules/angular-dialog-service": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/angular-dialog-service/-/angular-dialog-service-5.3.0.tgz", - "integrity": "sha512-03xUYE7EWkjVtkEKLDFDE+4cDw0L/ffpG4TnKsF6EtIRigoqtbhShmzW3LiyVLBwTyMhtOyeBD9LMrBhMj1jfA==" - }, - "node_modules/angular-filter": { - "version": "0.5.17", - "resolved": "https://registry.npmjs.org/angular-filter/-/angular-filter-0.5.17.tgz", - "integrity": "sha512-jWZuok808roBwEiNZ2IiBzKD7gf4vWTDzbp9RhhZO9usQxTiywsZ3O94X75MOHBtOzM/rxbI78sHw3Y2sz40QQ==", - "dependencies": { - "angular": "*" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/angular-gravatar": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/angular-gravatar/-/angular-gravatar-0.4.2.tgz", - "integrity": "sha512-ykWxH4m71W+AN8+HcmYR1EdfXvokFyZuO8BB2cQHlJj/lWFDbfas+w0tMY5HJnb4WagKWUru+uzkMFUvgouunA==", - "dependencies": { - "angular": "^1.2.5" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/angular-hotkeys": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/angular-hotkeys/-/angular-hotkeys-1.7.0.tgz", - "integrity": "sha512-yw4BYutP4fVbnG8Yssuyo2DqAch+wGhSpdX7C1PURjHQzbyXPq/hNM1QupxHor0nwYcHy+57Tb1tzgngTrd+SA==" - }, - "node_modules/angular-input-match": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/angular-input-match/-/angular-input-match-0.0.3.tgz", - "integrity": "sha512-eRqdG0NPlkDsKsWttOUcMA3+yPA9oG4ZjkcraREzIOx8VX8oZPI8XkkTNuSEjBUeI0XlANGKgGPrUhPFDwt3Mg==", - "dependencies": { - "angular": ">= 1.0.8" - } - }, - "node_modules/angular-intercom": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/angular-intercom/-/angular-intercom-2.1.1.tgz", - "integrity": "sha512-SQXUv/NaTMAhk5Tymp00IfWfhbSahQzfvDOXMI38ke+3yO39SoSSYLcPHaqvr/bRxpi6NKfvn+pVV3Qv9StzwQ==" - }, - "node_modules/angular-loading-bar": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/angular-loading-bar/-/angular-loading-bar-0.9.0.tgz", - "integrity": "sha512-m131J6XitwSMS2D95Kso9WwpoV92ixkksJv4KXGdhk6UKS1gW09IDvSaJq24iUtsLHIRqnhpj9Y2HiIn7q0WpQ==" - }, - "node_modules/angular-locker": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/angular-locker/-/angular-locker-2.0.5.tgz", - "integrity": "sha512-uaRYy1kAZ0JFpFTDsx0u2b9ybRzaCQHN9vFDZC22/aC1if1utLF4dvV6sa89xK+NK4h0dG/p1FbkoaD2EqjzDg==", - "dependencies": { - "angular": ">=1.2.0 <2.0.0" - } - }, - "node_modules/angular-messages": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/angular-messages/-/angular-messages-1.8.3.tgz", - "integrity": "sha512-f/ywtg32lqzX8FnXkBJOyn13lbCbo333/xy/5TTFcsH/gZdXoiuERj+dLTOs8xHCkOeFQhFx0VD0DgtMgSag7A==" - }, - "node_modules/angular-mocks": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/angular-mocks/-/angular-mocks-1.8.3.tgz", - "integrity": "sha512-vqsT6zwu80cZ8RY7qRQBZuy6Fq5X7/N5hkV9LzNT0c8b546rw4ErGK6muW1u2JnDKYa7+jJuaGM702bWir4HGw==" - }, - "node_modules/angular-payments": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/angular-payments/-/angular-payments-1.0.7.tgz", - "integrity": "sha512-SuU3vu5i8TyUI4rxcsxKWYylz5fWb+eS0e7Xlh5gsRhDHU2UAOZcIvYfdCwPUKlUijIShscunYMcs08n6I1/Lg==" - }, - "node_modules/angular-q-promisify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/angular-q-promisify/-/angular-q-promisify-3.0.0.tgz", - "integrity": "sha512-37SFVsrWNIcYv0ypCDgBF7kYEOuwTbOSiiZMYfpnYyqflLlfdQyc21JFI2RS3yw2dh5sjZmddXKYovwGnwY2+A==", - "dependencies": { - "assert-function": "~1.0.0", - "browserify": "~13.1.0", - "to-array": "~0.1.4" - }, - "peerDependencies": { - "angular": ">=1.3 <=1.5" - } - }, - "node_modules/angular-resource": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/angular-resource/-/angular-resource-1.8.3.tgz", - "integrity": "sha512-x3+4w2SyIDEIe6PUBTzcjDFI5MhZNWAPELUxkYK0QMOkOo4w3BN/qhpd+fO1deFiDS23mZKqT8kfctO1IDkCDA==", - "deprecated": "For the actively supported Angular, see https://www.npmjs.com/package/@angular/core. AngularJS support has officially ended. For extended AngularJS support options, see https://goo.gle/angularjs-path-forward." - }, - "node_modules/angular-sanitize": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/angular-sanitize/-/angular-sanitize-1.8.3.tgz", - "integrity": "sha512-2rxdqzlUVafUeWOwvY/FtyWk1pFTyCtzreeiTytG9m4smpuAEKaIJAjYeVwWsoV+nlTOcgpwV4W1OCmR+BQbUg==", - "deprecated": "For the actively supported Angular, see https://www.npmjs.com/package/@angular/core. AngularJS support has officially ended. For extended AngularJS support options, see https://goo.gle/angularjs-path-forward." - }, - "node_modules/angular-stripe": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/angular-stripe/-/angular-stripe-5.0.0.tgz", - "integrity": "sha512-rCjpfxGjqTaE6PVQ4NGyystVwFLa/CqKWiqFGK1+yVF8NlB+SQIKWgutwBwWpA4h8jWr/97GTnOL9+xHTg1rKg==", - "dependencies": { - "angular-assert-q-constructor": "~1.0.1", - "angular-q-promisify": "~3.0.0", - "ap": "~0.2.0", - "dot-prop": "~3.0.0", - "lazy-async": "~1.0.1", - "load-script-global": "~1.0.2", - "pify": "~2.3.0", - "stripe-as-promised": "~2.1.0", - "stripe-errback": "~1.0.2" - }, - "peerDependencies": { - "angular": "^1.3" - } - }, - "node_modules/angular-translate": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/angular-translate/-/angular-translate-2.19.0.tgz", - "integrity": "sha512-Z/Fip5uUT2N85dPQ0sMEe1JdF5AehcDe4tg/9mWXNDVU531emHCg53ZND9Oe0dyNiGX5rWcJKmsL1Fujus1vGQ==", - "dependencies": { - "angular": "^1.8.0" - }, - "engines": { - "node": "*" - } - }, - "node_modules/angular-translate-loader-static-files": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/angular-translate-loader-static-files/-/angular-translate-loader-static-files-2.19.0.tgz", - "integrity": "sha512-B0LiFzn+rHGgL0okl+uAcQKtmySskTlhw6vsnQODUqARV0IcgMUi2tl9RhybgGy3MbGmdF1JjJFjITCbXzWZQA==", - "dependencies": { - "angular-translate": "~2.19.0" - } - }, - "node_modules/angular-ui-bootstrap": { - "version": "2.5.6", - "resolved": "https://registry.npmjs.org/angular-ui-bootstrap/-/angular-ui-bootstrap-2.5.6.tgz", - "integrity": "sha512-yzcHpPMLQl0232nDzm5P4iAFTFQ9dMw0QgFLuKYbDj9M0xJ62z0oudYD/Lvh1pWfRsukiytP4Xj6BHOSrSXP8A==" - }, - "node_modules/angular-ui-router": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/angular-ui-router/-/angular-ui-router-0.4.2.tgz", - "integrity": "sha512-5qkKxRp+EY2qJ98Ezjo6JebiRai4knaBYeFfZhEeeWBHPE45YMf7aBczUH8kNMAY+auKbKrtOcUgqOZjzCYXlg==", - "deprecated": "This npm package 'angular-ui-router' has been renamed to '@uirouter/angularjs'. Please update your package.json. See https://ui-router.github.io/blog/uirouter-scoped-packages/", - "dependencies": { - "angular": "^1.0.8" - } - }, - "node_modules/angular-xeditable": { - "version": "0.10.2", - "resolved": "https://registry.npmjs.org/angular-xeditable/-/angular-xeditable-0.10.2.tgz", - "integrity": "sha512-X0pxMaWSYf0jIvW290PZYTw+IxyMMs6hel5mW5/nzYswpHvzApJZeFtlV9CbNI95Ejf/i2iCer2DOtMZKi7jfg==", - "dependencies": { - "angular": "~1.x" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/angularjs-toaster": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/angularjs-toaster/-/angularjs-toaster-3.0.0.tgz", - "integrity": "sha512-Upv5lkyrWWv9aIK4Jo7X9nCp1tPaWerSEeUrdjibm0GhwuW0/aoaUjn8Ou7IS8/QIGZspXrXHWGTa/CRBOj6Jg==" - }, - "node_modules/angulartics": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/angulartics/-/angulartics-1.4.0.tgz", - "integrity": "sha512-MsqVLuvLxe4/dvKjouXxn+odC4kTDKlHQV063Mo+39lSWFBJ/u4lNjlY56O0PXde9XNwPZXU8KyrliL+ViEdcw==" - }, - "node_modules/animate.css": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/animate.css/-/animate.css-4.1.1.tgz", - "integrity": "sha512-+mRmCTv6SbCmtYJCN4faJMNFVNN5EuCTTprDTAo7YzIGji2KADmakjVA3+8mVDkZ2Bf09vayB35lSQIex2+QaQ==" - }, - "node_modules/ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/ansi-to-html": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/ansi-to-html/-/ansi-to-html-0.1.1.tgz", - "integrity": "sha512-wRrzWyI4HdblK63EPanO4YFBKO7mMOAN/KcJoFdRp9x6q++fUTk4TX/wQ08+s+T7299mKbXWejUGKacI8TINYQ==", - "dev": true - }, - "node_modules/ap": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/ap/-/ap-0.2.0.tgz", - "integrity": "sha512-ImdvquIuBSVpWRWhB441UjvTcZqic1RL+lTQaUKGdGEp1aiTvt/phAvY8Vvs32qya5FJBI8U+tzNBYzFDQY/lQ==" - }, - "node_modules/applause": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/applause/-/applause-2.0.4.tgz", - "integrity": "sha512-wFhNjSoflbAEgelX3psyKSXV2iQFjuYW31DEhcCOD/bQ98VdfltLclK4p1mI6E58Qp4Q7+5RCbBdr+Nc9b5QhA==", - "dev": true, - "dependencies": { - "lodash": "^4.17.21", - "optional-require": "^1.0.2" - }, - "engines": { - "node": ">=10" - }, - "optionalDependencies": { - "cson-parser": "^4.0.8", - "js-yaml": "^4.0.0" - } - }, - "node_modules/applause/node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, - "optional": true - }, - "node_modules/applause/node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "optional": true, - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "dependencies": { - "sprintf-js": "~1.0.2" - } - }, - "node_modules/array-buffer-byte-length": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz", - "integrity": "sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "is-array-buffer": "^3.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/array-differ": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-3.0.0.tgz", - "integrity": "sha512-THtfYS6KtME/yIAhKjZ2ul7XI96lQGHRputJQHO80LAWQnuGP4iCIN8vdMRboGbIEYBwU33q8Tch1os2+X0kMg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/array-each": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/array-each/-/array-each-1.0.1.tgz", - "integrity": "sha512-zHjL5SZa68hkKHBFBK6DJCTtr9sfTCPCaph/L7tMSLcTFgy+zX7E+6q5UArbtOtMBCtxdICpfTCspRse+ywyXA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/array-find-index": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", - "integrity": "sha512-M1HQyIXcBGtVywBt8WVdim+lrNaK7VHp99Qt5pSNziXznKHViIBbXWtfRTpEFpF/c4FdfxNAsCCwPp5phBYJtw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/array-includes": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.6.tgz", - "integrity": "sha512-sgTbLvL6cNnw24FnbaDyjmvddQ2ML8arZsgaJhoABMoplz/4QRhtrYS+alr1BUM1Bwp6dhx8vVCBSLG+StwOFw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4", - "get-intrinsic": "^1.1.3", - "is-string": "^1.0.7" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/array-last": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array-last/-/array-last-1.0.2.tgz", - "integrity": "sha512-tf0rRd9PBdiqi8jGjshtXBPeFjZEUgRzBw+4pgQgEkXqnpxqz0eXLa9BzjqB7igW1uu3Xn4AELH2c8qKaH9Ktg==", - "dependencies": { - "array-slice": "^0.2.2", - "is-number": "^0.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/array-slice": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-0.2.3.tgz", - "integrity": "sha512-rlVfZW/1Ph2SNySXwR9QYkChp8EkOEiTMO5Vwx60usw04i4nWemkm9RXmQqgkQFaLHsqLuADvjp6IfgL9l2M8Q==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/array.prototype.flat": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.1.tgz", - "integrity": "sha512-roTU0KWIOmJ4DRLmwKd19Otg0/mT3qPNt0Qb3GWW8iObuZXxrjB/pzn0R3hqpRSWg4HCwqx+0vwOnWnvlOyeIA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4", - "es-shim-unscopables": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/array.prototype.flatmap": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.1.tgz", - "integrity": "sha512-8UGn9O1FDVvMNB0UlLv4voxRMze7+FpHyF5mSMRjWHUMlpoDViniy05870VlxhfgTnLbpuwTzvD76MTtWxB/mQ==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4", - "es-shim-unscopables": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/arrify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", - "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/asap": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", - "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==" - }, - "node_modules/asn1.js": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz", - "integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==", - "dependencies": { - "bn.js": "^4.0.0", - "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0", - "safer-buffer": "^2.1.0" - } - }, - "node_modules/asn1.js/node_modules/bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" - }, - "node_modules/assert": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/assert/-/assert-1.3.0.tgz", - "integrity": "sha512-5aKcpD+XnHpZ7EGxsuo6uoILNh0rvm0Ypa17GlkrF2CNSPhvdgi3ft9XsL2ajdVOI2I3xuGZnHvlXAeqTZYvXg==", - "dependencies": { - "util": "0.10.3" - } - }, - "node_modules/assert-equal": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-equal/-/assert-equal-1.0.0.tgz", - "integrity": "sha512-tn2VBgrBE+hZlOVM/c634JIWjCmbjGWjb5TTB/gazrRW/v2/XumePza4eX+lRVaHPmA1/B9HAQ8nawd1ISysjA==", - "dependencies": { - "assert-ok": "~1.0.0", - "print-value": "~1.0.0", - "simple-format": "~1.0.0" - } - }, - "node_modules/assert-function": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-function/-/assert-function-1.0.0.tgz", - "integrity": "sha512-1OGDSf5VlUSNvTbr88vYH/ZBmeXFW+gay1rsnZ+HowCiHSoREtjbR9qtyGC0TibzGsiWmGPbhyLseXTgZaV4Ng==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/assert-ok": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-ok/-/assert-ok-1.0.0.tgz", - "integrity": "sha512-lCvYmCpMl8c1tp9ynExhoDEk0gGW43SVVC3RE1VYrrVKhNMy8GHfdiwZdoIM6a605s56bUAbENQxtOC0uZp3wg==" - }, - "node_modules/assert/node_modules/inherits": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", - "integrity": "sha512-8nWq2nLTAwd02jTqJExUYFSD/fKq6VH9Y/oG2accc/kdI0V98Bag8d5a4gi3XHz73rDWa2PvTtvcWYquKqSENA==" - }, - "node_modules/assert/node_modules/util": { - "version": "0.10.3", - "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", - "integrity": "sha512-5KiHfsmkqacuKjkRkdV7SsfDJ2EGiPsK92s2MhNSY0craxjTdKTtqKsJaCWp4LW33ZZ0OPUv1WO/TFvNQRiQxQ==", - "dependencies": { - "inherits": "2.0.1" - } - }, - "node_modules/ast-types": { - "version": "0.14.2", - "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.14.2.tgz", - "integrity": "sha512-O0yuUDnZeQDL+ncNGlJ78BiO4jnYI3bvMsD5prT0/nsgijG/LpNBIr63gTjVTNsiGkgQhiyCShTgxt8oXOrklA==", - "dev": true, - "dependencies": { - "tslib": "^2.0.1" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/async": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz", - "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==", - "dev": true - }, - "node_modules/async-limiter": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", - "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==", - "dev": true - }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "dev": true - }, - "node_modules/available-typed-arrays": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", - "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", - "license": "MIT", - "dependencies": { - "possible-typed-array-names": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" - }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/basic-auth": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", - "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", - "dev": true, - "dependencies": { - "safe-buffer": "5.1.2" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/basic-auth/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "node_modules/batch": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", - "integrity": "sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==", - "dev": true - }, - "node_modules/bn.js": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", - "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==" - }, - "node_modules/body": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/body/-/body-5.1.0.tgz", - "integrity": "sha512-chUsBxGRtuElD6fmw1gHLpvnKdVLK302peeFa9ZqAEk8TyzZ3fygLyUEDDPTJvL9+Bor0dIwn6ePOsRM2y0zQQ==", - "dev": true, - "dependencies": { - "continuable-cache": "^0.3.1", - "error": "^7.0.0", - "raw-body": "~1.1.0", - "safe-json-parse": "~1.0.1" - } - }, - "node_modules/boolbase": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", - "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", - "dev": true - }, - "node_modules/bootstrap": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-3.4.1.tgz", - "integrity": "sha512-yN5oZVmRCwe5aKwzRj6736nSmKDX7pLYwsXiCj/EYmo16hODaBiT4En5btW/jhBF/seV+XMx3aYwukYC3A49DA==", - "engines": { - "node": ">=6" - } - }, - "node_modules/bootstrap-daterangepicker": { - "version": "2.1.27", - "resolved": "https://registry.npmjs.org/bootstrap-daterangepicker/-/bootstrap-daterangepicker-2.1.27.tgz", - "integrity": "sha512-VutNHszlzCNDoSl2IZ8AaPcu1pFZS7HMlDQdK8nxPryDFro8mKqjar+iueb1Yz/3DOYNTtS4YOAX7UrdwiD7xA==", - "dependencies": { - "jquery": ">=1.10", - "moment": "^2.9.0" - } - }, - "node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, - "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/brorand": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", - "integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==" - }, - "node_modules/browser-pack": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/browser-pack/-/browser-pack-6.1.0.tgz", - "integrity": "sha512-erYug8XoqzU3IfcU8fUgyHqyOXqIE4tUTTQ+7mqUjQlvnXkOO6OlT9c/ZoJVHYoAaqGxr09CN53G7XIsO4KtWA==", - "dependencies": { - "combine-source-map": "~0.8.0", - "defined": "^1.0.0", - "JSONStream": "^1.0.3", - "safe-buffer": "^5.1.1", - "through2": "^2.0.0", - "umd": "^3.0.0" - }, - "bin": { - "browser-pack": "bin/cmd.js" - } - }, - "node_modules/browser-resolve": { - "version": "1.11.3", - "resolved": "https://registry.npmjs.org/browser-resolve/-/browser-resolve-1.11.3.tgz", - "integrity": "sha512-exDi1BYWB/6raKHmDTCicQfTkqwN5fioMFV4j8BsfMU4R2DK/QfZfK7kOVkmWCNANf0snkBzqGqAJBao9gZMdQ==", - "dependencies": { - "resolve": "1.1.7" - } - }, - "node_modules/browser-resolve/node_modules/resolve": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", - "integrity": "sha512-9znBF0vBcaSN3W2j7wKvdERPwqTxSpCq+if5C0WoTCyV9n24rua28jeuQ2pL/HOf+yUe/Mef+H/5p60K0Id3bg==" - }, - "node_modules/browserify": { - "version": "13.1.1", - "resolved": "https://registry.npmjs.org/browserify/-/browserify-13.1.1.tgz", - "integrity": "sha512-/a2iFDn8tc6FIU9v4fqB3SX4fFOPLZqA9BeUS1A+SWSsnzKRfL2gZkBwy01msbArQOOxypfr8v92UFq9BYgZ2A==", - "dependencies": { - "assert": "~1.3.0", - "browser-pack": "^6.0.1", - "browser-resolve": "^1.11.0", - "browserify-zlib": "~0.1.2", - "buffer": "^4.1.0", - "cached-path-relative": "^1.0.0", - "concat-stream": "~1.5.1", - "console-browserify": "^1.1.0", - "constants-browserify": "~1.0.0", - "crypto-browserify": "^3.0.0", - "defined": "^1.0.0", - "deps-sort": "^2.0.0", - "domain-browser": "~1.1.0", - "duplexer2": "~0.1.2", - "events": "~1.1.0", - "glob": "^5.0.15", - "has": "^1.0.0", - "htmlescape": "^1.1.0", - "https-browserify": "~0.0.0", - "inherits": "~2.0.1", - "insert-module-globals": "^7.0.0", - "JSONStream": "^1.0.3", - "labeled-stream-splicer": "^2.0.0", - "module-deps": "^4.0.8", - "os-browserify": "~0.1.1", - "parents": "^1.0.1", - "path-browserify": "~0.0.0", - "process": "~0.11.0", - "punycode": "^1.3.2", - "querystring-es3": "~0.2.0", - "read-only-stream": "^2.0.0", - "readable-stream": "^2.0.2", - "resolve": "^1.1.4", - "shasum": "^1.0.0", - "shell-quote": "^1.4.3", - "stream-browserify": "^2.0.0", - "stream-http": "^2.0.0", - "string_decoder": "~0.10.0", - "subarg": "^1.0.0", - "syntax-error": "^1.1.1", - "through2": "^2.0.0", - "timers-browserify": "^1.0.1", - "tty-browserify": "~0.0.0", - "url": "~0.11.0", - "util": "~0.10.1", - "vm-browserify": "~0.0.1", - "xtend": "^4.0.0" - }, - "bin": { - "browserify": "bin/cmd.js" - } - }, - "node_modules/browserify-aes": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", - "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", - "dependencies": { - "buffer-xor": "^1.0.3", - "cipher-base": "^1.0.0", - "create-hash": "^1.1.0", - "evp_bytestokey": "^1.0.3", - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/browserify-cipher": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz", - "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==", - "dependencies": { - "browserify-aes": "^1.0.4", - "browserify-des": "^1.0.0", - "evp_bytestokey": "^1.0.0" - } - }, - "node_modules/browserify-des": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz", - "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==", - "dependencies": { - "cipher-base": "^1.0.1", - "des.js": "^1.0.0", - "inherits": "^2.0.1", - "safe-buffer": "^5.1.2" - } - }, - "node_modules/browserify-rsa": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.1.0.tgz", - "integrity": "sha512-AdEER0Hkspgno2aR97SAf6vi0y0k8NuOpGnVH3O99rcA5Q6sh8QxcngtHuJ6uXwnfAXNM4Gn1Gb7/MV1+Ymbog==", - "dependencies": { - "bn.js": "^5.0.0", - "randombytes": "^2.0.1" - } - }, - "node_modules/browserify-sign": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.2.tgz", - "integrity": "sha512-1rudGyeYY42Dk6texmv7c4VcQ0EsvVbLwZkA+AQB7SxvXxmcD93jcHie8bzecJ+ChDlmAm2Qyu0+Ccg5uhZXCg==", - "dependencies": { - "bn.js": "^5.2.1", - "browserify-rsa": "^4.1.0", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "elliptic": "^6.5.4", - "inherits": "^2.0.4", - "parse-asn1": "^5.1.6", - "readable-stream": "^3.6.2", - "safe-buffer": "^5.2.1" - }, - "engines": { - "node": ">= 4" - } - }, - "node_modules/browserify-sign/node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/browserify-sign/node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, - "node_modules/browserify-zlib": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.1.4.tgz", - "integrity": "sha512-19OEpq7vWgsH6WkvkBJQDFvJS1uPcbFOQ4v9CU839dO+ZZXUZO6XpE6hNCqvlIIj+4fZvRiJ6DsAQ382GwiyTQ==", - "dependencies": { - "pako": "~0.2.0" - } - }, - "node_modules/buffer": { - "version": "4.9.2", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz", - "integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==", - "dependencies": { - "base64-js": "^1.0.2", - "ieee754": "^1.1.4", - "isarray": "^1.0.0" - } - }, - "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" - }, - "node_modules/buffer-xor": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", - "integrity": "sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ==" - }, - "node_modules/builtin-status-codes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", - "integrity": "sha512-HpGFw18DgFWlncDfjTa2rcQ4W88O1mC8e8yZ2AvQY5KDaktSTwo+KRf6nHK6FRI5FyRyb/5T6+TSxfP7QyGsmQ==" - }, - "node_modules/bytes": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-1.0.0.tgz", - "integrity": "sha512-/x68VkHLeTl3/Ll8IvxdwzhrT+IyKc52e/oyHhA2RwqPqswSnjVbSddfPRwAsJtbilMAPSRWwAlpxdYsSWOTKQ==", - "dev": true - }, - "node_modules/cached-path-relative": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/cached-path-relative/-/cached-path-relative-1.1.0.tgz", - "integrity": "sha512-WF0LihfemtesFcJgO7xfOoOcnWzY/QHR4qeDqV44jPU3HTI54+LnfXK3SA27AVVGCdZFgjjFFaqUA9Jx7dMJZA==" - }, - "node_modules/call-all-fns": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/call-all-fns/-/call-all-fns-1.0.1.tgz", - "integrity": "sha512-F7SOzeCfBzHdeYUkxwSMPwgFCTjzj2zEMGio+OyQr0x6HWOBeBfLYhh7iOo8X1HCe8gVYxoaswgq7KPLJK8nyQ==" - }, - "node_modules/call-bind": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", - "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.0", - "es-define-property": "^1.0.0", - "get-intrinsic": "^1.2.4", - "set-function-length": "^1.2.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/call-bind-apply-helpers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", - "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/call-bound": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", - "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "get-intrinsic": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/camel-case": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-3.0.0.tgz", - "integrity": "sha512-+MbKztAYHXPr1jNTSKQF52VpcFjwY5RkR7fxksV8Doo4KAYc5Fl4UJRgthBbTmEx8C54DqahhbLJkDwjI3PI/w==", - "dev": true, - "dependencies": { - "no-case": "^2.2.0", - "upper-case": "^1.1.1" - } - }, - "node_modules/camelcase": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz", - "integrity": "sha512-DLIsRzJVBQu72meAKPkWQOLcujdXT32hwdfnkI1frSiSRMK1MofjKHf+MEx0SB6fjEFXL8fBDv1dKymBlOp4Qw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/camelcase-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz", - "integrity": "sha512-bA/Z/DERHKqoEOrp+qeGKw1QlvEQkGZSc0XaY6VnTxZr+Kv1G5zFwttpjv8qxZ/sBPT4nthwZaAcsAZTJlSKXQ==", - "dev": true, - "dependencies": { - "camelcase": "^2.0.0", - "map-obj": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/checklist-model": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/checklist-model/-/checklist-model-1.0.0.tgz", - "integrity": "sha512-ZDaTrfKeUSleZjyNqTjD1+QKU6iK1qlXH1GwixX59xEKPiRFu4anDh0U8brNjjEef+SM6vyQb87YWhI5CU8eNg==", - "engines": { - "node": ">= 0.8.0" - }, - "peerDependencies": { - "angular": ">=1.0.8" - } - }, - "node_modules/cheerio": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.12.tgz", - "integrity": "sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q==", - "dev": true, - "dependencies": { - "cheerio-select": "^2.1.0", - "dom-serializer": "^2.0.0", - "domhandler": "^5.0.3", - "domutils": "^3.0.1", - "htmlparser2": "^8.0.1", - "parse5": "^7.0.0", - "parse5-htmlparser2-tree-adapter": "^7.0.0" - }, - "engines": { - "node": ">= 6" - }, - "funding": { - "url": "https://github.com/cheeriojs/cheerio?sponsor=1" - } - }, - "node_modules/cheerio-select": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz", - "integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==", - "dev": true, - "dependencies": { - "boolbase": "^1.0.0", - "css-select": "^5.1.0", - "css-what": "^6.1.0", - "domelementtype": "^2.3.0", - "domhandler": "^5.0.3", - "domutils": "^3.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } - }, - "node_modules/cipher-base": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.6.tgz", - "integrity": "sha512-3Ek9H3X6pj5TgenXYtNWdaBon1tgYCaebd+XPg0keyjEbEfkD4KkmAxkQ/i1vYvxdcT5nscLBfq9VJRmCBcFSw==", - "license": "MIT", - "dependencies": { - "inherits": "^2.0.4", - "safe-buffer": "^5.2.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/clean-css": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.2.tgz", - "integrity": "sha512-JVJbM+f3d3Q704rF4bqQ5UUyTtuJ0JRKNbTKVEeujCCBoMdkEi+V+e8oktO9qGQNSvHrFTM6JZRXrUvGR1czww==", - "dev": true, - "dependencies": { - "source-map": "~0.6.0" - }, - "engines": { - "node": ">= 10.0" - } - }, - "node_modules/clean-css/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", - "dev": true, - "engines": { - "iojs": ">= 1.0.0", - "node": ">= 0.12.0" - } - }, - "node_modules/coffee-script": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.10.0.tgz", - "integrity": "sha512-ef2EsKe2bCuX3VGXfPCNGqni3wgrL8Bu0tDdY8mUDa+QnDR1GNcsC4QhxwG4az6l5y5W0wKUc1Pn/F3MCyafjg==", - "deprecated": "CoffeeScript on NPM has moved to \"coffeescript\" (no hyphen)", - "dev": true, - "bin": { - "cake": "bin/cake", - "coffee": "bin/coffee" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/coffeescript": { - "version": "1.12.7", - "resolved": "https://registry.npmjs.org/coffeescript/-/coffeescript-1.12.7.tgz", - "integrity": "sha512-pLXHFxQMPklVoEekowk8b3erNynC+DVJzChxS/LCBBgR6/8AJkHivkm//zbowcfc7BTCAjryuhx6gPqPRfsFoA==", - "dev": true, - "bin": { - "cake": "bin/cake", - "coffee": "bin/coffee" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/colors": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/colors/-/colors-1.1.2.tgz", - "integrity": "sha512-ENwblkFQpqqia6b++zLD/KUWafYlVY/UNnAp7oz7LY7E924wmpye416wBOmvv/HMWzl8gL1kJlfvId/1Dg176w==", - "dev": true, - "engines": { - "node": ">=0.1.90" - } - }, - "node_modules/combine-source-map": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/combine-source-map/-/combine-source-map-0.8.0.tgz", - "integrity": "sha512-UlxQ9Vw0b/Bt/KYwCFqdEwsQ1eL8d1gibiFb7lxQJFdvTgc2hIZi6ugsg+kyhzhPV+QEpUiEIwInIAIrgoEkrg==", - "dependencies": { - "convert-source-map": "~1.1.0", - "inline-source-map": "~0.6.0", - "lodash.memoize": "~3.0.3", - "source-map": "~0.5.3" - } - }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true - }, - "node_modules/component-emitter": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", - "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", - "dev": true - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" - }, - "node_modules/concat-stream": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.5.2.tgz", - "integrity": "sha512-H6xsIBfQ94aESBG8jGHXQ7i5AEpy5ZeVaLDOisDICiTCKpqEfr34/KmTrspKQNoLKNu9gTkovlpQcUi630AKiQ==", - "engines": [ - "node >= 0.8" - ], - "dependencies": { - "inherits": "~2.0.1", - "readable-stream": "~2.0.0", - "typedarray": "~0.0.5" - } - }, - "node_modules/concat-stream/node_modules/process-nextick-args": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", - "integrity": "sha512-yN0WQmuCX63LP/TMvAg31nvT6m4vDqJEiiv2CAZqWOGNWutc9DfDk1NPYYmKUFmaVM2UwDowH4u5AHWYP/jxKw==" - }, - "node_modules/concat-stream/node_modules/readable-stream": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz", - "integrity": "sha512-TXcFfb63BQe1+ySzsHZI/5v1aJPCShfqvWJ64ayNImXMsN1Cd0YGk/wm8KB7/OeessgPc9QvS9Zou8QTkFzsLw==", - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "~1.0.0", - "process-nextick-args": "~1.0.6", - "string_decoder": "~0.10.x", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/confusing-browser-globals": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.11.tgz", - "integrity": "sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA==", - "dev": true - }, - "node_modules/connect": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz", - "integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==", - "dev": true, - "dependencies": { - "debug": "2.6.9", - "finalhandler": "1.1.2", - "parseurl": "~1.3.3", - "utils-merge": "1.0.1" - }, - "engines": { - "node": ">= 0.10.0" - } - }, - "node_modules/connect-livereload": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/connect-livereload/-/connect-livereload-0.6.1.tgz", - "integrity": "sha512-3R0kMOdL7CjJpU66fzAkCe6HNtd3AavCS4m+uW4KtJjrdGPT0SQEZieAYd+cm+lJoBznNQ4lqipYWkhBMgk00g==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/console-browserify": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.2.0.tgz", - "integrity": "sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA==" - }, - "node_modules/constants-browserify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz", - "integrity": "sha512-xFxOwqIzR/e1k1gLiWEophSCMqXcwVHIH7akf7b/vxcUeGunlj3hvZaaqxwHsTgn+IndtkQJgSztIDWeumWJDQ==" - }, - "node_modules/continuable-cache": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/continuable-cache/-/continuable-cache-0.3.1.tgz", - "integrity": "sha512-TF30kpKhTH8AGCG3dut0rdd/19B7Z+qCnrMoBLpyQu/2drZdNrrpcjPEoJeSVsQM+8KmWG5O56oPDjSSUsuTyA==", - "dev": true - }, - "node_modules/convert-source-map": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.1.3.tgz", - "integrity": "sha512-Y8L5rp6jo+g9VEPgvqNfEopjTR4OTYct8lXlS8iVQdmnjDvbdbzYe9rjtFCB9egC86JoNCU61WRY+ScjkZpnIg==" - }, - "node_modules/cookiejar": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.4.tgz", - "integrity": "sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==", - "dev": true - }, - "node_modules/copy-anything": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-2.0.6.tgz", - "integrity": "sha512-1j20GZTsvKNkc4BY3NpMOM8tt///wY3FpIzozTOFO2ffuZcV61nojHXVKIy3WM+7ADCy5FVhdZYHYDdgTU0yJw==", - "dependencies": { - "is-what": "^3.14.1" - }, - "funding": { - "url": "https://github.com/sponsors/mesqueeb" - } - }, - "node_modules/core-util-is": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" - }, - "node_modules/create-ecdh": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.4.tgz", - "integrity": "sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A==", - "dependencies": { - "bn.js": "^4.1.0", - "elliptic": "^6.5.3" - } - }, - "node_modules/create-ecdh/node_modules/bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" - }, - "node_modules/create-hash": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", - "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", - "dependencies": { - "cipher-base": "^1.0.1", - "inherits": "^2.0.1", - "md5.js": "^1.3.4", - "ripemd160": "^2.0.1", - "sha.js": "^2.4.0" - } - }, - "node_modules/create-hmac": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", - "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", - "dependencies": { - "cipher-base": "^1.0.3", - "create-hash": "^1.1.0", - "inherits": "^2.0.1", - "ripemd160": "^2.0.0", - "safe-buffer": "^5.0.1", - "sha.js": "^2.4.8" - } - }, - "node_modules/cross-env": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz", - "integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==", - "dev": true, - "dependencies": { - "cross-spawn": "^7.0.1" - }, - "bin": { - "cross-env": "src/bin/cross-env.js", - "cross-env-shell": "src/bin/cross-env-shell.js" - }, - "engines": { - "node": ">=10.14", - "npm": ">=6", - "yarn": ">=1" - } - }, - "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/crypto-browserify": { - "version": "3.12.0", - "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz", - "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==", - "dependencies": { - "browserify-cipher": "^1.0.0", - "browserify-sign": "^4.0.0", - "create-ecdh": "^4.0.0", - "create-hash": "^1.1.0", - "create-hmac": "^1.1.0", - "diffie-hellman": "^5.0.0", - "inherits": "^2.0.1", - "pbkdf2": "^3.0.3", - "public-encrypt": "^4.0.0", - "randombytes": "^2.0.0", - "randomfill": "^1.0.3" - }, - "engines": { - "node": "*" - } - }, - "node_modules/cson": { - "version": "7.20.0", - "resolved": "https://registry.npmjs.org/cson/-/cson-7.20.0.tgz", - "integrity": "sha512-K7g/86vC77mJqi6PRFhd3p8U/y0R6SsCeTJb5RpOAaQdApHxCoK5JwfZ8WeJjpOZ6lyyi8tNvh3UuC184H3GWg==", - "dev": true, - "dependencies": { - "cson-parser": "^4.0.5", - "extract-opts": "^4.3.0", - "requirefresh": "^4.12.0", - "safefs": "^6.12.0" - }, - "bin": { - "cson2json": "bin.cjs", - "json2cson": "bin.cjs" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://bevry.me/fund" - } - }, - "node_modules/cson-parser": { - "version": "4.0.9", - "resolved": "https://registry.npmjs.org/cson-parser/-/cson-parser-4.0.9.tgz", - "integrity": "sha512-I79SAcCYquWnEfXYj8hBqOOWKj6eH6zX1hhX3yqmS4K3bYp7jME3UFpHPzu3rUew0oyfc0s8T6IlWGXRAheHag==", - "dev": true, - "dependencies": { - "coffeescript": "1.12.7" - }, - "engines": { - "node": ">=10.13" - } - }, - "node_modules/css-select": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", - "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", - "dev": true, - "dependencies": { - "boolbase": "^1.0.0", - "css-what": "^6.1.0", - "domhandler": "^5.0.2", - "domutils": "^3.0.1", - "nth-check": "^2.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } - }, - "node_modules/css-what": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", - "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", - "dev": true, - "engines": { - "node": ">= 6" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } - }, - "node_modules/cuid": { - "version": "2.1.8", - "resolved": "https://registry.npmjs.org/cuid/-/cuid-2.1.8.tgz", - "integrity": "sha512-xiEMER6E7TlTPnDxrM4eRiC6TRgjNX9xzEZ5U/Se2YJKr7Mq4pJn/2XEHjl3STcSh96GmkHPcBXLES8M29wyyg==", - "deprecated": "Cuid and other k-sortable and non-cryptographic ids (Ulid, ObjectId, KSUID, all UUIDs) are all insecure. Use @paralleldrive/cuid2 instead." - }, - "node_modules/currently-unhandled": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", - "integrity": "sha512-/fITjgjGU50vjQ4FH6eUoYu+iUoUKIXws2hL15JJpIR+BbTxaXQsMuuyjtNh2WqsSBS5nsaZHFsFecyw5CCAng==", - "dev": true, - "dependencies": { - "array-find-index": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/d3": { - "version": "3.5.17", - "resolved": "https://registry.npmjs.org/d3/-/d3-3.5.17.tgz", - "integrity": "sha512-yFk/2idb8OHPKkbAL8QaOaqENNoMhIaSHZerk3oQsECwkObkCpJyjYwCe+OHiq6UEdhe1m8ZGARRRO3ljFjlKg==" - }, - "node_modules/dash-ast": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/dash-ast/-/dash-ast-1.0.0.tgz", - "integrity": "sha512-Vy4dx7gquTeMcQR/hDkYLGUnwVil6vk4FOOct+djUnHOUWt+zJPJAaRIXaAFkPXtJjvlY7o3rfRu0/3hpnwoUA==" - }, - "node_modules/data-uri-to-buffer": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-1.2.0.tgz", - "integrity": "sha512-vKQ9DTQPN1FLYiiEEOQ6IBGFqvjCa5rSK3cWMy/Nespm5d/x3dGFT9UBZnkLxCwua/IXBi2TYnwTEpsOvhC4UQ==", - "dev": true - }, - "node_modules/dateformat": { - "version": "4.6.3", - "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz", - "integrity": "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true - }, - "node_modules/define-data-property": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", - "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", - "license": "MIT", - "dependencies": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "gopd": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/define-lazy-prop": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", - "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/define-properties": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.0.tgz", - "integrity": "sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==", - "dev": true, - "dependencies": { - "has-property-descriptors": "^1.0.0", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/defined": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.1.tgz", - "integrity": "sha512-hsBd2qSVCRE+5PmNdHt1uzyrFu5d3RwmFDKzyNZMFq/EwDNJF7Ee5+D5oEKF0hU6LhtoUF1macFvOe4AskQC1Q==", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/degenerator": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-1.0.4.tgz", - "integrity": "sha512-EMAC+riLSC64jKfOs1jp8J7M4ZXstUUwTdwFBEv6HOzL/Ae+eAzMKEK0nJnpof2fnw9IOjmE6u6qXFejVyk8AA==", - "dev": true, - "dependencies": { - "ast-types": "0.x.x", - "escodegen": "1.x.x", - "esprima": "3.x.x" - } - }, - "node_modules/degenerator/node_modules/esprima": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-3.1.3.tgz", - "integrity": "sha512-AWwVMNxwhN8+NIPQzAQZCm7RkLC4RbM3B1OobMuyp3i+w73X57KCKaVIxaRZb+DYCojq7rspo+fmuQfAboyhFg==", - "dev": true, - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "dev": true, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/deps-sort": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/deps-sort/-/deps-sort-2.0.1.tgz", - "integrity": "sha512-1orqXQr5po+3KI6kQb9A4jnXT1PBwggGl2d7Sq2xsnOeI9GPcE/tGcF9UiSZtZBM7MukY4cAh7MemS6tZYipfw==", - "dependencies": { - "JSONStream": "^1.0.3", - "shasum-object": "^1.0.0", - "subarg": "^1.0.0", - "through2": "^2.0.0" - }, - "bin": { - "deps-sort": "bin/cmd.js" - } - }, - "node_modules/des.js": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.1.tgz", - "integrity": "sha512-Q0I4pfFrv2VPd34/vfLrFOoRmlYj3OV50i7fskps1jZWK1kApMWWT9G6RRUeYedLcBDIhnSDaUvJMb3AhUlaEA==", - "dependencies": { - "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0" - } - }, - "node_modules/destroy": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", - "dev": true, - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/detect-file": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz", - "integrity": "sha512-DtCOLG98P007x7wiiOmfI0fi3eIKyWiLTGJ2MDnVi/E04lWGbf+JzrRHMm0rgIIZJGtHpKpbVgLWHrv8xXpc3Q==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/detective": { - "version": "4.7.1", - "resolved": "https://registry.npmjs.org/detective/-/detective-4.7.1.tgz", - "integrity": "sha512-H6PmeeUcZloWtdt4DAkFyzFL94arpHr3NOwwmVILFiy+9Qd4JTxxXrzfyGk/lmct2qVGBwTSwSXagqu2BxmWig==", - "dependencies": { - "acorn": "^5.2.1", - "defined": "^1.0.0" - } - }, - "node_modules/detective/node_modules/acorn": { - "version": "5.7.4", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.4.tgz", - "integrity": "sha512-1D++VG7BhrtvQpNbBzovKNc1FLGGEE/oGe7b9xJm/RFHMBeUaUGpluV9RLjZa47YFdPcDAenEYuq9pQPcMdLJg==", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/dezalgo": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz", - "integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==", - "dependencies": { - "asap": "^2.0.0", - "wrappy": "1" - } - }, - "node_modules/diffie-hellman": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", - "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", - "dependencies": { - "bn.js": "^4.1.0", - "miller-rabin": "^4.0.0", - "randombytes": "^2.0.0" - } - }, - "node_modules/diffie-hellman/node_modules/bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" - }, - "node_modules/doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/dom-serializer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", - "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", - "dev": true, - "dependencies": { - "domelementtype": "^2.3.0", - "domhandler": "^5.0.2", - "entities": "^4.2.0" - }, - "funding": { - "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" - } - }, - "node_modules/dom-walk": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.2.tgz", - "integrity": "sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w==" - }, - "node_modules/domain-browser": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.1.7.tgz", - "integrity": "sha512-fJ5MoHxe69h3E4/lJtFRhcWwLb04bhIBSfvCEMS1YDH+/9yEZTqBHTSTgch8nCP5tE5k2gdQEjodUqJzy7qJ9Q==", - "engines": { - "node": ">=0.4", - "npm": ">=1.2" - } - }, - "node_modules/domelementtype": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", - "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ] - }, - "node_modules/domhandler": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", - "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", - "dev": true, - "dependencies": { - "domelementtype": "^2.3.0" - }, - "engines": { - "node": ">= 4" - }, - "funding": { - "url": "https://github.com/fb55/domhandler?sponsor=1" - } - }, - "node_modules/domutils": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", - "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==", - "dev": true, - "dependencies": { - "dom-serializer": "^2.0.0", - "domelementtype": "^2.3.0", - "domhandler": "^5.0.3" - }, - "funding": { - "url": "https://github.com/fb55/domutils?sponsor=1" - } - }, - "node_modules/dot-prop": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-3.0.0.tgz", - "integrity": "sha512-k4ELWeEU3uCcwub7+dWydqQBRjAjkV9L33HjVRG5Xo2QybI6ja/v+4W73SRi8ubCqJz0l9XsTP1NbewfyqaSlw==", - "dependencies": { - "is-obj": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/dunder-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/duplexer": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", - "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", - "dev": true - }, - "node_modules/duplexer2": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", - "integrity": "sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==", - "dependencies": { - "readable-stream": "^2.0.2" - } - }, - "node_modules/duplexify": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", - "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==", - "dev": true, - "dependencies": { - "end-of-stream": "^1.0.0", - "inherits": "^2.0.1", - "readable-stream": "^2.0.0", - "stream-shift": "^1.0.0" - } - }, - "node_modules/eachr": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/eachr/-/eachr-4.5.0.tgz", - "integrity": "sha512-9I664RWp6p8jvcHZIwo7bWaiSaUmA1wNSLKwNZEiaYjqiTARq3cGjyRiIunsopZv4QMmX3T5Hs17QoPAzdYxfg==", - "dev": true, - "dependencies": { - "typechecker": "^6.2.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://bevry.me/fund" - } - }, - "node_modules/ear": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/ear/-/ear-1.0.1.tgz", - "integrity": "sha512-+Juh7u1ck0G8dyLgHWti0hYWNf3fvtHz0U3fx+FCUH1C1fWfYdshbZwsA0kzjc84imHd0CuGXkh5bG0JCCJ9ww==", - "dependencies": { - "assert-function": "~1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", - "dev": true - }, - "node_modules/elliptic": { - "version": "6.6.1", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.6.1.tgz", - "integrity": "sha512-RaddvvMatK2LJHqFJ+YA4WysVN5Ita9E35botqIYspQ4TkRAlCicdzKOjlyv/1Za5RyTNn7di//eEV0uTAfe3g==", - "license": "MIT", - "dependencies": { - "bn.js": "^4.11.9", - "brorand": "^1.1.0", - "hash.js": "^1.0.0", - "hmac-drbg": "^1.0.1", - "inherits": "^2.0.4", - "minimalistic-assert": "^1.0.1", - "minimalistic-crypto-utils": "^1.0.1" - } - }, - "node_modules/elliptic/node_modules/bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" - }, - "node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "dev": true, - "dependencies": { - "once": "^1.4.0" - } - }, - "node_modules/entities": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", - "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", - "dev": true, - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/errno": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz", - "integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==", - "optional": true, - "dependencies": { - "prr": "~1.0.1" - }, - "bin": { - "errno": "cli.js" - } - }, - "node_modules/error": { - "version": "7.2.1", - "resolved": "https://registry.npmjs.org/error/-/error-7.2.1.tgz", - "integrity": "sha512-fo9HBvWnx3NGUKMvMwB/CBCMMrfEJgbDTVDEkPygA3Bdd3lM1OyCd+rbQ8BwnpF6GdVeOLDNmyL4N5Bg80ZvdA==", - "dev": true, - "dependencies": { - "string-template": "~0.2.1" - } - }, - "node_modules/error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dev": true, - "dependencies": { - "is-arrayish": "^0.2.1" - } - }, - "node_modules/es-abstract": { - "version": "1.21.2", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.21.2.tgz", - "integrity": "sha512-y/B5POM2iBnIxCiernH1G7rC9qQoM77lLIMQLuob0zhp8C56Po81+2Nj0WFKnd0pNReDTnkYryc+zhOzpEIROg==", - "dev": true, - "dependencies": { - "array-buffer-byte-length": "^1.0.0", - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", - "es-set-tostringtag": "^2.0.1", - "es-to-primitive": "^1.2.1", - "function.prototype.name": "^1.1.5", - "get-intrinsic": "^1.2.0", - "get-symbol-description": "^1.0.0", - "globalthis": "^1.0.3", - "gopd": "^1.0.1", - "has": "^1.0.3", - "has-property-descriptors": "^1.0.0", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "internal-slot": "^1.0.5", - "is-array-buffer": "^3.0.2", - "is-callable": "^1.2.7", - "is-negative-zero": "^2.0.2", - "is-regex": "^1.1.4", - "is-shared-array-buffer": "^1.0.2", - "is-string": "^1.0.7", - "is-typed-array": "^1.1.10", - "is-weakref": "^1.0.2", - "object-inspect": "^1.12.3", - "object-keys": "^1.1.1", - "object.assign": "^4.1.4", - "regexp.prototype.flags": "^1.4.3", - "safe-regex-test": "^1.0.0", - "string.prototype.trim": "^1.2.7", - "string.prototype.trimend": "^1.0.6", - "string.prototype.trimstart": "^1.0.6", - "typed-array-length": "^1.0.4", - "unbox-primitive": "^1.0.2", - "which-typed-array": "^1.1.9" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/es-define-property": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-object-atoms": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-set-tostringtag": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", - "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-shim-unscopables": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz", - "integrity": "sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==", - "dev": true, - "dependencies": { - "has": "^1.0.3" - } - }, - "node_modules/es-to-primitive": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", - "dev": true, - "dependencies": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/es6-promise": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", - "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==", - "dev": true - }, - "node_modules/es6-promisify": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", - "integrity": "sha512-C+d6UdsYDk0lMebHNR4S2NybQMMngAOnOwYBQjTOiv0MkoJMP0Myw2mgpDLBcpfCmRLxyFqYhS/CfOENq4SJhQ==", - "dev": true, - "dependencies": { - "es6-promise": "^4.0.3" - } - }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", - "dev": true - }, - "node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/escodegen": { - "version": "1.14.3", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.3.tgz", - "integrity": "sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw==", - "dev": true, - "dependencies": { - "esprima": "^4.0.1", - "estraverse": "^4.2.0", - "esutils": "^2.0.2", - "optionator": "^0.8.1" - }, - "bin": { - "escodegen": "bin/escodegen.js", - "esgenerate": "bin/esgenerate.js" - }, - "engines": { - "node": ">=4.0" - }, - "optionalDependencies": { - "source-map": "~0.6.1" - } - }, - "node_modules/escodegen/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "optional": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/eslint": { - "version": "8.45.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.45.0.tgz", - "integrity": "sha512-pd8KSxiQpdYRfYa9Wufvdoct3ZPQQuVuU5O6scNgMuOMYuxvH0IGaYK0wUFjo4UYYQQCUndlXiMbnxopwvvTiw==", - "dev": true, - "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.4.0", - "@eslint/eslintrc": "^2.1.0", - "@eslint/js": "8.44.0", - "@humanwhocodes/config-array": "^0.11.10", - "@humanwhocodes/module-importer": "^1.0.1", - "@nodelib/fs.walk": "^1.2.8", - "ajv": "^6.10.0", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.3.2", - "doctrine": "^3.0.0", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.2.0", - "eslint-visitor-keys": "^3.4.1", - "espree": "^9.6.0", - "esquery": "^1.4.2", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "globals": "^13.19.0", - "graphemer": "^1.4.0", - "ignore": "^5.2.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "is-path-inside": "^3.0.3", - "js-yaml": "^4.1.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", - "natural-compare": "^1.4.0", - "optionator": "^0.9.3", - "strip-ansi": "^6.0.1", - "text-table": "^0.2.0" - }, - "bin": { - "eslint": "bin/eslint.js" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-config-airbnb-base": { - "version": "15.0.0", - "resolved": "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-15.0.0.tgz", - "integrity": "sha512-xaX3z4ZZIcFLvh2oUNvcX5oEofXda7giYmuplVxoOg5A7EXJMrUyqRgR+mhDhPK8LZ4PttFOBvCYDbX3sUoUig==", - "dev": true, - "dependencies": { - "confusing-browser-globals": "^1.0.10", - "object.assign": "^4.1.2", - "object.entries": "^1.1.5", - "semver": "^6.3.0" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - }, - "peerDependencies": { - "eslint": "^7.32.0 || ^8.2.0", - "eslint-plugin-import": "^2.25.2" - } - }, - "node_modules/eslint-config-airbnb-base/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, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/eslint-config-prettier": { - "version": "8.8.0", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.8.0.tgz", - "integrity": "sha512-wLbQiFre3tdGgpDv67NQKnJuTlcUVYHas3k+DZCc2U2BadthoEY4B7hLPvAxaqdyOGCzuLfii2fqGph10va7oA==", - "dev": true, - "bin": { - "eslint-config-prettier": "bin/cli.js" - }, - "peerDependencies": { - "eslint": ">=7.0.0" - } - }, - "node_modules/eslint-import-resolver-node": { - "version": "0.3.7", - "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.7.tgz", - "integrity": "sha512-gozW2blMLJCeFpBwugLTGyvVjNoeo1knonXAcatC6bjPBZitotxdWf7Gimr25N4c0AAOo4eOUfaG82IJPDpqCA==", - "dev": true, - "dependencies": { - "debug": "^3.2.7", - "is-core-module": "^2.11.0", - "resolve": "^1.22.1" - } - }, - "node_modules/eslint-import-resolver-node/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/eslint-import-resolver-node/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 - }, - "node_modules/eslint-module-utils": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.8.0.tgz", - "integrity": "sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw==", - "dev": true, - "dependencies": { - "debug": "^3.2.7" - }, - "engines": { - "node": ">=4" - }, - "peerDependenciesMeta": { - "eslint": { - "optional": true - } - } - }, - "node_modules/eslint-module-utils/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/eslint-module-utils/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 - }, - "node_modules/eslint-plugin-import": { - "version": "2.27.5", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.27.5.tgz", - "integrity": "sha512-LmEt3GVofgiGuiE+ORpnvP+kAm3h6MLZJ4Q5HCyHADofsb4VzXFsRiWj3c0OFiV+3DWFh0qg3v9gcPlfc3zRow==", - "dev": true, - "dependencies": { - "array-includes": "^3.1.6", - "array.prototype.flat": "^1.3.1", - "array.prototype.flatmap": "^1.3.1", - "debug": "^3.2.7", - "doctrine": "^2.1.0", - "eslint-import-resolver-node": "^0.3.7", - "eslint-module-utils": "^2.7.4", - "has": "^1.0.3", - "is-core-module": "^2.11.0", - "is-glob": "^4.0.3", - "minimatch": "^3.1.2", - "object.values": "^1.1.6", - "resolve": "^1.22.1", - "semver": "^6.3.0", - "tsconfig-paths": "^3.14.1" - }, - "engines": { - "node": ">=4" - }, - "peerDependencies": { - "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8" - } - }, - "node_modules/eslint-plugin-import/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/eslint-plugin-import/node_modules/doctrine": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", - "dev": true, - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/eslint-plugin-import/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 - }, - "node_modules/eslint-plugin-import/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, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/eslint-scope": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.0.tgz", - "integrity": "sha512-DYj5deGlHBfMt15J7rdtyKNq/Nqlv5KfU4iodrQ019XESsRnwXH9KAE0y3cwtUHDo2ob7CypAnCqefh6vioWRw==", - "dev": true, - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-scope/node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/eslint-visitor-keys": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz", - "integrity": "sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==", - "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/eslint/node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "node_modules/eslint/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/eslint/node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint/node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint/node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/eslint/node_modules/levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/eslint/node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "node_modules/eslint/node_modules/optionator": { - "version": "0.9.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", - "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", - "dev": true, - "dependencies": { - "@aashutoshrathi/word-wrap": "^1.2.3", - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/eslint/node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint/node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint/node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/eslint/node_modules/prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/eslint/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/eslint/node_modules/type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "dependencies": { - "prelude-ls": "^1.2.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/espree": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", - "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", - "dev": true, - "dependencies": { - "acorn": "^8.9.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.4.1" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/espree/node_modules/acorn": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", - "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true, - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/esquery": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", - "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", - "dev": true, - "dependencies": { - "estraverse": "^5.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/esquery/node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esrecurse/node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/eventemitter2": { - "version": "0.4.14", - "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-0.4.14.tgz", - "integrity": "sha512-K7J4xq5xAD5jHsGM5ReWXRTFa3JRGofHiMcVgQ8PRwgWxzjHpMWCIzsmyf60+mh8KLsqYPcjUMa0AC4hd6lPyQ==", - "dev": true - }, - "node_modules/eventemitter3": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-1.2.0.tgz", - "integrity": "sha512-DOFqA1MF46fmZl2xtzXR3MPCRsXqgoFqdXcrCVYM3JNnfUeHTm/fh/v/iU7gBFpwkuBmoJPAm5GuhdDfSEJMJA==", - "dev": true - }, - "node_modules/events": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", - "integrity": "sha512-kEcvvCBByWXGnZy6JUlgAp2gBIUjfCAV6P6TgT1/aaQKcmuAEC4OZTV1I4EWQLz2gxZw76atuVyvHhTxvi0Flw==", - "engines": { - "node": ">=0.4.x" - } - }, - "node_modules/evp_bytestokey": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", - "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", - "dependencies": { - "md5.js": "^1.3.4", - "safe-buffer": "^5.1.1" - } - }, - "node_modules/exceptionless": { - "version": "1.6.4", - "resolved": "https://registry.npmjs.org/exceptionless/-/exceptionless-1.6.4.tgz", - "integrity": "sha512-XKdvXEca5Dy1tRbAtJQgj8T1Y//6sk32SAAohZlBjfCGLSCM0YcpYAlUMD4MDXYTodRYznZIxM43YI2kcvR5PQ==", - "dependencies": { - "stack-trace": "0.0.10" - } - }, - "node_modules/exit": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", - "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", - "dev": true, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/expand-tilde": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz", - "integrity": "sha512-A5EmesHW6rfnZ9ysHQjPdJRni0SRar0tjtG5MNtm9n5TUvsYU8oozprtRD4AqHxcZWWlVuAmQo2nWKfN9oyjTw==", - "dev": true, - "dependencies": { - "homedir-polyfill": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "dev": true - }, - "node_modules/extract-opts": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/extract-opts/-/extract-opts-4.3.0.tgz", - "integrity": "sha512-Dmssi1tgKJkQsVmbP7TgW/kbdc42SAsNf6h9ClweP+dS7O24YYMTXsNwPelxhx0LF15npiWwY5ahKtyQiNgxWA==", - "dev": true, - "dependencies": { - "eachr": "^4.5.0", - "typechecker": "^6.2.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://bevry.me/fund" - } - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true - }, - "node_modules/fast-safe-stringify": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", - "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==" - }, - "node_modules/fastq": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", - "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", - "dev": true, - "dependencies": { - "reusify": "^1.0.4" - } - }, - "node_modules/faye-websocket": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.10.0.tgz", - "integrity": "sha512-Xhj93RXbMSq8urNCUq4p9l0P6hnySJ/7YNRhYNug0bLOuii7pKO7xQFb5mx9xZXWCar88pLPb805PvUkwrLZpQ==", - "dev": true, - "dependencies": { - "websocket-driver": ">=0.5.1" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/figures": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", - "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", - "dev": true, - "dependencies": { - "escape-string-regexp": "^1.0.5" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", - "dev": true, - "dependencies": { - "flat-cache": "^3.0.4" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/file-sync-cmp": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/file-sync-cmp/-/file-sync-cmp-0.1.1.tgz", - "integrity": "sha512-0k45oWBokCqh2MOexeYKpyqmGKG+8mQ2Wd8iawx+uWd/weWJQAZ6SoPybagdCI4xFisag8iAR77WPm4h3pTfxA==", - "dev": true - }, - "node_modules/file-uri-to-path": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", - "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", - "dev": true - }, - "node_modules/filendir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/filendir/-/filendir-1.0.2.tgz", - "integrity": "sha512-+QyyBklJrN60IPkJTzBuwCFe2ewhHb7CS8ZIVGOzLX2mO2c6q23vY8AeKP5OSiPYEJQ/sF3JRIJjEF2vIiNzww==", - "dev": true, - "dependencies": { - "mkdirp": "^0.5.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/finalhandler": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", - "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", - "dev": true, - "dependencies": { - "debug": "2.6.9", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "on-finished": "~2.3.0", - "parseurl": "~1.3.3", - "statuses": "~1.5.0", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dev": true, - "dependencies": { - "locate-path": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/findup-sync": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-5.0.0.tgz", - "integrity": "sha512-MzwXju70AuyflbgeOhzvQWAvvQdo1XL0A9bVvlXsYcFEBM87WR4OakL4OfZq+QRmr+duJubio+UtNQCPsVESzQ==", - "dev": true, - "dependencies": { - "detect-file": "^1.0.0", - "is-glob": "^4.0.3", - "micromatch": "^4.0.4", - "resolve-dir": "^1.0.1" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/fined": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/fined/-/fined-1.2.0.tgz", - "integrity": "sha512-ZYDqPLGxDkDhDZBjZBb+oD1+j0rA4E0pXY50eplAAOPg2N/gUBSSk5IM1/QhPfyVo19lJ+CvXpqfvk+b2p/8Ng==", - "dev": true, - "dependencies": { - "expand-tilde": "^2.0.2", - "is-plain-object": "^2.0.3", - "object.defaults": "^1.1.0", - "object.pick": "^1.2.0", - "parse-filepath": "^1.0.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/flagged-respawn": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/flagged-respawn/-/flagged-respawn-1.0.1.tgz", - "integrity": "sha512-lNaHNVymajmk0OJMBn8fVUAU1BtDeKIqKoVhk4xAALB57aALg6b4W0MfJ/cUE0g9YBXy5XhSlPIpYIJ7HaY/3Q==", - "dev": true, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/flat-cache": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", - "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", - "dev": true, - "dependencies": { - "flatted": "^3.1.0", - "rimraf": "^3.0.2" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/flat-cache/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/flat-cache/node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/flatted": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", - "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", - "dev": true - }, - "node_modules/font-awesome": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/font-awesome/-/font-awesome-4.7.0.tgz", - "integrity": "sha512-U6kGnykA/6bFmg1M/oT9EkFeIYv7JlX3bozwQJWiiLz6L0w3F5vBVPxHlwyX/vtNq1ckcpRKOB9f2Qal/VtFpg==", - "engines": { - "node": ">=0.10.3" - } - }, - "node_modules/for-each": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", - "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", - "license": "MIT", - "dependencies": { - "is-callable": "^1.2.7" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/for-in": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", - "integrity": "sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/for-own": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/for-own/-/for-own-1.0.0.tgz", - "integrity": "sha512-0OABksIGrxKK8K4kynWkQ7y1zounQxP+CWnyclVwj81KW3vlLlGUx57DKGcP/LH216GzqnstnPocF16Nxs0Ycg==", - "dev": true, - "dependencies": { - "for-in": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/form-data": { - "version": "2.5.5", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.5.tgz", - "integrity": "sha512-jqdObeR2rxZZbPSGL+3VckHMYtu+f9//KXBsVny6JSX/pa38Fy+bGjuG8eW/H6USNQWhLi8Num++cU2yOCNz4A==", - "dev": true, - "license": "MIT", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "es-set-tostringtag": "^2.1.0", - "hasown": "^2.0.2", - "mime-types": "^2.1.35", - "safe-buffer": "^5.2.1" - }, - "engines": { - "node": ">= 0.12" - } - }, - "node_modules/formidable": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.2.6.tgz", - "integrity": "sha512-KcpbcpuLNOwrEjnbpMC0gS+X8ciDoZE1kkqzat4a8vrprf+s9pKNQ/QIwWfbfs4ltgmFl3MD177SNTkve3BwGQ==", - "deprecated": "Please upgrade to latest, formidable@v2 or formidable@v3! Check these notes: https://bit.ly/2ZEqIau", - "dev": true, - "funding": { - "url": "https://ko-fi.com/tunnckoCore/commissions" - } - }, - "node_modules/fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fs-extra": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-6.0.1.tgz", - "integrity": "sha512-GnyIkKhhzXZUWFCaJzvyDLEEgDkPfb4/TPvJCJVuS8MWZgoSsErf++QpiAlDnKFcqhRlm+tIOcencCjyJE6ZCA==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.1.2", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - } - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true - }, - "node_modules/ftp": { - "version": "0.3.10", - "resolved": "https://registry.npmjs.org/ftp/-/ftp-0.3.10.tgz", - "integrity": "sha512-faFVML1aBx2UoDStmLwv2Wptt4vw5x03xxX172nhA5Y5HBshW5JweqQ2W4xL4dezQTG8inJsuYcpPHHU3X5OTQ==", - "dev": true, - "dependencies": { - "readable-stream": "1.1.x", - "xregexp": "2.0.0" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/ftp/node_modules/isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==", - "dev": true - }, - "node_modules/ftp/node_modules/readable-stream": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", - "integrity": "sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ==", - "dev": true, - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "0.0.1", - "string_decoder": "~0.10.x" - } - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/function.prototype.name": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz", - "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.0", - "functions-have-names": "^1.2.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/functions-have-names": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", - "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/gaze": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/gaze/-/gaze-1.1.3.tgz", - "integrity": "sha512-BRdNm8hbWzFzWHERTrejLqwHDfS4GibPoq5wjTPIoJHoBtKGPg3xAFfxmM+9ztbXelxcf2hwQcaz1PtmFeue8g==", - "dev": true, - "dependencies": { - "globule": "^1.0.0" - }, - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/get-assigned-identifiers": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/get-assigned-identifiers/-/get-assigned-identifiers-1.2.0.tgz", - "integrity": "sha512-mBBwmeGTrxEMO4pMaaf/uUEFHnYtwr8FTe8Y/mer4rcV/bye0qGm6pw1bGZFGStxC5O76c5ZAVBGnqHmOaJpdQ==" - }, - "node_modules/get-intrinsic": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", - "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "function-bind": "^1.1.2", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "math-intrinsics": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/get-stdin": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz", - "integrity": "sha512-F5aQMywwJ2n85s4hJPTT9RPxGmubonuB10MNYo17/xph174n2MIR33HRguhzVag10O/npM7SPk73LMZNP+FaWw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/get-symbol-description": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", - "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-uri": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-2.0.4.tgz", - "integrity": "sha512-v7LT/s8kVjs+Tx0ykk1I+H/rbpzkHvuIq87LmeXptcf5sNWm9uQiwjNAt94SJPA1zOlCntmnOlJvVWKmzsxG8Q==", - "dev": true, - "dependencies": { - "data-uri-to-buffer": "1", - "debug": "2", - "extend": "~3.0.2", - "file-uri-to-path": "1", - "ftp": "~0.3.10", - "readable-stream": "2" - } - }, - "node_modules/getobject": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/getobject/-/getobject-1.0.2.tgz", - "integrity": "sha512-2zblDBaFcb3rB4rF77XVnuINOE2h2k/OnqXAiy0IrTxUfV1iFp3la33oAQVY9pCpWU268WFYVt2t71hlMuLsOg==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/glob": { - "version": "5.0.15", - "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz", - "integrity": "sha512-c9IPMazfRITpmAAKi22dK1VKxGDX9ehhqfABDriL/lzO92xcUKEJPQHrVA/2YHSNFB4iFlykVmWvwo48nr3OxA==", - "dependencies": { - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "2 || 3", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - } - }, - "node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/global": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/global/-/global-4.4.0.tgz", - "integrity": "sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w==", - "dependencies": { - "min-document": "^2.19.0", - "process": "^0.11.10" - } - }, - "node_modules/global-modules": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz", - "integrity": "sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==", - "dev": true, - "dependencies": { - "global-prefix": "^1.0.1", - "is-windows": "^1.0.1", - "resolve-dir": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/global-prefix": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-1.0.2.tgz", - "integrity": "sha512-5lsx1NUDHtSjfg0eHlmYvZKv8/nVqX4ckFbM+FrGcQ+04KWcWFo9P5MxPZYSzUvyzmdTbI7Eix8Q4IbELDqzKg==", - "dev": true, - "dependencies": { - "expand-tilde": "^2.0.2", - "homedir-polyfill": "^1.0.1", - "ini": "^1.3.4", - "is-windows": "^1.0.1", - "which": "^1.2.14" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/global-prefix/node_modules/which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "which": "bin/which" - } - }, - "node_modules/globals": { - "version": "13.20.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", - "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", - "dev": true, - "dependencies": { - "type-fest": "^0.20.2" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/globalthis": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", - "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", - "dev": true, - "dependencies": { - "define-properties": "^1.1.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/globule": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/globule/-/globule-1.3.4.tgz", - "integrity": "sha512-OPTIfhMBh7JbBYDpa5b+Q5ptmMWKwcNcFSR/0c6t8V4f3ZAVBEsKNY37QdVqmLRYSMhOUGYrY0QhSoEpzGr/Eg==", - "dev": true, - "dependencies": { - "glob": "~7.1.1", - "lodash": "^4.17.21", - "minimatch": "~3.0.2" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/globule/node_modules/glob": { - "version": "7.1.7", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", - "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/globule/node_modules/minimatch": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.8.tgz", - "integrity": "sha512-6FsRAQsxQ61mw+qP1ZzbL9Bc78x2p5OqNgNpnoAFLTrX8n5Kxph0CsnhmKKNXTWjXqU5L0pGPR7hYk+XWZr60Q==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/gopd": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "devOptional": true - }, - "node_modules/graphemer": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "dev": true - }, - "node_modules/grunt": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/grunt/-/grunt-1.6.1.tgz", - "integrity": "sha512-/ABUy3gYWu5iBmrUSRBP97JLpQUm0GgVveDCp6t3yRNIoltIYw7rEj3g5y1o2PGPR2vfTRGa7WC/LZHLTXnEzA==", - "dev": true, - "dependencies": { - "dateformat": "~4.6.2", - "eventemitter2": "~0.4.13", - "exit": "~0.1.2", - "findup-sync": "~5.0.0", - "glob": "~7.1.6", - "grunt-cli": "~1.4.3", - "grunt-known-options": "~2.0.0", - "grunt-legacy-log": "~3.0.0", - "grunt-legacy-util": "~2.0.1", - "iconv-lite": "~0.6.3", - "js-yaml": "~3.14.0", - "minimatch": "~3.0.4", - "nopt": "~3.0.6" - }, - "bin": { - "grunt": "bin/grunt" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/grunt-angular-templates": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/grunt-angular-templates/-/grunt-angular-templates-1.2.0.tgz", - "integrity": "sha512-W/cwZTBN/MpeWaEv37SIFuUlw9A4BsZkybjDdjrmGzHGqaEBin6lZpA/d5NGLxNDiFn8R62gdr8Ti7zmhl5JnQ==", - "dev": true, - "dependencies": { - "html-minifier": "~4.0.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/grunt-browser-output": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/grunt-browser-output/-/grunt-browser-output-1.0.3.tgz", - "integrity": "sha512-miBN8HF1LxSmUsc/DIlrcIp15ncDmWgRlZ3Cg7gnTQEWAg++3c4CKg26vBRn3INXhVyM+D+NO/hEQBQCAObTcg==", - "dev": true, - "dependencies": { - "ansi-to-html": "~0.1.1", - "hooker": "~0.2.3", - "ws": "~0.4.31" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/grunt-cache-bust": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/grunt-cache-bust/-/grunt-cache-bust-1.7.0.tgz", - "integrity": "sha512-f0bh/WWQeHW6lMpLnQAVsJlmGH6sPRmlxk2+db1tF5HpOCZ4pyYHHEQPzPwHILzLhOXBqq02JXR5LIdH7flFdw==", - "dev": true, - "dependencies": { - "fs-extra": "^6.0.1" - }, - "engines": { - "node": ">=0.10.0" - }, - "peerDependencies": { - "grunt": ">=0.4.0" - } - }, - "node_modules/grunt-cli": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/grunt-cli/-/grunt-cli-1.4.3.tgz", - "integrity": "sha512-9Dtx/AhVeB4LYzsViCjUQkd0Kw0McN2gYpdmGYKtE2a5Yt7v1Q+HYZVWhqXc/kGnxlMtqKDxSwotiGeFmkrCoQ==", - "dev": true, - "dependencies": { - "grunt-known-options": "~2.0.0", - "interpret": "~1.1.0", - "liftup": "~3.0.1", - "nopt": "~4.0.1", - "v8flags": "~3.2.0" - }, - "bin": { - "grunt": "bin/grunt" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/grunt-cli/node_modules/nopt": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.3.tgz", - "integrity": "sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg==", - "dev": true, - "dependencies": { - "abbrev": "1", - "osenv": "^0.1.4" - }, - "bin": { - "nopt": "bin/nopt.js" - } - }, - "node_modules/grunt-connect-proxy2": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/grunt-connect-proxy2/-/grunt-connect-proxy2-2.0.0.tgz", - "integrity": "sha512-Qwf00JO/UbEW5RiHQ46t1ng1wfIUlr6jHXLvW30/p62NcWzd3YWCrN3m1AsWKkSnS7AIHOwnnaOcpYc6T86rUA==", - "dev": true, - "dependencies": { - "grunt": "1.0.1", - "http-proxy": "1.15.2", - "lodash": "4.16.4" - }, - "engines": { - "node": ">= 0.10.0" - }, - "peerDependencies": { - "grunt": "1.0.1" - } - }, - "node_modules/grunt-connect-proxy2/node_modules/ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/grunt-connect-proxy2/node_modules/async": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", - "integrity": "sha512-nSVgobk4rv61R9PUSDtYt7mPVB2olxNR5RWJcAsH676/ef11bUZwvu7+RGYrYauVdDPcO519v68wRhXQtxsV9w==", - "dev": true - }, - "node_modules/grunt-connect-proxy2/node_modules/chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==", - "dev": true, - "dependencies": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/grunt-connect-proxy2/node_modules/dateformat": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-1.0.12.tgz", - "integrity": "sha512-5sFRfAAmbHdIts+eKjR9kYJoF0ViCMVX9yqLu5A7S/v+nd077KgCITOMiirmyCBiZpKLDXbBOkYm6tu7rX/TKg==", - "dev": true, - "dependencies": { - "get-stdin": "^4.0.1", - "meow": "^3.3.0" - }, - "bin": { - "dateformat": "bin/cli.js" - }, - "engines": { - "node": "*" - } - }, - "node_modules/grunt-connect-proxy2/node_modules/esprima": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", - "integrity": "sha512-OarPfz0lFCiW4/AV2Oy1Rp9qu0iusTKqykwTspGCZtPxmF81JR4MmIebvF1F9+UOKth2ZubLQ4XGGaU+hSn99A==", - "dev": true, - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/grunt-connect-proxy2/node_modules/findup-sync": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-0.3.0.tgz", - "integrity": "sha512-z8Nrwhi6wzxNMIbxlrTzuUW6KWuKkogZ/7OdDVq+0+kxn77KUH1nipx8iU6suqkHqc4y6n7a9A8IpmxY/pTjWg==", - "dev": true, - "dependencies": { - "glob": "~5.0.0" - }, - "engines": { - "node": ">= 0.6.0" - } - }, - "node_modules/grunt-connect-proxy2/node_modules/getobject": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/getobject/-/getobject-0.1.0.tgz", - "integrity": "sha512-hIGEBfnHcZpWkXPsAVeVmpYDvfy/matVl03yOY91FPmnpCC12Lm5izNxCjO3lHAeO6uaTwMxu7g450Siknlhig==", - "dev": true, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/grunt-connect-proxy2/node_modules/grunt": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/grunt/-/grunt-1.0.1.tgz", - "integrity": "sha512-1hqGwYZGk7NCh6QioejJRdZue0CVCiNaqwWffgVqHRW3HDgDJrpIroWYs/SmRhICNanfjZiWX36ECyy8ayryGg==", - "dev": true, - "dependencies": { - "coffee-script": "~1.10.0", - "dateformat": "~1.0.12", - "eventemitter2": "~0.4.13", - "exit": "~0.1.1", - "findup-sync": "~0.3.0", - "glob": "~7.0.0", - "grunt-cli": "~1.2.0", - "grunt-known-options": "~1.1.0", - "grunt-legacy-log": "~1.0.0", - "grunt-legacy-util": "~1.0.0", - "iconv-lite": "~0.4.13", - "js-yaml": "~3.5.2", - "minimatch": "~3.0.0", - "nopt": "~3.0.6", - "path-is-absolute": "~1.0.0", - "rimraf": "~2.2.8" - }, - "bin": { - "grunt": "bin/grunt" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/grunt-connect-proxy2/node_modules/grunt-cli": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/grunt-cli/-/grunt-cli-1.2.0.tgz", - "integrity": "sha512-8oM6ZAe4yG8Y7co/Ejc9613AixyN+gdCADyAFvJ1BbHGvrNa0ltaqrEWXV9P/W0gbQbAh3C8swJIaDuAX7syiw==", - "dev": true, - "dependencies": { - "findup-sync": "~0.3.0", - "grunt-known-options": "~1.1.0", - "nopt": "~3.0.6", - "resolve": "~1.1.0" - }, - "bin": { - "grunt": "bin/grunt" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/grunt-connect-proxy2/node_modules/grunt-known-options": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/grunt-known-options/-/grunt-known-options-1.1.1.tgz", - "integrity": "sha512-cHwsLqoighpu7TuYj5RonnEuxGVFnztcUqTqp5rXFGYL4OuPFofwC4Ycg7n9fYwvK6F5WbYgeVOwph9Crs2fsQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/grunt-connect-proxy2/node_modules/grunt-legacy-log": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/grunt-legacy-log/-/grunt-legacy-log-1.0.2.tgz", - "integrity": "sha512-WdedTJ/6zCXnI/coaouzqvkI19uwqbcPkdsXiDRKJyB5rOUlOxnCnTVbpeUdEckKVir2uHF3rDBYppj2p6N3+g==", - "dev": true, - "dependencies": { - "colors": "~1.1.2", - "grunt-legacy-log-utils": "~1.0.0", - "hooker": "~0.2.3", - "lodash": "~4.17.5" - }, - "engines": { - "node": ">= 0.10.0" - } - }, - "node_modules/grunt-connect-proxy2/node_modules/grunt-legacy-log-utils": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/grunt-legacy-log-utils/-/grunt-legacy-log-utils-1.0.0.tgz", - "integrity": "sha512-lUTZ5ndWtpPQgVEXUm2efZEYhc8fMC/trcAipiIkiPNomSA8yGxEbvrmZz9y9lW0eeXRGYjF2AnQbfyP6rpZzQ==", - "dev": true, - "dependencies": { - "chalk": "~1.1.1", - "lodash": "~4.3.0" - }, - "engines": { - "node": ">= 0.10.0" - } - }, - "node_modules/grunt-connect-proxy2/node_modules/grunt-legacy-log-utils/node_modules/lodash": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.3.0.tgz", - "integrity": "sha512-JDXk2rxAUY3cHGG9OJfRbhd7zc2feQRdMthkMdqmK19l0+ojybg9ISylGUnmqHtteg/wXH8QudOLN+RKgKNKIQ==", - "dev": true - }, - "node_modules/grunt-connect-proxy2/node_modules/grunt-legacy-log/node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true - }, - "node_modules/grunt-connect-proxy2/node_modules/grunt-legacy-util": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/grunt-legacy-util/-/grunt-legacy-util-1.0.0.tgz", - "integrity": "sha512-KvPwmOldHh7qeFMnlxKyoO7XO6cFC98xtPM8IVQu24txjKIJ6RBLcL7g/qv5NFmiKgg0xCS3CSH78quoeYBWcg==", - "dev": true, - "dependencies": { - "async": "~1.5.2", - "exit": "~0.1.1", - "getobject": "~0.1.0", - "hooker": "~0.2.3", - "lodash": "~4.3.0", - "underscore.string": "~3.2.3", - "which": "~1.2.1" - }, - "engines": { - "node": ">= 0.10.0" - } - }, - "node_modules/grunt-connect-proxy2/node_modules/grunt-legacy-util/node_modules/lodash": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.3.0.tgz", - "integrity": "sha512-JDXk2rxAUY3cHGG9OJfRbhd7zc2feQRdMthkMdqmK19l0+ojybg9ISylGUnmqHtteg/wXH8QudOLN+RKgKNKIQ==", - "dev": true - }, - "node_modules/grunt-connect-proxy2/node_modules/grunt/node_modules/glob": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.0.6.tgz", - "integrity": "sha512-f8c0rE8JiCxpa52kWPAOa3ZaYEnzofDzCQLCn3Vdk0Z5OVLq3BsRFJI4S4ykpeVW6QMGBUkMeUpoEgWnMTnw5Q==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.2", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - } - }, - "node_modules/grunt-connect-proxy2/node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dev": true, - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/grunt-connect-proxy2/node_modules/js-yaml": { - "version": "3.5.5", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.5.5.tgz", - "integrity": "sha512-epxjSConucMdujAtGaMmQ5PG+9562qhRQHmbHy29hoq06f5Ah9K5ez/b5nkYUyVrlGzGkxHma0NLIBKW0Zr9hw==", - "dev": true, - "dependencies": { - "argparse": "^1.0.2", - "esprima": "^2.6.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/grunt-connect-proxy2/node_modules/lodash": { - "version": "4.16.4", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.16.4.tgz", - "integrity": "sha512-nnaYhZqDvVKdmBJYqTAxIXxVUlRNwr8yfDKjKSpBEwoxi0Os+e5pU+/GtSO0mtTd06g49RCINYTmuixJR1Tz2w==", - "dev": true - }, - "node_modules/grunt-connect-proxy2/node_modules/minimatch": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.8.tgz", - "integrity": "sha512-6FsRAQsxQ61mw+qP1ZzbL9Bc78x2p5OqNgNpnoAFLTrX8n5Kxph0CsnhmKKNXTWjXqU5L0pGPR7hYk+XWZr60Q==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/grunt-connect-proxy2/node_modules/resolve": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", - "integrity": "sha512-9znBF0vBcaSN3W2j7wKvdERPwqTxSpCq+if5C0WoTCyV9n24rua28jeuQ2pL/HOf+yUe/Mef+H/5p60K0Id3bg==", - "dev": true - }, - "node_modules/grunt-connect-proxy2/node_modules/rimraf": { - "version": "2.2.8", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.2.8.tgz", - "integrity": "sha512-R5KMKHnPAQaZMqLOsyuyUmcIjSeDm+73eoqQpaXA7AZ22BL+6C+1mcUscgOsNd8WVlJuvlgAPsegcx7pjlV0Dg==", - "dev": true, - "bin": { - "rimraf": "bin.js" - } - }, - "node_modules/grunt-connect-proxy2/node_modules/supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/grunt-connect-proxy2/node_modules/underscore.string": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-3.2.3.tgz", - "integrity": "sha512-4FNx1KnBckIW9Z7XvptVBzhWZvyuaB9NC3fdqdAp6GIRY4r6eDAENOZx2dzPrriQVZctkTYw2J7Vi1vjHS2E3A==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/grunt-connect-proxy2/node_modules/which": { - "version": "1.2.14", - "resolved": "https://registry.npmjs.org/which/-/which-1.2.14.tgz", - "integrity": "sha512-16uPglFkRPzgiUXYMi1Jf8Z5EzN1iB4V0ZtMXcHZnwsBtQhhHeCqoWw7tsUY42hJGNDWtUsVLTjakIa5BgAxCw==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "which": "bin/which" - } - }, - "node_modules/grunt-contrib-clean": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/grunt-contrib-clean/-/grunt-contrib-clean-2.0.1.tgz", - "integrity": "sha512-uRvnXfhiZt8akb/ZRDHJpQQtkkVkqc/opWO4Po/9ehC2hPxgptB9S6JHDC/Nxswo4CJSM0iFPT/Iym3cEMWzKA==", - "dev": true, - "dependencies": { - "async": "^3.2.3", - "rimraf": "^2.6.2" - }, - "engines": { - "node": ">=12" - }, - "peerDependencies": { - "grunt": ">=0.4.5" - } - }, - "node_modules/grunt-contrib-concat": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/grunt-contrib-concat/-/grunt-contrib-concat-2.1.0.tgz", - "integrity": "sha512-Vnl95JIOxfhEN7bnYIlCgQz41kkbi7tsZ/9a4usZmxNxi1S2YAIOy8ysFmO8u4MN26Apal1O106BwARdaNxXQw==", - "dev": true, - "dependencies": { - "chalk": "^4.1.2", - "source-map": "^0.5.3" - }, - "engines": { - "node": ">=0.12.0" - }, - "peerDependencies": { - "grunt": ">=1.4.1" - } - }, - "node_modules/grunt-contrib-connect": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/grunt-contrib-connect/-/grunt-contrib-connect-4.0.0.tgz", - "integrity": "sha512-VR2/+ailwTClAXrvI7bK78roCZzfY1C48vmpdRldohx8P1VXcb51NmBNhukBvG2RKFChNheEcKEcM+wSb/5nYA==", - "dev": true, - "dependencies": { - "async": "^3.2.0", - "connect": "^3.7.0", - "connect-livereload": "^0.6.1", - "morgan": "^1.10.0", - "node-http2": "^4.0.1", - "open": "^8.0.0", - "portscanner": "^2.2.0", - "serve-index": "^1.9.1", - "serve-static": "^1.14.1" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/grunt-contrib-copy": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/grunt-contrib-copy/-/grunt-contrib-copy-1.0.0.tgz", - "integrity": "sha512-gFRFUB0ZbLcjKb67Magz1yOHGBkyU6uL29hiEW1tdQ9gQt72NuMKIy/kS6dsCbV0cZ0maNCb0s6y+uT1FKU7jA==", - "dev": true, - "dependencies": { - "chalk": "^1.1.1", - "file-sync-cmp": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/grunt-contrib-copy/node_modules/ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/grunt-contrib-copy/node_modules/chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==", - "dev": true, - "dependencies": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/grunt-contrib-copy/node_modules/supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/grunt-contrib-cssmin": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/grunt-contrib-cssmin/-/grunt-contrib-cssmin-5.0.0.tgz", - "integrity": "sha512-SNp4H4+85mm2xaHYi83FBHuOXylpi5vcwgtNoYCZBbkgeXQXoeTAKa59VODRb0woTDBvxouP91Ff5PzCkikg6g==", - "dev": true, - "dependencies": { - "chalk": "^4.1.2", - "clean-css": "^5.3.2", - "maxmin": "^3.0.0" - }, - "engines": { - "node": ">=14.0" - } - }, - "node_modules/grunt-contrib-htmlmin": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/grunt-contrib-htmlmin/-/grunt-contrib-htmlmin-3.1.0.tgz", - "integrity": "sha512-Khaa+0MUuqqNroDIe9tsjZkioZnW2Y+iTGbonBkLWaG7+SkSFExfb4jLt7M6rxKV3RSqlS7NtVvu4SVIPkmKXg==", - "dev": true, - "dependencies": { - "chalk": "^2.4.2", - "html-minifier": "^4.0.0", - "pretty-bytes": "^5.1.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/grunt-contrib-htmlmin/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/grunt-contrib-htmlmin/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/grunt-contrib-htmlmin/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/grunt-contrib-htmlmin/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true - }, - "node_modules/grunt-contrib-htmlmin/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/grunt-contrib-htmlmin/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/grunt-contrib-less": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/grunt-contrib-less/-/grunt-contrib-less-3.0.0.tgz", - "integrity": "sha512-fBB8MUDCo5EgT7WdOVQnZq4GF+XCeFdnkhaxI7uepp8P973sH1jdodjF87c6d9WSHKgArJAGP5JEtthhdKVovg==", - "dev": true, - "dependencies": { - "async": "^3.2.0", - "chalk": "^4.1.0", - "less": "^4.1.1", - "lodash": "^4.17.21" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/grunt-contrib-uglify": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/grunt-contrib-uglify/-/grunt-contrib-uglify-5.2.2.tgz", - "integrity": "sha512-ITxiWxrjjP+RZu/aJ5GLvdele+sxlznh+6fK9Qckio5ma8f7Iv8woZjRkGfafvpuygxNefOJNc+hfjjBayRn2Q==", - "dev": true, - "dependencies": { - "chalk": "^4.1.2", - "maxmin": "^3.0.0", - "uglify-js": "^3.16.1", - "uri-path": "^1.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/grunt-contrib-watch": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/grunt-contrib-watch/-/grunt-contrib-watch-1.1.0.tgz", - "integrity": "sha512-yGweN+0DW5yM+oo58fRu/XIRrPcn3r4tQx+nL7eMRwjpvk+rQY6R8o94BPK0i2UhTg9FN21hS+m8vR8v9vXfeg==", - "dev": true, - "dependencies": { - "async": "^2.6.0", - "gaze": "^1.1.0", - "lodash": "^4.17.10", - "tiny-lr": "^1.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/grunt-contrib-watch/node_modules/async": { - "version": "2.6.4", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", - "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", - "dev": true, - "dependencies": { - "lodash": "^4.17.14" - } - }, - "node_modules/grunt-dom-munger": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/grunt-dom-munger/-/grunt-dom-munger-4.0.0.tgz", - "integrity": "sha512-+VjKRfPTYPuonPt9abzu+jk3+6avVXAO99RU/zs3nENPpnTnsW6EIzIdCzbpmXxpyyOE+XVbG/n9tACWgPLapw==", - "dev": true, - "dependencies": { - "cheerio": "^1.0.0-rc.10" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "grunt": "^1.1.0" - } - }, - "node_modules/grunt-eslint": { - "version": "24.3.0", - "resolved": "https://registry.npmjs.org/grunt-eslint/-/grunt-eslint-24.3.0.tgz", - "integrity": "sha512-dUPiRgX8fhmh4uwTAn9xrzg7HV5j5DhGmZZGJdHfjy/AN9G4jD+5IjfbcAJ209JcIG8m4B7xz3crIhuDSm3siQ==", - "dev": true, - "dependencies": { - "chalk": "^4.1.2", - "eslint": "^8.44.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - }, - "peerDependencies": { - "grunt": ">=1" - } - }, - "node_modules/grunt-html-angular-validate": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/grunt-html-angular-validate/-/grunt-html-angular-validate-0.6.1.tgz", - "integrity": "sha512-dy6+SxOWYMxmv6izFJHNrOqN5Xq6nNplIVOecp3QhychhWcniLFm5ebRiRYgCDMTBRLwGR0cXm3JOoV0xy/ezA==", - "dev": true, - "dependencies": { - "colors": "~1.1.2", - "html-angular-validate": "~0.2.3" - }, - "engines": { - "node": ">= 5.0.0" - } - }, - "node_modules/grunt-known-options": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/grunt-known-options/-/grunt-known-options-2.0.0.tgz", - "integrity": "sha512-GD7cTz0I4SAede1/+pAbmJRG44zFLPipVtdL9o3vqx9IEyb7b4/Y3s7r6ofI3CchR5GvYJ+8buCSioDv5dQLiA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/grunt-legacy-log": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/grunt-legacy-log/-/grunt-legacy-log-3.0.0.tgz", - "integrity": "sha512-GHZQzZmhyq0u3hr7aHW4qUH0xDzwp2YXldLPZTCjlOeGscAOWWPftZG3XioW8MasGp+OBRIu39LFx14SLjXRcA==", - "dev": true, - "dependencies": { - "colors": "~1.1.2", - "grunt-legacy-log-utils": "~2.1.0", - "hooker": "~0.2.3", - "lodash": "~4.17.19" - }, - "engines": { - "node": ">= 0.10.0" - } - }, - "node_modules/grunt-legacy-log-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/grunt-legacy-log-utils/-/grunt-legacy-log-utils-2.1.0.tgz", - "integrity": "sha512-lwquaPXJtKQk0rUM1IQAop5noEpwFqOXasVoedLeNzaibf/OPWjKYvvdqnEHNmU+0T0CaReAXIbGo747ZD+Aaw==", - "dev": true, - "dependencies": { - "chalk": "~4.1.0", - "lodash": "~4.17.19" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/grunt-legacy-util": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/grunt-legacy-util/-/grunt-legacy-util-2.0.1.tgz", - "integrity": "sha512-2bQiD4fzXqX8rhNdXkAywCadeqiPiay0oQny77wA2F3WF4grPJXCvAcyoWUJV+po/b15glGkxuSiQCK299UC2w==", - "dev": true, - "dependencies": { - "async": "~3.2.0", - "exit": "~0.1.2", - "getobject": "~1.0.0", - "hooker": "~0.2.3", - "lodash": "~4.17.21", - "underscore.string": "~3.3.5", - "which": "~2.0.2" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/grunt-ng-annotate": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/grunt-ng-annotate/-/grunt-ng-annotate-4.0.0.tgz", - "integrity": "sha512-dQMZsztaf5Uvysw5aiX/ZGmHYqBXpdojBgxBS4aiGpvHJSz436PEpYmCvHsZNiLNDq+TWtY34TMCsKbE09juwA==", - "deprecated": "grunt-ng-annotate is deprecated. Switch to babel-plugin-angularjs-annotate or provide annotations by yourself.", - "dev": true, - "dependencies": { - "ng-annotate": "^1.2.2" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "grunt": ">=0.4.5" - } - }, - "node_modules/grunt-replace": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/grunt-replace/-/grunt-replace-2.0.2.tgz", - "integrity": "sha512-cv7ua3vBeUpCBkb+L4vZ8hwE8Sz6Sf/mb3031kufKVfKAestekHSsDO0BK63mhrRknsAHlnkHkieIxrjHS/fJA==", - "dev": true, - "dependencies": { - "applause": "^2.0.0", - "chalk": "^4.1.0", - "file-sync-cmp": "^0.1.0", - "lodash": "^4.17.21" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/grunt/node_modules/glob": { - "version": "7.1.7", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", - "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/grunt/node_modules/minimatch": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.8.tgz", - "integrity": "sha512-6FsRAQsxQ61mw+qP1ZzbL9Bc78x2p5OqNgNpnoAFLTrX8n5Kxph0CsnhmKKNXTWjXqU5L0pGPR7hYk+XWZr60Q==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/gzip-size": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-5.1.1.tgz", - "integrity": "sha512-FNHi6mmoHvs1mxZAds4PpdCS6QG8B4C1krxJsMutgxl5t3+GlRTzzI3NEkifXx2pVsOvJdOGSmIgDhQ55FwdPA==", - "dev": true, - "dependencies": { - "duplexer": "^0.1.1", - "pify": "^4.0.1" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/gzip-size/node_modules/pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/handlebars": { - "version": "4.7.7", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.7.tgz", - "integrity": "sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA==", - "dependencies": { - "minimist": "^1.2.5", - "neo-async": "^2.6.0", - "source-map": "^0.6.1", - "wordwrap": "^1.0.0" - }, - "bin": { - "handlebars": "bin/handlebars" - }, - "engines": { - "node": ">=0.4.7" - }, - "optionalDependencies": { - "uglify-js": "^3.1.4" - } - }, - "node_modules/handlebars/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/handlebars/node_modules/wordwrap": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", - "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==" - }, - "node_modules/has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dependencies": { - "function-bind": "^1.1.1" - }, - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/has-ansi": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", - "integrity": "sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==", - "dev": true, - "dependencies": { - "ansi-regex": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/has-bigints": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", - "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/has-property-descriptors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", - "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", - "license": "MIT", - "dependencies": { - "es-define-property": "^1.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", - "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-tostringtag": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", - "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", - "license": "MIT", - "dependencies": { - "has-symbols": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/hash-base": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz", - "integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==", - "dependencies": { - "inherits": "^2.0.4", - "readable-stream": "^3.6.0", - "safe-buffer": "^5.2.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/hash-base/node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/hash-base/node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, - "node_modules/hash.js": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", - "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", - "dependencies": { - "inherits": "^2.0.3", - "minimalistic-assert": "^1.0.1" - } - }, - "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/he": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", - "dev": true, - "bin": { - "he": "bin/he" - } - }, - "node_modules/hmac-drbg": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", - "integrity": "sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==", - "dependencies": { - "hash.js": "^1.0.3", - "minimalistic-assert": "^1.0.0", - "minimalistic-crypto-utils": "^1.0.1" - } - }, - "node_modules/homedir-polyfill": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", - "integrity": "sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==", - "dev": true, - "dependencies": { - "parse-passwd": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/hooker": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/hooker/-/hooker-0.2.3.tgz", - "integrity": "sha512-t+UerCsQviSymAInD01Pw+Dn/usmz1sRO+3Zk1+lx8eg+WKpD2ulcwWqHHL0+aseRBr+3+vIhiG1K1JTwaIcTA==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/hosted-git-info": { - "version": "2.8.9", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", - "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", - "dev": true - }, - "node_modules/html-angular-validate": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/html-angular-validate/-/html-angular-validate-0.2.3.tgz", - "integrity": "sha512-4UQ/6xKPd/hOEZJ+WQ77XeW+wZgu1iWr2YudgfDvxJcCVeZz5G2KtP8ITJnz53jwOkyRRt70K9jSCN9O8MYRVA==", - "dev": true, - "dependencies": { - "async": "^2.6.0", - "filendir": "~1.0.0", - "globule": "^1.2.0", - "node.extend": "^2.0.0", - "string.prototype.endswith": "~0.2.0", - "w3cjs": "^0.4.0", - "xmlbuilder": "^9.0.4" - }, - "engines": { - "node": ">= 5.0.0" - } - }, - "node_modules/html-angular-validate/node_modules/async": { - "version": "2.6.4", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", - "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", - "dev": true, - "dependencies": { - "lodash": "^4.17.14" - } - }, - "node_modules/html-minifier": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/html-minifier/-/html-minifier-4.0.0.tgz", - "integrity": "sha512-aoGxanpFPLg7MkIl/DDFYtb0iWz7jMFGqFhvEDZga6/4QTjneiD8I/NXL1x5aaoCp7FSIT6h/OhykDdPsbtMig==", - "dev": true, - "dependencies": { - "camel-case": "^3.0.0", - "clean-css": "^4.2.1", - "commander": "^2.19.0", - "he": "^1.2.0", - "param-case": "^2.1.1", - "relateurl": "^0.2.7", - "uglify-js": "^3.5.1" - }, - "bin": { - "html-minifier": "cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/html-minifier/node_modules/clean-css": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.4.tgz", - "integrity": "sha512-EJUDT7nDVFDvaQgAo2G/PJvxmp1o/c6iXLbswsBbUFXi1Nr+AjA2cKmfbKDMjMvzEe75g3P6JkaDDAKk96A85A==", - "dev": true, - "dependencies": { - "source-map": "~0.6.0" - }, - "engines": { - "node": ">= 4.0" - } - }, - "node_modules/html-minifier/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/htmlescape": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/htmlescape/-/htmlescape-1.1.1.tgz", - "integrity": "sha512-eVcrzgbR4tim7c7soKQKtxa/kQM4TzjnlU83rcZ9bHU6t31ehfV7SktN6McWgwPWg+JYMA/O3qpGxBvFq1z2Jg==", - "engines": { - "node": ">=0.10" - } - }, - "node_modules/htmlparser2": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz", - "integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==", - "dev": true, - "funding": [ - "https://github.com/fb55/htmlparser2?sponsor=1", - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ], - "dependencies": { - "domelementtype": "^2.3.0", - "domhandler": "^5.0.3", - "domutils": "^3.0.1", - "entities": "^4.4.0" - } - }, - "node_modules/http-errors": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", - "integrity": "sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==", - "dev": true, - "dependencies": { - "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.0", - "statuses": ">= 1.4.0 < 2" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/http-errors/node_modules/depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/http-errors/node_modules/inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", - "dev": true - }, - "node_modules/http-parser-js": { - "version": "0.5.8", - "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.8.tgz", - "integrity": "sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==", - "dev": true - }, - "node_modules/http-proxy": { - "version": "1.15.2", - "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.15.2.tgz", - "integrity": "sha512-E6A/0XNoSfQuCUtxoHuj788eRfZPIp3eCXpQHe2uV+Ebbkq6dvcWSbb9lrC9NdMBedAZBNMB5JRRA3kiiyRRxg==", - "dev": true, - "dependencies": { - "eventemitter3": "1.x.x", - "requires-port": "1.x.x" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/http-proxy-agent": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-2.1.0.tgz", - "integrity": "sha512-qwHbBLV7WviBl0rQsOzH6o5lwyOIvwp/BdFnvVxXORldu5TmjFfjzBcWUWS5kWAZhmv+JtiDhSuQCp4sBfbIgg==", - "dev": true, - "dependencies": { - "agent-base": "4", - "debug": "3.1.0" - }, - "engines": { - "node": ">= 4.5.0" - } - }, - "node_modules/http-proxy-agent/node_modules/debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/https-browserify": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-0.0.1.tgz", - "integrity": "sha512-EjDQFbgJr1vDD/175UJeSX3ncQ3+RUnCL5NkthQGHvF4VNHlzTy8ifJfTqz47qiPRqaFH58+CbuG3x51WuB1XQ==" - }, - "node_modules/https-proxy-agent": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz", - "integrity": "sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==", - "dev": true, - "dependencies": { - "agent-base": "^4.3.0", - "debug": "^3.1.0" - }, - "engines": { - "node": ">= 4.5.0" - } - }, - "node_modules/https-proxy-agent/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/https-proxy-agent/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 - }, - "node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "devOptional": true, - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/ignore": { - "version": "5.2.4", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", - "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/image-size": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/image-size/-/image-size-0.5.5.tgz", - "integrity": "sha512-6TDAlDPZxUFCv+fuOkIoXT/V/f3Qbq8e37p+YOiYrUv3v9cc3/6x78VdfPgFVaB9dZYeLUfKgHRebpkm/oP2VQ==", - "optional": true, - "bin": { - "image-size": "bin/image-size.js" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/import-fresh/node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true, - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/indent-string": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz", - "integrity": "sha512-aqwDFWSgSgfRaEwao5lg5KEcVd/2a+D1rvoG7NdilmYz0NwRk6StWpWdz/Hpk34MKPpx7s8XxUqimfcQK6gGlg==", - "dev": true, - "dependencies": { - "repeating": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/indexof": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz", - "integrity": "sha512-i0G7hLJ1z0DE8dsqJa2rycj9dBmNKgXBvotXtZYXakU9oivfB9Uj2ZBC27qqef2U58/ZLwalxa1X/RDCdkHtVg==" - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "node_modules/ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", - "dev": true - }, - "node_modules/inline-source-map": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/inline-source-map/-/inline-source-map-0.6.2.tgz", - "integrity": "sha512-0mVWSSbNDvedDWIN4wxLsdPM4a7cIPcpyMxj3QZ406QRwQ6ePGB1YIHxVPjqpcUGbWQ5C+nHTwGNWAGvt7ggVA==", - "dependencies": { - "source-map": "~0.5.3" - } - }, - "node_modules/insert-module-globals": { - "version": "7.2.1", - "resolved": "https://registry.npmjs.org/insert-module-globals/-/insert-module-globals-7.2.1.tgz", - "integrity": "sha512-ufS5Qq9RZN+Bu899eA9QCAYThY+gGW7oRkmb0vC93Vlyu/CFGcH0OYPEjVkDXA5FEbTt1+VWzdoOD3Ny9N+8tg==", - "dependencies": { - "acorn-node": "^1.5.2", - "combine-source-map": "^0.8.0", - "concat-stream": "^1.6.1", - "is-buffer": "^1.1.0", - "JSONStream": "^1.0.3", - "path-is-absolute": "^1.0.1", - "process": "~0.11.0", - "through2": "^2.0.0", - "undeclared-identifiers": "^1.1.2", - "xtend": "^4.0.0" - }, - "bin": { - "insert-module-globals": "bin/cmd.js" - } - }, - "node_modules/insert-module-globals/node_modules/concat-stream": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", - "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", - "engines": [ - "node >= 0.8" - ], - "dependencies": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^2.2.2", - "typedarray": "^0.0.6" - } - }, - "node_modules/insert-module-globals/node_modules/typedarray": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==" - }, - "node_modules/internal-slot": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz", - "integrity": "sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==", - "dev": true, - "dependencies": { - "get-intrinsic": "^1.2.0", - "has": "^1.0.3", - "side-channel": "^1.0.4" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/interpret": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.1.0.tgz", - "integrity": "sha512-CLM8SNMDu7C5psFCn6Wg/tgpj/bKAg7hc2gWqcuR9OD5Ft9PhBpIu8PLicPeis+xDd6YX2ncI8MCA64I9tftIA==", - "dev": true - }, - "node_modules/ip": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.9.tgz", - "integrity": "sha512-cyRxvOEpNHNtchU3Ln9KC/auJgup87llfQpQ+t5ghoC/UhL16SWzbueiCsdTnWmqAWl7LadfuwhlqmtOaqMHdQ==", - "dev": true - }, - "node_modules/is": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/is/-/is-3.3.0.tgz", - "integrity": "sha512-nW24QBoPcFGGHJGUwnfpI7Yc5CdqWNdsyHQszVE/z2pKHXzh7FZ5GWhJqSyaQ9wMkQnsTx+kAI8bHlCX4tKdbg==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/is-absolute": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-1.0.0.tgz", - "integrity": "sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA==", - "dev": true, - "dependencies": { - "is-relative": "^1.0.0", - "is-windows": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-array-buffer": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz", - "integrity": "sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.2.0", - "is-typed-array": "^1.1.10" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", - "dev": true - }, - "node_modules/is-bigint": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", - "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", - "dev": true, - "dependencies": { - "has-bigints": "^1.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-boolean-object": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", - "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" - }, - "node_modules/is-callable": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", - "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-core-module": { - "version": "2.12.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.1.tgz", - "integrity": "sha512-Q4ZuBAe2FUsKtyQJoQHlvP8OvBERxO3jEmy1I7hcRXcJBGGHFh/aJBswbXuS9sgrDH2QUO8ilkwNPHvHMd8clg==", - "dependencies": { - "has": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-date-object": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", - "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", - "dev": true, - "dependencies": { - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-docker": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", - "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", - "dev": true, - "bin": { - "is-docker": "cli.js" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-finite": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.1.0.tgz", - "integrity": "sha512-cdyMtqX/BOqqNBBiKlIVkytNHm49MtMlYyn1zxzvJKWmFMlGzm+ry5BBfYyeY9YmNKbRSo/o7OX9w9ale0wg3w==", - "dev": true, - "engines": { - "node": ">=0.10.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-negative-zero": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", - "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-number": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-0.1.1.tgz", - "integrity": "sha512-la5kPULwIgkSSaZj9w7/A1uHqOBAgOhDUKQ5CkfL8LZ4Si6r4+2D0hI6b4o60MW4Uj2yNJARWIZUDPxlvOYQcw==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-number-like": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/is-number-like/-/is-number-like-1.0.8.tgz", - "integrity": "sha512-6rZi3ezCyFcn5L71ywzz2bS5b2Igl1En3eTlZlvKjpz1n3IZLAYMbKYAIQgFmEu0GENg92ziU/faEOA/aixjbA==", - "dev": true, - "dependencies": { - "lodash.isfinite": "^3.3.2" - } - }, - "node_modules/is-number-object": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", - "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", - "dev": true, - "dependencies": { - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-obj": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", - "integrity": "sha512-l4RyHgRqGN4Y3+9JHVrNqO+tN0rV5My76uW5/nuO4K1b6vw5G8d/cmFjP9tRfEsdhZNt0IFdZuK/c2Vr4Nb+Qg==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-regex": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", - "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-relative": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-1.0.0.tgz", - "integrity": "sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA==", - "dev": true, - "dependencies": { - "is-unc-path": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-shared-array-buffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", - "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-string": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", - "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", - "dev": true, - "dependencies": { - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-symbol": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", - "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", - "dev": true, - "dependencies": { - "has-symbols": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-typed-array": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", - "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", - "license": "MIT", - "dependencies": { - "which-typed-array": "^1.1.16" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-unc-path": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-unc-path/-/is-unc-path-1.0.0.tgz", - "integrity": "sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ==", - "dev": true, - "dependencies": { - "unc-path-regex": "^0.1.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-utf8": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", - "integrity": "sha512-rMYPYvCzsXywIsldgLaSoPlw5PfoB/ssr7hY4pLfcodrA5M/eArza1a9VmTiNIBNMjOGr1Ow9mTyU2o69U6U9Q==", - "dev": true - }, - "node_modules/is-weakref": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", - "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-what": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/is-what/-/is-what-3.14.1.tgz", - "integrity": "sha512-sNxgpk9793nzSs7bA6JQJGeIuRBQhAaNGG77kzYQgMkrID+lS6SlK07K5LaptscDlSaIgH+GPFzf+d75FVxozA==" - }, - "node_modules/is-windows": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", - "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-wsl": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", - "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", - "dev": true, - "dependencies": { - "is-docker": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true - }, - "node_modules/isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/jit-grunt": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/jit-grunt/-/jit-grunt-0.10.0.tgz", - "integrity": "sha512-eT/f4c9wgZ3buXB7X1JY1w6uNtAV0bhrbOGf/mFmBb0CDNLUETJ/VRoydayWOI54tOoam0cz9RooVCn3QY1WoA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - }, - "peerDependencies": { - "grunt": ">=0.4.0" - } - }, - "node_modules/jquery": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.0.tgz", - "integrity": "sha512-umpJ0/k8X0MvD1ds0P9SfowREz2LenHsQaxSohMZ5OMNEU2r0tf8pdeEFTHMFxWVxKNyU9rTtK3CWzUCTKJUeQ==" - }, - "node_modules/js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", - "dev": true, - "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/js-yaml-js-types": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/js-yaml-js-types/-/js-yaml-js-types-1.0.0.tgz", - "integrity": "sha512-UNjPwuoaj4mcHkJCJSF6l4MgkzoFjG+JJkBXMYNvjgO3yE9gTeRt+E6PN022vduz/daZZ7HmlEiSEE36NrGE4w==", - "dev": true, - "dependencies": { - "esprima": "^4.0.1" - } - }, - "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "node_modules/json-stable-stringify": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-0.0.1.tgz", - "integrity": "sha512-nKtD/Qxm7tWdZqJoldEC7fF0S41v0mWbeaXG3637stOWfyGxTgWTYE2wtfKmjzpvxv2MA2xzxsXOIiwUpkX6Qw==", - "dependencies": { - "jsonify": "~0.0.0" - } - }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true - }, - "node_modules/json-stringify-safe": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==" - }, - "node_modules/json5": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", - "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", - "dev": true, - "dependencies": { - "minimist": "^1.2.0" - }, - "bin": { - "json5": "lib/cli.js" - } - }, - "node_modules/jsonfile": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", - "dev": true, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/jsonify": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.1.tgz", - "integrity": "sha512-2/Ki0GcmuqSrgFyelQq9M05y7PS0mEwuIzrf3f1fPqkVDVRvZrPZtVSMHxdgo8Aq0sxAOb/cr2aqqA3LeWHVPg==", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/jsonparse": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", - "integrity": "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==", - "engines": [ - "node >= 0.2.0" - ] - }, - "node_modules/JSONStream": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", - "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==", - "dependencies": { - "jsonparse": "^1.2.0", - "through": ">=2.2.7 <3" - }, - "bin": { - "JSONStream": "bin.js" - }, - "engines": { - "node": "*" - } - }, - "node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/labeled-stream-splicer": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/labeled-stream-splicer/-/labeled-stream-splicer-2.0.2.tgz", - "integrity": "sha512-Ca4LSXFFZUjPScRaqOcFxneA0VpKZr4MMYCljyQr4LIewTLb3Y0IUTIsnBBsVubIeEfxeSZpSjSsRM8APEQaAw==", - "dependencies": { - "inherits": "^2.0.1", - "stream-splicer": "^2.0.0" - } - }, - "node_modules/lazy-async": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/lazy-async/-/lazy-async-1.0.1.tgz", - "integrity": "sha512-AoiucSfdR9pQ4LNZm1nAyu2v7ahe5O/T/iedxbIkY8jqZ51pxdtt7uyc5p74EZ9P8nGoBR33UMcNn5NUOvEyYA==", - "dependencies": { - "array-last": "~1.0.2", - "assert-equal": "~1.0.0", - "assert-ok": "~1.0.0", - "call-all-fns": "~1.0.0", - "dezalgo": "~1.0.3", - "dot-prop": "~2.4.0", - "to-array": "~0.1.4" - } - }, - "node_modules/lazy-async/node_modules/dot-prop": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-2.4.0.tgz", - "integrity": "sha512-wAg7VK6GV/tLH/Lkuqt/uMn6hUDJpWYt+0mAcaGL6nxlNZMbF+0nKu+HgfKVYGbGn/l8I4BzkCHA916OCFgdpg==", - "dependencies": { - "is-obj": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/less": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/less/-/less-4.1.3.tgz", - "integrity": "sha512-w16Xk/Ta9Hhyei0Gpz9m7VS8F28nieJaL/VyShID7cYvP6IL5oHeL6p4TXSDJqZE/lNv0oJ2pGVjJsRkfwm5FA==", - "dependencies": { - "copy-anything": "^2.0.1", - "parse-node-version": "^1.0.1", - "tslib": "^2.3.0" - }, - "bin": { - "lessc": "bin/lessc" - }, - "engines": { - "node": ">=6" - }, - "optionalDependencies": { - "errno": "^0.1.1", - "graceful-fs": "^4.1.2", - "image-size": "~0.5.0", - "make-dir": "^2.1.0", - "mime": "^1.4.1", - "needle": "^3.1.0", - "source-map": "~0.6.0" - } - }, - "node_modules/less/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "optional": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", - "dev": true, - "dependencies": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/li": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/li/-/li-1.3.0.tgz", - "integrity": "sha512-z34TU6GlMram52Tss5mt1m//ifRIpKH5Dqm7yUVOdHI+BQCs9qGPHFaCUTIzsWX7edN30aa2WrPwR7IO10FHaw==" - }, - "node_modules/liftup": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/liftup/-/liftup-3.0.1.tgz", - "integrity": "sha512-yRHaiQDizWSzoXk3APcA71eOI/UuhEkNN9DiW2Tt44mhYzX4joFoCZlxsSOF7RyeLlfqzFLQI1ngFq3ggMPhOw==", - "dev": true, - "dependencies": { - "extend": "^3.0.2", - "findup-sync": "^4.0.0", - "fined": "^1.2.0", - "flagged-respawn": "^1.0.1", - "is-plain-object": "^2.0.4", - "object.map": "^1.0.1", - "rechoir": "^0.7.0", - "resolve": "^1.19.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/liftup/node_modules/findup-sync": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-4.0.0.tgz", - "integrity": "sha512-6jvvn/12IC4quLBL1KNokxC7wWTvYncaVUYSoxWw7YykPLuRrnv4qdHcSOywOI5RpkOVGeQRtWM8/q+G6W6qfQ==", - "dev": true, - "dependencies": { - "detect-file": "^1.0.0", - "is-glob": "^4.0.0", - "micromatch": "^4.0.2", - "resolve-dir": "^1.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/livereload-js": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/livereload-js/-/livereload-js-2.4.0.tgz", - "integrity": "sha512-XPQH8Z2GDP/Hwz2PCDrh2mth4yFejwA1OZ/81Ti3LgKyhDcEjsSsqFWZojHG0va/duGd+WyosY7eXLDoOyqcPw==", - "dev": true - }, - "node_modules/load-grunt-config": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/load-grunt-config/-/load-grunt-config-4.0.1.tgz", - "integrity": "sha512-F3CD28C1WFocVvdTRSKnV73q7JvGY2OrqoeUeflbOQg58AhkFROIFc2BQXOU2xdrYJqWZx53glT5PZp1vrr9qA==", - "dev": true, - "dependencies": { - "cson": "7.20.0", - "glob": "7.1.7", - "jit-grunt": "0.10.0", - "js-yaml": "4.1.0", - "js-yaml-js-types": "1.0.0", - "load-grunt-tasks": "5.1.0", - "lodash": "4.17.21" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/load-grunt-config/node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "node_modules/load-grunt-config/node_modules/glob": { - "version": "7.1.7", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", - "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/load-grunt-config/node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/load-grunt-tasks": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/load-grunt-tasks/-/load-grunt-tasks-5.1.0.tgz", - "integrity": "sha512-oNj0Jlka1TsfDe+9He0kcA1cRln+TMoTsEByW7ij6kyktNLxBKJtslCFEvFrLC2Dj0S19IWJh3fOCIjLby2Xrg==", - "dev": true, - "dependencies": { - "arrify": "^2.0.1", - "multimatch": "^4.0.0", - "pkg-up": "^3.1.0", - "resolve-pkg": "^2.0.0" - }, - "engines": { - "node": ">=8" - }, - "peerDependencies": { - "grunt": ">=1" - } - }, - "node_modules/load-json-file": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", - "integrity": "sha512-cy7ZdNRXdablkXYNI049pthVeXFurRyb9+hA/dZzerZ0pGTx42z+y+ssxBaVV2l70t1muq5IdKhn4UtcoGUY9A==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.1.2", - "parse-json": "^2.2.0", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0", - "strip-bom": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/load-script": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/load-script/-/load-script-1.0.0.tgz", - "integrity": "sha512-kPEjMFtZvwL9TaZo0uZ2ml+Ye9HUMmPwbYRJ324qF9tqMejwykJ5ggTyvzmrbBeapCAbk98BSbTeovHEEP1uCA==" - }, - "node_modules/load-script-global": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/load-script-global/-/load-script-global-1.0.4.tgz", - "integrity": "sha512-K34esrf0+hw/l5BflwpOfcLI2uk6ybXM0yZbcAHsXniJMiy3qWXeA/rvQI+2h089Pp/COibwhW3Y87eZo+Wjzw==", - "dependencies": { - "assert-ok": "~1.0.0", - "cuid": "~2.1.4", - "dezalgo": "~1.0.3", - "ear": "~1.0.1", - "global": "~4.4.0", - "load-script": "~1.0.0", - "query-extend": "~0.2.0", - "xtend": "~4.0.1" - } - }, - "node_modules/locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dev": true, - "dependencies": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" - }, - "node_modules/lodash.isfinite": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/lodash.isfinite/-/lodash.isfinite-3.3.2.tgz", - "integrity": "sha512-7FGG40uhC8Mm633uKW1r58aElFlBlxCrg9JfSi3P6aYiWmfiWF0PgMd86ZUsxE5GwWPdHoS2+48bwTh2VPkIQA==", - "dev": true - }, - "node_modules/lodash.memoize": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-3.0.4.tgz", - "integrity": "sha512-eDn9kqrAmVUC1wmZvlQ6Uhde44n+tXpqPrN8olQJbttgh0oKclk+SF54P47VEGE9CEiMeRwAP8BaM7UHvBkz2A==" - }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true - }, - "node_modules/loud-rejection": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz", - "integrity": "sha512-RPNliZOFkqFumDhvYqOaNY4Uz9oJM2K9tC6JWsJJsNdhuONW4LQHRBpb0qf4pJApVffI5N39SwzWZJuEhfd7eQ==", - "dev": true, - "dependencies": { - "currently-unhandled": "^0.4.1", - "signal-exit": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/lower-case": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-1.1.4.tgz", - "integrity": "sha512-2Fgx1Ycm599x+WGpIYwJOvsjmXFzTSc34IwDWALRA/8AopUKAVPwfJ+h5+f85BCp0PWmmJcWzEpxOpoXycMpdA==", - "dev": true - }, - "node_modules/lru-cache": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", - "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", - "dev": true, - "dependencies": { - "pseudomap": "^1.0.2", - "yallist": "^2.1.2" - } - }, - "node_modules/make-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", - "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", - "optional": true, - "dependencies": { - "pify": "^4.0.1", - "semver": "^5.6.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/make-dir/node_modules/pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", - "optional": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/make-iterator": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/make-iterator/-/make-iterator-1.0.1.tgz", - "integrity": "sha512-pxiuXh0iVEq7VM7KMIhs5gxsfxCux2URptUQaXo4iZZJxBAzTPOLE2BumO5dbfVYq/hBJFBR/a1mFDmOx5AGmw==", - "dev": true, - "dependencies": { - "kind-of": "^6.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/map-cache": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", - "integrity": "sha512-8y/eV9QQZCiyn1SprXSrCmqJN0yNRATe+PO8ztwqrvrbdRLA3eYJF0yaR0YayLWkMbsQSKWS9N2gPcGEc4UsZg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/map-obj": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", - "integrity": "sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/math-intrinsics": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", - "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/maxmin": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/maxmin/-/maxmin-3.0.0.tgz", - "integrity": "sha512-wcahMInmGtg/7c6a75fr21Ch/Ks1Tb+Jtoan5Ft4bAI0ZvJqyOw8kkM7e7p8hDSzY805vmxwHT50KcjGwKyJ0g==", - "dev": true, - "dependencies": { - "chalk": "^4.1.0", - "figures": "^3.2.0", - "gzip-size": "^5.1.1", - "pretty-bytes": "^5.3.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/md5.js": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", - "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", - "dependencies": { - "hash-base": "^3.0.0", - "inherits": "^2.0.1", - "safe-buffer": "^5.1.2" - } - }, - "node_modules/meow": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz", - "integrity": "sha512-TNdwZs0skRlpPpCUK25StC4VH+tP5GgeY1HQOOGP+lQ2xtdkN2VtT/5tiX9k3IWpkBPV9b3LsAWXn4GGi/PrSA==", - "dev": true, - "dependencies": { - "camelcase-keys": "^2.0.0", - "decamelize": "^1.1.2", - "loud-rejection": "^1.0.0", - "map-obj": "^1.0.1", - "minimist": "^1.1.3", - "normalize-package-data": "^2.3.4", - "object-assign": "^4.0.1", - "read-pkg-up": "^1.0.1", - "redent": "^1.0.0", - "trim-newlines": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", - "dev": true, - "dependencies": { - "braces": "^3.0.2", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/miller-rabin": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", - "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", - "dependencies": { - "bn.js": "^4.0.0", - "brorand": "^1.0.1" - }, - "bin": { - "miller-rabin": "bin/miller-rabin" - } - }, - "node_modules/miller-rabin/node_modules/bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" - }, - "node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "devOptional": true, - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dev": true, - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/min-document": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.0.tgz", - "integrity": "sha512-9Wy1B3m3f66bPPmU5hdA4DR4PB2OfDU/+GS3yAB7IQozE3tqXaVv2zOjgla7MEGSRv95+ILmOuvhLkOK6wJtCQ==", - "dependencies": { - "dom-walk": "^0.1.0" - } - }, - "node_modules/minimalistic-assert": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", - "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==" - }, - "node_modules/minimalistic-crypto-utils": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", - "integrity": "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==" - }, - "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", - "dev": true, - "dependencies": { - "minimist": "^1.2.6" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, - "node_modules/module-deps": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/module-deps/-/module-deps-4.1.1.tgz", - "integrity": "sha512-ze1e77tkYtlJI90RmlJJvTOGe91OAbtNQj34tg26GWlvdDc0dzmlxujTnh85S8feiTB3eBkKAOCD/v5p9v6wHg==", - "dependencies": { - "browser-resolve": "^1.7.0", - "cached-path-relative": "^1.0.0", - "concat-stream": "~1.5.0", - "defined": "^1.0.0", - "detective": "^4.0.0", - "duplexer2": "^0.1.2", - "inherits": "^2.0.1", - "JSONStream": "^1.0.3", - "parents": "^1.0.0", - "readable-stream": "^2.0.2", - "resolve": "^1.1.3", - "stream-combiner2": "^1.1.1", - "subarg": "^1.0.0", - "through2": "^2.0.0", - "xtend": "^4.0.0" - }, - "bin": { - "module-deps": "bin/cmd.js" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/moment": { - "version": "2.29.4", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", - "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==", - "engines": { - "node": "*" - } - }, - "node_modules/morgan": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.1.tgz", - "integrity": "sha512-223dMRJtI/l25dJKWpgij2cMtywuG/WiUKXdvwfbhGKBhy1puASqXwFzmWZ7+K73vUPoR7SS2Qz2cI/g9MKw0A==", - "dev": true, - "license": "MIT", - "dependencies": { - "basic-auth": "~2.0.1", - "debug": "2.6.9", - "depd": "~2.0.0", - "on-finished": "~2.3.0", - "on-headers": "~1.1.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/mousetrap": { - "version": "1.6.5", - "resolved": "https://registry.npmjs.org/mousetrap/-/mousetrap-1.6.5.tgz", - "integrity": "sha512-QNo4kEepaIBwiT8CDhP98umTetp+JNfQYBWvC1pc6/OAibuXtRcxZ58Qz8skvEHYvURne/7R8T5VoOI7rDsEUA==" - }, - "node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true - }, - "node_modules/multimatch": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/multimatch/-/multimatch-4.0.0.tgz", - "integrity": "sha512-lDmx79y1z6i7RNx0ZGCPq1bzJ6ZoDDKbvh7jxr9SJcWLkShMzXrHbYVpTdnhNM5MXpDUxCQ4DgqVttVXlBgiBQ==", - "dev": true, - "dependencies": { - "@types/minimatch": "^3.0.3", - "array-differ": "^3.0.0", - "array-union": "^2.1.0", - "arrify": "^2.0.1", - "minimatch": "^3.0.4" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/nan": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-1.0.0.tgz", - "integrity": "sha512-Wm2/nFOm2y9HtJfgOLnctGbfvF23FcQZeyUZqDD8JQG3zO5kXh3MkQKiUaA68mJiVWrOzLFkAV1u6bC8P52DJA==", - "dev": true - }, - "node_modules/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true - }, - "node_modules/needle": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/needle/-/needle-3.2.0.tgz", - "integrity": "sha512-oUvzXnyLiVyVGoianLijF9O/RecZUf7TkBfimjGrLM4eQhXyeJwM6GeAWccwfQ9aa4gMCZKqhAOuLaMIcQxajQ==", - "optional": true, - "dependencies": { - "debug": "^3.2.6", - "iconv-lite": "^0.6.3", - "sax": "^1.2.4" - }, - "bin": { - "needle": "bin/needle" - }, - "engines": { - "node": ">= 4.4.x" - } - }, - "node_modules/needle/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "optional": true, - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/needle/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==", - "optional": true - }, - "node_modules/negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/neo-async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==" - }, - "node_modules/netmask": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/netmask/-/netmask-1.0.6.tgz", - "integrity": "sha512-3DWDqAtIiPSkBXZyYEjwebfK56nrlQfRGt642fu8RPaL+ePu750+HCMHxjJCG3iEHq/0aeMvX6KIzlv7nuhfrA==", - "dev": true, - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/ng-annotate": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/ng-annotate/-/ng-annotate-1.2.2.tgz", - "integrity": "sha512-bExxD+wmtr38NwfFoeAsLqFZvliBPy+H1jz2tZveEoFrmmP4uFmWmMaJSFcO/5Kry6Is1WaXfpXiFQtK0T8tWQ==", - "dev": true, - "dependencies": { - "acorn": "~2.6.4", - "alter": "~0.2.0", - "convert-source-map": "~1.1.2", - "optimist": "~0.6.1", - "ordered-ast-traverse": "~1.1.1", - "simple-fmt": "~0.1.0", - "simple-is": "~0.2.0", - "source-map": "~0.5.3", - "stable": "~0.1.5", - "stringmap": "~0.2.2", - "stringset": "~0.2.1", - "tryor": "~0.1.2" - }, - "bin": { - "ng-annotate": "build/es5/ng-annotate" - } - }, - "node_modules/ng-annotate/node_modules/acorn": { - "version": "2.6.4", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-2.6.4.tgz", - "integrity": "sha512-aINieSoQYX0C9uQqJGeC8mnO1T6onBTmtCdxHel6ZP/nBu4mpC03EoDtQUzAAAlUXluWjIvVV9vCuMhmOdRDXQ==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/ng-debounce": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/ng-debounce/-/ng-debounce-1.0.1.tgz", - "integrity": "sha512-G0aZEa4yQ8LiQh4YbV4HOMoKpl67WGmrJcfX9YsF/DUbpc81VfYh99vGPv9IggjV1vJ2gmbsE3GWjYWZpdkQAg==" - }, - "node_modules/ng-filters": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/ng-filters/-/ng-filters-1.3.1.tgz", - "integrity": "sha512-Hjn+9QzHZ9L9hldU3JHi6ExEMOX+TcHFltMy3BAPusA6g2dp4tUITw8OTglI+wQQYYZkQSto/vyQXh5bB2Fqbg==", - "dependencies": { - "angular": "^1.5.8", - "angular-mocks": "^1.5.8" - } - }, - "node_modules/no-case": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/no-case/-/no-case-2.3.2.tgz", - "integrity": "sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ==", - "dev": true, - "dependencies": { - "lower-case": "^1.1.1" - } - }, - "node_modules/node-http2": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/node-http2/-/node-http2-4.0.1.tgz", - "integrity": "sha512-AP21BjQsOAMTCJCCkdXUUMa1o7/Qx+yAWHnHZbCf8RhZ+hKMjB9rUkAtnfayk/yGj1qapZ5eBHZJBpk1dqdNlw==", - "dev": true, - "dependencies": { - "assert": "1.4.1", - "events": "1.1.1", - "https-browserify": "0.0.1", - "setimmediate": "^1.0.5", - "stream-browserify": "2.0.1", - "timers-browserify": "2.0.2", - "url": "^0.11.0", - "websocket-stream": "^5.0.1" - }, - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/node-http2/node_modules/assert": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/assert/-/assert-1.4.1.tgz", - "integrity": "sha512-N+aAxov+CKVS3JuhDIQFr24XvZvwE96Wlhk9dytTg/GmwWoghdOvR8dspx8MVz71O+Y0pA3UPqHF68D6iy8UvQ==", - "dev": true, - "dependencies": { - "util": "0.10.3" - } - }, - "node_modules/node-http2/node_modules/inherits": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", - "integrity": "sha512-8nWq2nLTAwd02jTqJExUYFSD/fKq6VH9Y/oG2accc/kdI0V98Bag8d5a4gi3XHz73rDWa2PvTtvcWYquKqSENA==", - "dev": true - }, - "node_modules/node-http2/node_modules/stream-browserify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.1.tgz", - "integrity": "sha512-nmQnY9D9TlnfQIkYJCCWxvCcQODilFRZIw14gCMYQVXOiY4E1Ze1VMxB+6y3qdXHpTordULo2qWloHmNcNAQYw==", - "dev": true, - "dependencies": { - "inherits": "~2.0.1", - "readable-stream": "^2.0.2" - } - }, - "node_modules/node-http2/node_modules/timers-browserify": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.2.tgz", - "integrity": "sha512-O7UB405+hxP2OWqlBdlUMxZVEdsi8NOWL2c730Cs6zeO1l1AkxygvTm6yC4nTw84iGbFcqxbIkkrdNKzq/3Fvg==", - "dev": true, - "dependencies": { - "setimmediate": "^1.0.4" - }, - "engines": { - "node": ">=0.6.0" - } - }, - "node_modules/node-http2/node_modules/util": { - "version": "0.10.3", - "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", - "integrity": "sha512-5KiHfsmkqacuKjkRkdV7SsfDJ2EGiPsK92s2MhNSY0craxjTdKTtqKsJaCWp4LW33ZZ0OPUv1WO/TFvNQRiQxQ==", - "dev": true, - "dependencies": { - "inherits": "2.0.1" - } - }, - "node_modules/node.extend": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/node.extend/-/node.extend-2.0.2.tgz", - "integrity": "sha512-pDT4Dchl94/+kkgdwyS2PauDFjZG0Hk0IcHIB+LkW27HLDtdoeMxHTxZh39DYbPP8UflWXWj9JcdDozF+YDOpQ==", - "dev": true, - "dependencies": { - "has": "^1.0.3", - "is": "^3.2.1" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/nopt": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", - "integrity": "sha512-4GUt3kSEYmk4ITxzB/b9vaIDfUVWN/Ml1Fwl11IlnIG2iaJ9O6WXZ9SrYM9NLI8OCBieN2Y8SWC2oJV0RQ7qYg==", - "dev": true, - "dependencies": { - "abbrev": "1" - }, - "bin": { - "nopt": "bin/nopt.js" - } - }, - "node_modules/normalize-package-data": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", - "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", - "dev": true, - "dependencies": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" - } - }, - "node_modules/nth-check": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", - "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", - "dev": true, - "dependencies": { - "boolbase": "^1.0.0" - }, - "funding": { - "url": "https://github.com/fb55/nth-check?sponsor=1" - } - }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-inspect": { - "version": "1.13.4", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", - "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/object.assign": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", - "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "has-symbols": "^1.0.3", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object.defaults": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/object.defaults/-/object.defaults-1.1.0.tgz", - "integrity": "sha512-c/K0mw/F11k4dEUBMW8naXUuBuhxRCfG7W+yFy8EcijU/rSmazOUd1XAEEe6bC0OuXY4HUKjTJv7xbxIMqdxrA==", - "dev": true, - "dependencies": { - "array-each": "^1.0.1", - "array-slice": "^1.0.0", - "for-own": "^1.0.0", - "isobject": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object.defaults/node_modules/array-slice": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-1.1.0.tgz", - "integrity": "sha512-B1qMD3RBP7O8o0H2KbrXDyB0IccejMF15+87Lvlor12ONPRHP6gTjXMNkt/d3ZuOGbAe66hFmaCfECI24Ufp6w==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object.entries": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.6.tgz", - "integrity": "sha512-leTPzo4Zvg3pmbQ3rDK69Rl8GQvIqMWubrkxONG9/ojtFE2rD9fjMKfSI5BxW3osRH1m6VdzmqK8oAY9aT4x5w==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/object.map": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/object.map/-/object.map-1.0.1.tgz", - "integrity": "sha512-3+mAJu2PLfnSVGHwIWubpOFLscJANBKuB/6A4CxBstc4aqwQY0FWcsppuy4jU5GSB95yES5JHSI+33AWuS4k6w==", - "dev": true, - "dependencies": { - "for-own": "^1.0.0", - "make-iterator": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object.pick": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", - "integrity": "sha512-tqa/UMy/CCoYmj+H5qc07qvSL9dqcs/WZENZ1JbtWBlATP+iVOe778gE6MSijnyCnORzDuX6hU+LA4SZ09YjFQ==", - "dev": true, - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object.values": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.6.tgz", - "integrity": "sha512-FVVTkD1vENCsAcwNs9k6jea2uHC/X0+JcjG8YA60FN5CMaJmG95wT9jek/xX9nornqGRrBkKtzuAu2wuHpKqvw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", - "dev": true, - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/on-headers": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz", - "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/open": { - "version": "8.4.2", - "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", - "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==", - "dev": true, - "dependencies": { - "define-lazy-prop": "^2.0.0", - "is-docker": "^2.1.1", - "is-wsl": "^2.2.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/optimist": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", - "integrity": "sha512-snN4O4TkigujZphWLN0E//nQmm7790RYaE53DdL7ZYwee2D8DDo9/EyYiKUfN3rneWUjhJnueija3G9I2i0h3g==", - "dev": true, - "dependencies": { - "minimist": "~0.0.1", - "wordwrap": "~0.0.2" - } - }, - "node_modules/optimist/node_modules/minimist": { - "version": "0.0.10", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz", - "integrity": "sha512-iotkTvxc+TwOm5Ieim8VnSNvCDjCK9S8G3scJ50ZthspSxa7jx50jkhYduuAtAjvfDUwSgOwf8+If99AlOEhyw==", - "dev": true - }, - "node_modules/optional-require": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/optional-require/-/optional-require-1.1.8.tgz", - "integrity": "sha512-jq83qaUb0wNg9Krv1c5OQ+58EK+vHde6aBPzLvPPqJm89UQWsvSuFy9X/OSNJnFeSOKo7btE0n8Nl2+nE+z5nA==", - "dev": true, - "dependencies": { - "require-at": "^1.0.6" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/optionator": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", - "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", - "dev": true, - "dependencies": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.6", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "word-wrap": "~1.2.3" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/options": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/options/-/options-0.0.6.tgz", - "integrity": "sha512-bOj3L1ypm++N+n7CEbbe473A414AB7z+amKYshRb//iuL3MpdDCLhPnw6aVTdKB9g5ZRVHIEp8eUln6L2NUStg==", - "dev": true, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/ordered-ast-traverse": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ordered-ast-traverse/-/ordered-ast-traverse-1.1.1.tgz", - "integrity": "sha512-/VDCNFrMUftwD04zNwMMXUY5+5IN0tQKbElSC1v6f53ueZMuRKzZmR5HxTC5TQnwLVY1FH3KbulWc0DXZ5kBnQ==", - "dev": true, - "dependencies": { - "ordered-esprima-props": "~1.1.0" - } - }, - "node_modules/ordered-esprima-props": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/ordered-esprima-props/-/ordered-esprima-props-1.1.0.tgz", - "integrity": "sha512-RVQ/gx+EZY/txtizlzn4OY4Rto8vhM2HasczvmP1SJYBW6GD9vLfqx/7vgKEAFK2C9ynzz/evBArFyp3HAVmTg==", - "dev": true - }, - "node_modules/os-browserify": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.1.2.tgz", - "integrity": "sha512-aZicJZccvxWOZ0Bja2eAch2L8RIJWBuRYmM8Gwl/JjNtRltH0Itcz4eH/ESyuIWfse8cc93ZCf0XrzhXK2HEDA==" - }, - "node_modules/os-homedir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", - "integrity": "sha512-B5JU3cabzk8c67mRRd3ECmROafjYMXbuzlwtqdM8IbS8ktlTix8aFGb2bAGKrSRIlnfKwovGUUr72JUPyOb6kQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/osenv": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", - "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", - "dev": true, - "dependencies": { - "os-homedir": "^1.0.0", - "os-tmpdir": "^1.0.0" - } - }, - "node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "dev": true, - "dependencies": { - "p-limit": "^2.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/pac-proxy-agent": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-2.0.2.tgz", - "integrity": "sha512-cDNAN1Ehjbf5EHkNY5qnRhGPUCp6SnpyVof5fRzN800QV1Y2OkzbH9rmjZkbBRa8igof903yOnjIl6z0SlAhxA==", - "dev": true, - "dependencies": { - "agent-base": "^4.2.0", - "debug": "^3.1.0", - "get-uri": "^2.0.0", - "http-proxy-agent": "^2.1.0", - "https-proxy-agent": "^2.2.1", - "pac-resolver": "^3.0.0", - "raw-body": "^2.2.0", - "socks-proxy-agent": "^3.0.0" - } - }, - "node_modules/pac-proxy-agent/node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/pac-proxy-agent/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/pac-proxy-agent/node_modules/http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", - "dev": true, - "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/pac-proxy-agent/node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dev": true, - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/pac-proxy-agent/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 - }, - "node_modules/pac-proxy-agent/node_modules/raw-body": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", - "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", - "dev": true, - "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/pac-proxy-agent/node_modules/setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", - "dev": true - }, - "node_modules/pac-proxy-agent/node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/pac-resolver": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-3.0.0.tgz", - "integrity": "sha512-tcc38bsjuE3XZ5+4vP96OfhOugrX+JcnpUbhfuc4LuXBLQhoTthOstZeoQJBDnQUDYzYmdImKsbz0xSl1/9qeA==", - "dev": true, - "dependencies": { - "co": "^4.6.0", - "degenerator": "^1.0.4", - "ip": "^1.1.5", - "netmask": "^1.0.6", - "thunkify": "^2.1.2" - } - }, - "node_modules/pako": { - "version": "0.2.9", - "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz", - "integrity": "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==" - }, - "node_modules/param-case": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/param-case/-/param-case-2.1.1.tgz", - "integrity": "sha512-eQE845L6ot89sk2N8liD8HAuH4ca6Vvr7VWAWwt7+kvvG5aBcPmmphQ68JsEG2qa9n1TykS2DLeMt363AAH8/w==", - "dev": true, - "dependencies": { - "no-case": "^2.2.0" - } - }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/parents": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parents/-/parents-1.0.1.tgz", - "integrity": "sha512-mXKF3xkoUt5td2DoxpLmtOmZvko9VfFpwRwkKDHSNvgmpLAeBo18YDhcPbBzJq+QLCHMbGOfzia2cX4U+0v9Mg==", - "dependencies": { - "path-platform": "~0.11.15" - } - }, - "node_modules/parse-asn1": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.6.tgz", - "integrity": "sha512-RnZRo1EPU6JBnra2vGHj0yhp6ebyjBZpmUCLHWiFhxlzvBCCpAuZ7elsBp1PVAbQN0/04VD/19rfzlBSwLstMw==", - "dependencies": { - "asn1.js": "^5.2.0", - "browserify-aes": "^1.0.0", - "evp_bytestokey": "^1.0.0", - "pbkdf2": "^3.0.3", - "safe-buffer": "^5.1.1" - } - }, - "node_modules/parse-filepath": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/parse-filepath/-/parse-filepath-1.0.2.tgz", - "integrity": "sha512-FwdRXKCohSVeXqwtYonZTXtbGJKrn+HNyWDYVcp5yuJlesTwNH4rsmRZ+GrKAPJ5bLpRxESMeS+Rl0VCHRvB2Q==", - "dev": true, - "dependencies": { - "is-absolute": "^1.0.0", - "map-cache": "^0.2.0", - "path-root": "^0.1.1" - }, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/parse-json": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", - "integrity": "sha512-QR/GGaKCkhwk1ePQNYDRKYZ3mwU9ypsKhB0XyFnLQdomyEqk3e8wpW3V5Jp88zbxK4n5ST1nqo+g9juTpownhQ==", - "dev": true, - "dependencies": { - "error-ex": "^1.2.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/parse-node-version": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parse-node-version/-/parse-node-version-1.0.1.tgz", - "integrity": "sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/parse-passwd": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", - "integrity": "sha512-1Y1A//QUXEZK7YKz+rD9WydcE1+EuPr6ZBgKecAB8tmoW6UFv0NREVJe1p+jRxtThkcbbKkfwIbWJe/IeE6m2Q==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/parse5": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", - "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", - "dev": true, - "dependencies": { - "entities": "^4.4.0" - }, - "funding": { - "url": "https://github.com/inikulin/parse5?sponsor=1" - } - }, - "node_modules/parse5-htmlparser2-tree-adapter": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.0.0.tgz", - "integrity": "sha512-B77tOZrqqfUfnVcOrUvfdLbz4pu4RopLD/4vmu3HUPswwTA8OH0EMW9BlWR2B0RCoiZRAHEUu7IxeP1Pd1UU+g==", - "dev": true, - "dependencies": { - "domhandler": "^5.0.2", - "parse5": "^7.0.0" - }, - "funding": { - "url": "https://github.com/inikulin/parse5?sponsor=1" - } - }, - "node_modules/parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/path-browserify": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.1.tgz", - "integrity": "sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ==" - }, - "node_modules/path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" - }, - "node_modules/path-platform": { - "version": "0.11.15", - "resolved": "https://registry.npmjs.org/path-platform/-/path-platform-0.11.15.tgz", - "integrity": "sha512-Y30dB6rab1A/nfEKsZxmr01nUotHX0c/ZiIAsCTatEe1CmS5Pm5He7fZ195bPT7RdquoaL8lLxFCMQi/bS7IJg==", - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/path-root": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/path-root/-/path-root-0.1.1.tgz", - "integrity": "sha512-QLcPegTHF11axjfojBIoDygmS2E3Lf+8+jI6wOVmNVenrKSo3mFdSGiIgdSHenczw3wPtlVMQaFVwGmM7BJdtg==", - "dev": true, - "dependencies": { - "path-root-regex": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-root-regex": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/path-root-regex/-/path-root-regex-0.1.2.tgz", - "integrity": "sha512-4GlJ6rZDhQZFE0DPVKh0e9jmZ5egZfxTkp7bcRDuPlJXbAwhxcl2dINPUAsjLdejqaLsCeg8axcLjIbvBjN4pQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-type": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", - "integrity": "sha512-S4eENJz1pkiQn9Znv33Q+deTOKmbl+jj1Fl+qiP/vYezj+S8x+J3Uo0ISrx/QoEvIlOaDWJhPaRd1flJ9HXZqg==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.1.2", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/pbkdf2": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.3.tgz", - "integrity": "sha512-wfRLBZ0feWRhCIkoMB6ete7czJcnNnqRpcoWQBLqatqXXmelSRqfdDK4F3u9T2s2cXas/hQJcryI/4lAL+XTlA==", - "license": "MIT", - "dependencies": { - "create-hash": "~1.1.3", - "create-hmac": "^1.1.7", - "ripemd160": "=2.0.1", - "safe-buffer": "^5.2.1", - "sha.js": "^2.4.11", - "to-buffer": "^1.2.0" - }, - "engines": { - "node": ">=0.12" - } - }, - "node_modules/pbkdf2/node_modules/create-hash": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.1.3.tgz", - "integrity": "sha512-snRpch/kwQhcdlnZKYanNF1m0RDlrCdSKQaH87w1FCFPVPNCQ/Il9QJKAX2jVBZddRdaHBMC+zXa9Gw9tmkNUA==", - "license": "MIT", - "dependencies": { - "cipher-base": "^1.0.1", - "inherits": "^2.0.1", - "ripemd160": "^2.0.0", - "sha.js": "^2.4.0" - } - }, - "node_modules/pbkdf2/node_modules/hash-base": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-2.0.2.tgz", - "integrity": "sha512-0TROgQ1/SxE6KmxWSvXHvRj90/Xo1JvZShofnYF+f6ZsGtR4eES7WfrQzPalmyagfKZCXpVnitiRebZulWsbiw==", - "license": "MIT", - "dependencies": { - "inherits": "^2.0.1" - } - }, - "node_modules/pbkdf2/node_modules/ripemd160": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.1.tgz", - "integrity": "sha512-J7f4wutN8mdbV08MJnXibYpCOPHR+yzy+iQ/AsjMv2j8cLavQ8VGagDFUwwTAdF8FmRKVeNpbTTEwNHCW1g94w==", - "license": "MIT", - "dependencies": { - "hash-base": "^2.0.0", - "inherits": "^2.0.1" - } - }, - "node_modules/pff": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/pff/-/pff-1.0.0.tgz", - "integrity": "sha512-xTEjIRrfxd3e2/xApimUnNSAYmdJ2eUhrjEQqSOYA39oJuytvlWI5p2t3EVBDjl1nquyy327qHMy9fqqXfxZmw==" - }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/pinkie": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", - "integrity": "sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/pinkie-promise": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", - "integrity": "sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw==", - "dev": true, - "dependencies": { - "pinkie": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/pkg-up": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-3.1.0.tgz", - "integrity": "sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==", - "dev": true, - "dependencies": { - "find-up": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/portscanner": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/portscanner/-/portscanner-2.2.0.tgz", - "integrity": "sha512-IFroCz/59Lqa2uBvzK3bKDbDDIEaAY8XJ1jFxcLWTqosrsc32//P4VuSB2vZXoHiHqOmx8B5L5hnKOxL/7FlPw==", - "dev": true, - "dependencies": { - "async": "^2.6.0", - "is-number-like": "^1.0.3" - }, - "engines": { - "node": ">=0.4", - "npm": ">=1.0.0" - } - }, - "node_modules/portscanner/node_modules/async": { - "version": "2.6.4", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", - "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", - "dev": true, - "dependencies": { - "lodash": "^4.17.14" - } - }, - "node_modules/possible-typed-array-names": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", - "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==", - "dev": true, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/prettier": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.0.tgz", - "integrity": "sha512-zBf5eHpwHOGPC47h0zrPyNn+eAEIdEzfywMoYn2XPi0P44Zp0tSq64rq0xAREh4auw2cJZHo9QUob+NqCQky4g==", - "dev": true, - "bin": { - "prettier": "bin/prettier.cjs" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" - } - }, - "node_modules/pretty-bytes": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz", - "integrity": "sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==", - "dev": true, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/print-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/print-value/-/print-value-1.0.0.tgz", - "integrity": "sha512-0otXUlok1rwo/io8Piqeq8zohz3JGxM3DJYbMoBLX6vKy5lZo6fpI2AV+MsO3ToAKc1VfmrPMVhnHAQJ0v/7rg==", - "dependencies": { - "isobject": "~2.0.0", - "json-stringify-safe": "~5.0.1" - } - }, - "node_modules/print-value/node_modules/isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==" - }, - "node_modules/print-value/node_modules/isobject": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.0.0.tgz", - "integrity": "sha512-r4CAG0OFgyZDPfDEhVOHKjegEkzIO2kJP+hhI/07kkcg6Ap9AGVh19CfXGUzpMCP4hQ0liYJQogg505amBHG7w==", - "dependencies": { - "isarray": "0.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/process": { - "version": "0.11.10", - "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", - "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", - "engines": { - "node": ">= 0.6.0" - } - }, - "node_modules/process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" - }, - "node_modules/proxy-agent": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-2.3.1.tgz", - "integrity": "sha512-CNKuhC1jVtm8KJYFTS2ZRO71VCBx3QSA92So/e6NrY6GoJonkx3Irnk4047EsCcswczwqAekRj3s8qLRGahSKg==", - "dev": true, - "dependencies": { - "agent-base": "^4.2.0", - "debug": "^3.1.0", - "http-proxy-agent": "^2.1.0", - "https-proxy-agent": "^2.2.1", - "lru-cache": "^4.1.2", - "pac-proxy-agent": "^2.0.1", - "proxy-from-env": "^1.0.0", - "socks-proxy-agent": "^3.0.0" - } - }, - "node_modules/proxy-agent/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/proxy-agent/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 - }, - "node_modules/proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", - "dev": true - }, - "node_modules/prr": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", - "integrity": "sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==", - "optional": true - }, - "node_modules/pseudomap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", - "integrity": "sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==", - "dev": true - }, - "node_modules/public-encrypt": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz", - "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==", - "dependencies": { - "bn.js": "^4.1.0", - "browserify-rsa": "^4.0.0", - "create-hash": "^1.1.0", - "parse-asn1": "^5.0.0", - "randombytes": "^2.0.1", - "safe-buffer": "^5.1.2" - } - }, - "node_modules/public-encrypt/node_modules/bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" - }, - "node_modules/punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==" - }, - "node_modules/qs": { - "version": "6.14.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz", - "integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "side-channel": "^1.1.0" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/query-extend": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/query-extend/-/query-extend-0.2.0.tgz", - "integrity": "sha512-Bv9BQWXgJQaKLL6rgdqd5ideti4uAy9heRNA79sr2f+V95Bc0MKNA2gYQj/W2FTyt5WXxzB8a0MmnAYjsIb/sQ==" - }, - "node_modules/query-string": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/query-string/-/query-string-4.2.3.tgz", - "integrity": "sha512-/vnoSkeRiKOQLwMwhKekPeEvTVoJ8ZN/ibV6KAkSquZIWK3VnfwkqLx5Ahcq91mdE+AxjHespuEAaqyrZ+68jA==", - "dependencies": { - "object-assign": "^4.1.0", - "strict-uri-encode": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/querystring": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", - "integrity": "sha512-X/xY82scca2tau62i9mDyU9K+I+djTMUsvwf7xnUX5GLvVzgJybOJf4Y6o9Zx3oJK/LSXg5tTZBjwzqVPaPO2g==", - "deprecated": "The querystring API is considered Legacy. new code should use the URLSearchParams API instead.", - "engines": { - "node": ">=0.4.x" - } - }, - "node_modules/querystring-es3": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz", - "integrity": "sha512-773xhDQnZBMFobEiztv8LIl70ch5MSF/jUQVlhwFyBILqq96anmoctVIYz+ZRp0qbCKATTn6ev02M3r7Ga5vqA==", - "engines": { - "node": ">=0.4.x" - } - }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dependencies": { - "safe-buffer": "^5.1.0" - } - }, - "node_modules/randomfill": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz", - "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", - "dependencies": { - "randombytes": "^2.0.5", - "safe-buffer": "^5.1.0" - } - }, - "node_modules/range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/raw-body": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-1.1.7.tgz", - "integrity": "sha512-WmJJU2e9Y6M5UzTOkHaM7xJGAPQD8PNzx3bAd2+uhZAim6wDk6dAZxPVYLF67XhbR4hmKGh33Lpmh4XWrCH5Mg==", - "dev": true, - "dependencies": { - "bytes": "1", - "string_decoder": "0.10" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/read-only-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/read-only-stream/-/read-only-stream-2.0.0.tgz", - "integrity": "sha512-3ALe0bjBVZtkdWKIcThYpQCLbBMd/+Tbh2CDSrAIDO3UsZ4Xs+tnyjv2MjCOMMgBG+AsUOeuP1cgtY1INISc8w==", - "dependencies": { - "readable-stream": "^2.0.2" - } - }, - "node_modules/read-pkg": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", - "integrity": "sha512-7BGwRHqt4s/uVbuyoeejRn4YmFnYZiFl4AuaeXHlgZf3sONF0SOGlxs2Pw8g6hCKupo08RafIO5YXFNOKTfwsQ==", - "dev": true, - "dependencies": { - "load-json-file": "^1.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/read-pkg-up": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", - "integrity": "sha512-WD9MTlNtI55IwYUS27iHh9tK3YoIVhxis8yKhLpTqWtml739uXc9NWTpxoHkfZf3+DkCCsXox94/VWZniuZm6A==", - "dev": true, - "dependencies": { - "find-up": "^1.0.0", - "read-pkg": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/read-pkg-up/node_modules/find-up": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", - "integrity": "sha512-jvElSjyuo4EMQGoTwo1uJU5pQMwTW5lS1x05zzfJuTIyLR3zwO27LYrxNg+dlvKpGOuGy/MzBdXh80g0ve5+HA==", - "dev": true, - "dependencies": { - "path-exists": "^2.0.0", - "pinkie-promise": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/read-pkg-up/node_modules/path-exists": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", - "integrity": "sha512-yTltuKuhtNeFJKa1PiRzfLAU5182q1y4Eb4XCJ3PBqyzEDkAZRzBrKKBct682ls9reBVHf9udYLN5Nd+K1B9BQ==", - "dev": true, - "dependencies": { - "pinkie-promise": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/readable-stream": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/readable-stream/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "node_modules/readable-stream/node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, - "node_modules/rechoir": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.7.1.tgz", - "integrity": "sha512-/njmZ8s1wVeR6pjTZ+0nCnv8SpZNRMT2D1RLOJQESlYFDBvwpTA4KWJpZ+sBJ4+vhjILRcK7JIFdGCdxEAAitg==", - "dev": true, - "dependencies": { - "resolve": "^1.9.0" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/redent": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz", - "integrity": "sha512-qtW5hKzGQZqKoh6JNSD+4lfitfPKGz42e6QwiRmPM5mmKtR0N41AbJRYu0xJi7nhOJ4WDgRkKvAk6tw4WIwR4g==", - "dev": true, - "dependencies": { - "indent-string": "^2.1.0", - "strip-indent": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/regexp.prototype.flags": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.0.tgz", - "integrity": "sha512-0SutC3pNudRKgquxGoRGIz946MZVHqbNfPjBdxeOhBrdgDKlRoXmYLQN9xRbrR09ZXWeGAdPuif7egofn6v5LA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "functions-have-names": "^1.2.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/relateurl": { - "version": "0.2.7", - "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", - "integrity": "sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==", - "dev": true, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/repeating": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", - "integrity": "sha512-ZqtSMuVybkISo2OWvqvm7iHSWngvdaW3IpsT9/uP8v4gMi591LY6h35wdOfvQdWCKFWZWm2Y1Opp4kV7vQKT6A==", - "dev": true, - "dependencies": { - "is-finite": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/require-at": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/require-at/-/require-at-1.0.6.tgz", - "integrity": "sha512-7i1auJbMUrXEAZCOQ0VNJgmcT2VOKPRl2YGJwgpHpC9CE91Mv4/4UYIUm4chGJaI381ZDq1JUicFii64Hapd8g==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/requirefresh": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/requirefresh/-/requirefresh-4.13.0.tgz", - "integrity": "sha512-APrzWWxqnwE2Bz3ysFepAU0Dc0aYjn8jsDLFIegedn+KVCS9LykAclkf+6YaJXk3ON9l6QAm04rN+bEA89cGIg==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://bevry.me/fund" - } - }, - "node_modules/requires-port": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", - "dev": true - }, - "node_modules/resolve": { - "version": "1.22.2", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz", - "integrity": "sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==", - "dependencies": { - "is-core-module": "^2.11.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/resolve-dir": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-1.0.1.tgz", - "integrity": "sha512-R7uiTjECzvOsWSfdM0QKFNBVFcK27aHOUwdvK53BcW8zqnGdYp0Fbj82cy54+2A4P2tFM22J5kRfe1R+lM/1yg==", - "dev": true, - "dependencies": { - "expand-tilde": "^2.0.0", - "global-modules": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/resolve-pkg": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/resolve-pkg/-/resolve-pkg-2.0.0.tgz", - "integrity": "sha512-+1lzwXehGCXSeryaISr6WujZzowloigEofRB+dj75y9RRa/obVcYgbHJd53tdYw8pvZj8GojXaaENws8Ktw/hQ==", - "dev": true, - "dependencies": { - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/restangular": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/restangular/-/restangular-1.6.1.tgz", - "integrity": "sha512-87o0xIweYAzsKCmRBOP1re0eYF1ioblKG7j3DcTeTvqMuLuO87qj/Zld9XVMTadjovC+CU7YFyywaI7UbMhtEg==", - "dependencies": { - "lodash": "~4.17.0" - }, - "engines": { - "node": ">= 0.9" - }, - "peerDependencies": { - "angular": ">=1.3.12" - } - }, - "node_modules/reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true, - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, - "node_modules/rickshaw": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/rickshaw/-/rickshaw-1.7.1.tgz", - "integrity": "sha512-H1eNOST/N8yIJPqdrZjW4R2r4hDN8zbtbBnvm2TzIYZhnfqmQr/fG/f8LMcvDMx1UrIzuBOPH2FTT9t0w79JWA==", - "dependencies": { - "d3": "^3.5.16" - }, - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - } - }, - "node_modules/rimraf/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/ripemd160": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", - "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", - "dependencies": { - "hash-base": "^3.0.0", - "inherits": "^2.0.1" - } - }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "queue-microtask": "^1.2.2" - } - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/safe-json-parse": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/safe-json-parse/-/safe-json-parse-1.0.1.tgz", - "integrity": "sha512-o0JmTu17WGUaUOHa1l0FPGXKBfijbxK6qoHzlkihsDXxzBHvJcA7zgviKR92Xs841rX9pK16unfphLq0/KqX7A==", - "dev": true - }, - "node_modules/safe-regex-test": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", - "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.3", - "is-regex": "^1.1.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/safefs": { - "version": "6.16.0", - "resolved": "https://registry.npmjs.org/safefs/-/safefs-6.16.0.tgz", - "integrity": "sha512-IUIN5h+r2acNVkbsM5VoBUsRnaSFwQFZnzyvQsiLlgFsncLi7rrtmM4FsEgwv+xDTJzsrp79rd+/GJu2zWcR9w==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.2.6" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://bevry.me/fund" - } - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" - }, - "node_modules/satellizer": { - "version": "0.15.5", - "resolved": "https://registry.npmjs.org/satellizer/-/satellizer-0.15.5.tgz", - "integrity": "sha512-TNPEv4QLo+6TWpCct7zFpePx9ADZZ/Yp8Qj159uvoJzem3ERNsyKkn1NCUfWEk44tsUIJwmI4af1GFFHmuaPTA==" - }, - "node_modules/sax": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", - "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", - "optional": true - }, - "node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "devOptional": true, - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/send": { - "version": "0.19.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", - "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", - "dev": true, - "dependencies": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "2.4.1", - "range-parser": "~1.2.1", - "statuses": "2.0.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/send/node_modules/http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", - "dev": true, - "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/send/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 - }, - "node_modules/send/node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "dev": true, - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/send/node_modules/setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", - "dev": true - }, - "node_modules/send/node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/serve-index": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", - "integrity": "sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw==", - "dev": true, - "dependencies": { - "accepts": "~1.3.4", - "batch": "0.6.1", - "debug": "2.6.9", - "escape-html": "~1.0.3", - "http-errors": "~1.6.2", - "mime-types": "~2.1.17", - "parseurl": "~1.3.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/serve-static": { - "version": "1.16.2", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", - "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", - "dev": true, - "dependencies": { - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.19.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/serve-static/node_modules/encodeurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", - "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/set-function-length": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", - "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", - "license": "MIT", - "dependencies": { - "define-data-property": "^1.1.4", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/setimmediate": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", - "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", - "dev": true - }, - "node_modules/setprototypeof": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", - "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", - "dev": true - }, - "node_modules/sha.js": { - "version": "2.4.12", - "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.12.tgz", - "integrity": "sha512-8LzC5+bvI45BjpfXU8V5fdU2mfeKiQe1D1gIMn7XUlF3OTUrpdJpPPH4EMAnF0DsHHdSZqCdSss5qCmJKuiO3w==", - "license": "(MIT AND BSD-3-Clause)", - "dependencies": { - "inherits": "^2.0.4", - "safe-buffer": "^5.2.1", - "to-buffer": "^1.2.0" - }, - "bin": { - "sha.js": "bin.js" - }, - "engines": { - "node": ">= 0.10" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/shasum": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/shasum/-/shasum-1.0.2.tgz", - "integrity": "sha512-UTzHm/+AzKfO9RgPgRpDIuMSNie1ubXRaljjlhFMNGYoG7z+rm9AHLPMf70R7887xboDH9Q+5YQbWKObFHEAtw==", - "dependencies": { - "json-stable-stringify": "~0.0.0", - "sha.js": "~2.4.4" - } - }, - "node_modules/shasum-object": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shasum-object/-/shasum-object-1.0.0.tgz", - "integrity": "sha512-Iqo5rp/3xVi6M4YheapzZhhGPVs0yZwHj7wvwQ1B9z8H6zk+FEnI7y3Teq7qwnekfEhu8WmG2z0z4iWZaxLWVg==", - "dependencies": { - "fast-safe-stringify": "^2.0.7" - } - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/shell-quote": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.1.tgz", - "integrity": "sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", - "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3", - "side-channel-list": "^1.0.0", - "side-channel-map": "^1.0.1", - "side-channel-weakmap": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-list": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", - "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-map": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", - "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-weakmap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", - "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3", - "side-channel-map": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true - }, - "node_modules/simple-concat": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", - "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/simple-fmt": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/simple-fmt/-/simple-fmt-0.1.0.tgz", - "integrity": "sha512-9a3zTDDh9LXbTR37qBhACWIQ/mP/ry5xtmbE98BJM8GR02sanCkfMzp7AdCTqYhkBZggK/w7hJtc8Pb9nmo16A==", - "dev": true - }, - "node_modules/simple-format": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/simple-format/-/simple-format-1.0.0.tgz", - "integrity": "sha512-G3QpUxBzk2Vrn9yzT9foa9sy/HGoprpyI3YtuEJtC0WlhVJSPnkJjHEwiHdyH2liok5qkzqh1hgrgFI0ZegEiQ==", - "dependencies": { - "pff": "~1.0.0", - "to-array": "~0.1.4" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/simple-is": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/simple-is/-/simple-is-0.2.0.tgz", - "integrity": "sha512-GJXhv3r5vdj5tGWO+rcrWgjU2azLB+fb7Ehh3SmZpXE0o4KrrFLti0w4mdDCbR29X/z0Ls20ApjZitlpAXhAeg==", - "dev": true - }, - "node_modules/smart-buffer": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-1.1.15.tgz", - "integrity": "sha512-1+8bxygjTsNfvQe0/0pNBesTOlSHtOeG6b6LYbvsZCCHDKYZ40zcQo6YTnZBWrBSLWOCbrHljLdEmGMYebu7aQ==", - "dev": true, - "engines": { - "node": ">= 0.10.15", - "npm": ">= 1.3.5" - } - }, - "node_modules/socks": { - "version": "1.1.10", - "resolved": "https://registry.npmjs.org/socks/-/socks-1.1.10.tgz", - "integrity": "sha512-ArX4vGPULWjKDKgUnW8YzfI2uXW7kzgkJuB0GnFBA/PfT3exrrOk+7Wk2oeb894Qf20u1PWv9LEgrO0Z82qAzA==", - "deprecated": "If using 2.x branch, please upgrade to at least 2.1.6 to avoid a serious bug with socket data flow and an import issue introduced in 2.1.0", - "dev": true, - "dependencies": { - "ip": "^1.1.4", - "smart-buffer": "^1.0.13" - }, - "engines": { - "node": ">= 0.10.0", - "npm": ">= 1.3.5" - } - }, - "node_modules/socks-proxy-agent": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-3.0.1.tgz", - "integrity": "sha512-ZwEDymm204mTzvdqyUqOdovVr2YRd2NYskrYrF2LXyZ9qDiMAoFESGK8CRphiO7rtbo2Y757k2Nia3x2hGtalA==", - "dev": true, - "dependencies": { - "agent-base": "^4.1.0", - "socks": "^1.1.10" - } - }, - "node_modules/source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/spdx-correct": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", - "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", - "dev": true, - "dependencies": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/spdx-exceptions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", - "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", - "dev": true - }, - "node_modules/spdx-expression-parse": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", - "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", - "dev": true, - "dependencies": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/spdx-license-ids": { - "version": "3.0.13", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.13.tgz", - "integrity": "sha512-XkD+zwiqXHikFZm4AX/7JSCXA98U5Db4AFd5XUg/+9UNtnH75+Z9KxtpYiJZx36mUDVOwH83pl7yvCer6ewM3w==", - "dev": true - }, - "node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "dev": true - }, - "node_modules/stable": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz", - "integrity": "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==", - "deprecated": "Modern JS already guarantees Array#sort() is a stable sort, so this library is deprecated. See the compatibility table on MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#browser_compatibility", - "dev": true - }, - "node_modules/stack-trace": { - "version": "0.0.10", - "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", - "integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==", - "engines": { - "node": "*" - } - }, - "node_modules/statuses": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/stream-browserify": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.2.tgz", - "integrity": "sha512-nX6hmklHs/gr2FuxYDltq8fJA1GDlxKQCz8O/IM4atRqBH8OORmBNgfvW5gG10GT/qQ9u0CzIvr2X5Pkt6ntqg==", - "dependencies": { - "inherits": "~2.0.1", - "readable-stream": "^2.0.2" - } - }, - "node_modules/stream-combiner2": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/stream-combiner2/-/stream-combiner2-1.1.1.tgz", - "integrity": "sha512-3PnJbYgS56AeWgtKF5jtJRT6uFJe56Z0Hc5Ngg/6sI6rIt8iiMBTa9cvdyFfpMQjaVHr8dusbNeFGIIonxOvKw==", - "dependencies": { - "duplexer2": "~0.1.0", - "readable-stream": "^2.0.2" - } - }, - "node_modules/stream-http": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-2.8.3.tgz", - "integrity": "sha512-+TSkfINHDo4J+ZobQLWiMouQYB+UVYFttRA94FpEzzJ7ZdqcL4uUUQ7WkdkI4DSozGmgBUE/a47L+38PenXhUw==", - "dependencies": { - "builtin-status-codes": "^3.0.0", - "inherits": "^2.0.1", - "readable-stream": "^2.3.6", - "to-arraybuffer": "^1.0.0", - "xtend": "^4.0.0" - } - }, - "node_modules/stream-shift": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz", - "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==", - "dev": true - }, - "node_modules/stream-splicer": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/stream-splicer/-/stream-splicer-2.0.1.tgz", - "integrity": "sha512-Xizh4/NPuYSyAXyT7g8IvdJ9HJpxIGL9PjyhtywCZvvP0OPIdqyrr4dMikeuvY8xahpdKEBlBTySe583totajg==", - "dependencies": { - "inherits": "^2.0.1", - "readable-stream": "^2.0.2" - } - }, - "node_modules/strict-uri-encode": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz", - "integrity": "sha512-R3f198pcvnB+5IpnBlRkphuE9n46WyVl8I39W/ZUTZLz4nqSP/oLYUrcnJrw462Ds8he4YKMov2efsTIw1BDGQ==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==" - }, - "node_modules/string-template": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/string-template/-/string-template-0.2.1.tgz", - "integrity": "sha512-Yptehjogou2xm4UJbxJ4CxgZx12HBfeystp0y3x7s4Dj32ltVVG1Gg8YhKjHZkHicuKpZX/ffilA8505VbUbpw==", - "dev": true - }, - "node_modules/string.prototype.endswith": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/string.prototype.endswith/-/string.prototype.endswith-0.2.0.tgz", - "integrity": "sha512-CLb+YN1AleXLWyh88P1R5s2ajz83wF6bWOyxfOXJ7P3ioK/lOnA39VIEcen24/ELWJvU6y8NG6vEmWSOJIVFfQ==", - "dev": true - }, - "node_modules/string.prototype.trim": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.7.tgz", - "integrity": "sha512-p6TmeT1T3411M8Cgg9wBTMRtY2q9+PNy9EV1i2lIXUN/btt763oIfxwN3RR8VU6wHX8j/1CFy0L+YuThm6bgOg==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/string.prototype.trimend": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz", - "integrity": "sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/string.prototype.trimstart": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz", - "integrity": "sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/stringmap": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/stringmap/-/stringmap-0.2.2.tgz", - "integrity": "sha512-mR1LEHDw6TsHa+LwJeeBc9ZqZqEOm7bHidgxMmDg8HB/rbA1HhDeT08gS67CCCG/xrgIfQx5tW42pd8vFpLUow==", - "dev": true - }, - "node_modules/stringset": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/stringset/-/stringset-0.2.1.tgz", - "integrity": "sha512-km3jeiRpmySChl1oLiBE2ESdG5k/4+6tjENVL6BB3mdmKBiUikI5ks4paad2WAKsxzpNiBqBBbXCC12QqlpLWA==", - "dev": true - }, - "node_modules/strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", - "dev": true, - "dependencies": { - "ansi-regex": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/strip-bom": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", - "integrity": "sha512-kwrX1y7czp1E69n2ajbG65mIo9dqvJ+8aBQXOGVxqwvNbsXdFM6Lq37dLAY3mknUwru8CfcCbfOLL/gMo+fi3g==", - "dev": true, - "dependencies": { - "is-utf8": "^0.2.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/strip-indent": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz", - "integrity": "sha512-I5iQq6aFMM62fBEAIB/hXzwJD6EEZ0xEGCX2t7oXqaKPIRgt4WruAQ285BISgdkP+HLGWyeGmNJcpIwFeRYRUA==", - "dev": true, - "dependencies": { - "get-stdin": "^4.0.1" - }, - "bin": { - "strip-indent": "cli.js" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/stripe-as-promised": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/stripe-as-promised/-/stripe-as-promised-2.1.1.tgz", - "integrity": "sha512-vcOvQ9WhbE+OpQUyANCslAp3tG8S8rae9yr9QStZI01Lilba6peS4cZQ6LUNpr3AFZXGa9oLa4wvA0CiBcLjvQ==", - "dependencies": { - "dot-prop": "~2.1.0", - "pify": "~2.3.0", - "stripe-errback": "~1.0.1" - } - }, - "node_modules/stripe-as-promised/node_modules/dot-prop": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-2.1.0.tgz", - "integrity": "sha512-ZrQHU2Oq0PbVb5lu5YitVJnpfC5HfWo+0lQnDK5TO4ipbzC1EiEYh/qDCu4j+7v6MDBgBfwTdFiznMSNPZXD7Q==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/stripe-errback": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/stripe-errback/-/stripe-errback-1.0.2.tgz", - "integrity": "sha512-4prrHme/PAKDuvL45q+Xx3b2epm1/FOjFshpStIOchROv/YpwPTtsXH2u9s7PwSQHKBBag8yzt/J05yn6oGe8w==", - "dependencies": { - "dot-prop": "~3.0.0", - "xtend": "~4.0.1" - } - }, - "node_modules/subarg": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/subarg/-/subarg-1.0.0.tgz", - "integrity": "sha512-RIrIdRY0X1xojthNcVtgT9sjpOGagEUKpZdgBUi054OEPFo282yg+zE+t1Rj3+RqKq2xStL7uUHhY+AjbC4BXg==", - "dependencies": { - "minimist": "^1.1.0" - } - }, - "node_modules/superagent": { - "version": "3.8.3", - "resolved": "https://registry.npmjs.org/superagent/-/superagent-3.8.3.tgz", - "integrity": "sha512-GLQtLMCoEIK4eDv6OGtkOoSMt3D+oq0y3dsxMuYuDvaNUvuT8eFBuLmfR0iYYzHC1e8hpzC6ZsxbuP6DIalMFA==", - "deprecated": "Please upgrade to v7.0.2+ of superagent. We have fixed numerous issues with streams, form-data, attach(), filesystem errors not bubbling up (ENOENT on attach()), and all tests are now passing. See the releases tab for more information at .", - "dev": true, - "dependencies": { - "component-emitter": "^1.2.0", - "cookiejar": "^2.1.0", - "debug": "^3.1.0", - "extend": "^3.0.0", - "form-data": "^2.3.1", - "formidable": "^1.2.0", - "methods": "^1.1.1", - "mime": "^1.4.1", - "qs": "^6.5.1", - "readable-stream": "^2.3.5" - }, - "engines": { - "node": ">= 4.0" - } - }, - "node_modules/superagent-proxy": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/superagent-proxy/-/superagent-proxy-1.0.3.tgz", - "integrity": "sha512-79Ujg1lRL2ICfuHUdX+H2MjIw73kB7bXsIkxLwHURz3j0XUmEEEoJ+u/wq+mKwna21Uejsm2cGR3OESA00TIjA==", - "dev": true, - "dependencies": { - "debug": "^3.1.0", - "proxy-agent": "2" - }, - "peerDependencies": { - "superagent": ">= 0.15.4 || 1 || 2 || 3" - } - }, - "node_modules/superagent-proxy/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/superagent-proxy/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 - }, - "node_modules/superagent/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/superagent/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 - }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/syntax-error": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/syntax-error/-/syntax-error-1.4.0.tgz", - "integrity": "sha512-YPPlu67mdnHGTup2A8ff7BC2Pjq0e0Yp/IyTFN03zWO0RcK07uLcbi7C2KpGR2FvWbaB0+bfE27a+sBKebSo7w==", - "dependencies": { - "acorn-node": "^1.2.0" - } - }, - "node_modules/text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dev": true - }, - "node_modules/through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==" - }, - "node_modules/through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", - "dependencies": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" - } - }, - "node_modules/thunkify": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/thunkify/-/thunkify-2.1.2.tgz", - "integrity": "sha512-w9foI80XcGImrhMQ19pxunaEC5Rp2uzxZZg4XBAFRfiLOplk3F0l7wo+bO16vC2/nlQfR/mXZxcduo0MF2GWLg==", - "dev": true - }, - "node_modules/timers-browserify": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-1.4.2.tgz", - "integrity": "sha512-PIxwAupJZiYU4JmVZYwXp9FKsHMXb5h0ZEFyuXTAn8WLHOlcij+FEcbrvDsom1o5dr1YggEtFbECvGCW2sT53Q==", - "dependencies": { - "process": "~0.11.0" - }, - "engines": { - "node": ">=0.6.0" - } - }, - "node_modules/tiny-lr": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/tiny-lr/-/tiny-lr-1.1.1.tgz", - "integrity": "sha512-44yhA3tsaRoMOjQQ+5v5mVdqef+kH6Qze9jTpqtVufgYjYt08zyZAwNwwVBj3i1rJMnR52IxOW0LK0vBzgAkuA==", - "dev": true, - "dependencies": { - "body": "^5.1.0", - "debug": "^3.1.0", - "faye-websocket": "~0.10.0", - "livereload-js": "^2.3.0", - "object-assign": "^4.1.0", - "qs": "^6.4.0" - } - }, - "node_modules/tiny-lr/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/tiny-lr/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 - }, - "node_modules/tinycolor": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/tinycolor/-/tinycolor-0.0.1.tgz", - "integrity": "sha512-+CorETse1kl98xg0WAzii8DTT4ABF4R3nquhrkIbVGcw1T8JYs5Gfx9xEfGINPUZGDj9C4BmOtuKeaTtuuRolg==", - "dev": true, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/to-array": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/to-array/-/to-array-0.1.4.tgz", - "integrity": "sha512-LhVdShQD/4Mk4zXNroIQZJC+Ap3zgLcDuwEdcmLv9CCO73NWockQDwyUnW/m8VX/EElfL6FcYx7EeutN4HJA6A==" - }, - "node_modules/to-arraybuffer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz", - "integrity": "sha512-okFlQcoGTi4LQBG/PgSYblw9VOyptsz2KJZqc6qtgGdes8VktzUQkj4BI2blit072iS8VODNcMA+tvnS9dnuMA==" - }, - "node_modules/to-buffer": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.2.1.tgz", - "integrity": "sha512-tB82LpAIWjhLYbqjx3X4zEeHN6M8CiuOEy2JY8SEQVdYRe3CCHOFaqrBW1doLDrfpWhplcW7BL+bO3/6S3pcDQ==", - "license": "MIT", - "dependencies": { - "isarray": "^2.0.5", - "safe-buffer": "^5.2.1", - "typed-array-buffer": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/to-buffer/node_modules/isarray": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", - "license": "MIT" - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/to-regex-range/node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", - "dev": true, - "engines": { - "node": ">=0.6" - } - }, - "node_modules/trim-newlines": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz", - "integrity": "sha512-Nm4cF79FhSTzrLKGDMi3I4utBtFv8qKy4sq1enftf2gMdpqI8oVQTAfySkTz5r49giVzDj88SVZXP4CeYQwjaw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/trunk8": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/trunk8/-/trunk8-0.0.1.tgz", - "integrity": "sha512-hp7Se9cqgc0/jcuVVaEe4kAXTFKl+7R2/CkQ9mThA0AzF5RUBTxO9DO6M5FxqymGtC3EEUSEdHEjjj/17TLdsQ==", - "peerDependencies": { - "jquery": ">=1.7.1" - } - }, - "node_modules/tryor": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/tryor/-/tryor-0.1.2.tgz", - "integrity": "sha512-2+ilNA00DGvbUYYbRrm3ux+snbo7I6uPXMw8I4p/QMl7HUOWBBZFbk+Mpr8/IAPDQE+LQ8vOdlI6xEzjc+e/BQ==", - "dev": true - }, - "node_modules/tsconfig-paths": { - "version": "3.14.2", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.2.tgz", - "integrity": "sha512-o/9iXgCYc5L/JxCHPe3Hvh8Q/2xm5Z+p18PESBU6Ff33695QnCHBEjcytY2q19ua7Mbl/DavtBOLq+oG0RCL+g==", - "dev": true, - "dependencies": { - "@types/json5": "^0.0.29", - "json5": "^1.0.2", - "minimist": "^1.2.6", - "strip-bom": "^3.0.0" - } - }, - "node_modules/tsconfig-paths/node_modules/strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/tslib": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.2.tgz", - "integrity": "sha512-5svOrSA2w3iGFDs1HibEVBGbDrAY82bFQ3HZ3ixB+88nsbsWQoKqDRb5UBYAUPEzbBn6dAp5gRNXglySbx1MlA==" - }, - "node_modules/tty-browserify": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.1.tgz", - "integrity": "sha512-C3TaO7K81YvjCgQH9Q1S3R3P3BtN3RIM8n+OvX4il1K1zgE8ZhI0op7kClgkxtutIE8hQrcrHBXvIheqKUUCxw==" - }, - "node_modules/twix": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/twix/-/twix-1.3.0.tgz", - "integrity": "sha512-D1kCQgEuRZLZHIoYa6HUMgEBwHTzN8n8gYVX1y8uBooM+ZpowNyPcAuYk8YCkZxoIKIMOR7qgrae9yUUBP3yDQ==", - "engines": { - "node": "*" - }, - "peerDependencies": { - "moment": ">= 2.10.0" - } - }, - "node_modules/type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", - "dev": true, - "dependencies": { - "prelude-ls": "~1.1.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/typechecker": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/typechecker/-/typechecker-6.4.0.tgz", - "integrity": "sha512-EbOu+9szY13mhl0EsvLXnR+pTCa3gTHQQPLdce72ujcC9fRHXlVFBNXtHeRhgzLxLlKUh4zA9C0tezLDgshf+A==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://bevry.me/fund" - } - }, - "node_modules/typed-array-buffer": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", - "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "es-errors": "^1.3.0", - "is-typed-array": "^1.1.14" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/typed-array-length": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz", - "integrity": "sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "for-each": "^0.3.3", - "is-typed-array": "^1.1.9" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/typedarray": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.7.tgz", - "integrity": "sha512-ueeb9YybpjhivjbHP2LdFDAjbS948fGEPj+ACAMs4xCMmh72OCOMQWBQKlaN4ZNQ04yfLSDLSx1tGRIoWimObQ==", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/uglify-js": { - "version": "3.17.4", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz", - "integrity": "sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==", - "devOptional": true, - "bin": { - "uglifyjs": "bin/uglifyjs" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/ultron": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.1.tgz", - "integrity": "sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og==", - "dev": true - }, - "node_modules/umd": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/umd/-/umd-3.0.3.tgz", - "integrity": "sha512-4IcGSufhFshvLNcMCV80UnQVlZ5pMOC8mvNPForqwA4+lzYQuetTESLDQkeLmihq8bRcnpbQa48Wb8Lh16/xow==", - "bin": { - "umd": "bin/cli.js" - } - }, - "node_modules/unbox-primitive": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", - "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "has-bigints": "^1.0.2", - "has-symbols": "^1.0.3", - "which-boxed-primitive": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/unc-path-regex": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz", - "integrity": "sha512-eXL4nmJT7oCpkZsHZUOJo8hcX3GbsiDOa0Qu9F646fi8dT3XuSVopVqAcEiVzSKKH7UoDti23wNX3qGFxcW5Qg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/undeclared-identifiers": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/undeclared-identifiers/-/undeclared-identifiers-1.1.3.tgz", - "integrity": "sha512-pJOW4nxjlmfwKApE4zvxLScM/njmwj/DiUBv7EabwE4O8kRUy+HIwxQtZLBPll/jx1LJyBcqNfB3/cpv9EZwOw==", - "dependencies": { - "acorn-node": "^1.3.0", - "dash-ast": "^1.0.0", - "get-assigned-identifiers": "^1.2.0", - "simple-concat": "^1.0.0", - "xtend": "^4.0.1" - }, - "bin": { - "undeclared-identifiers": "bin.js" - } - }, - "node_modules/underscore.string": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-3.3.6.tgz", - "integrity": "sha512-VoC83HWXmCrF6rgkyxS9GHv8W9Q5nhMKho+OadDJGzL2oDYbYEppBaCMH6pFlwLeqj2QS+hhkw2kpXkSdD1JxQ==", - "dev": true, - "dependencies": { - "sprintf-js": "^1.1.1", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": "*" - } - }, - "node_modules/underscore.string/node_modules/sprintf-js": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.2.tgz", - "integrity": "sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==", - "dev": true - }, - "node_modules/universalify": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", - "dev": true, - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/upper-case": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/upper-case/-/upper-case-1.1.3.tgz", - "integrity": "sha512-WRbjgmYzgXkCV7zNVpy5YgrHgbBv126rMALQQMrmzOVC4GM2waQ9x7xtm8VU+1yF2kWyPzI9zbZ48n4vSxwfSA==", - "dev": true - }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/uri-js/node_modules/punycode": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", - "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/uri-path": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/uri-path/-/uri-path-1.0.0.tgz", - "integrity": "sha512-8pMuAn4KacYdGMkFaoQARicp4HSw24/DHOVKWqVRJ8LhhAwPPFpdGvdL9184JVmUwe7vz7Z9n6IqI6t5n2ELdg==", - "dev": true, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/url": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz", - "integrity": "sha512-kbailJa29QrtXnxgq+DdCEGlbTeYM2eJUxsz6vjZavrCYPMIFHMKQmSKYAIuUK2i7hgPm28a8piX5NTUtM/LKQ==", - "dependencies": { - "punycode": "1.3.2", - "querystring": "0.2.0" - } - }, - "node_modules/url/node_modules/punycode": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", - "integrity": "sha512-RofWgt/7fL5wP1Y7fxE7/EmTLzQVnB0ycyibJ0OOHIlJqTNzglYFxVwETOcIoJqJmpDXJ9xImDv+Fq34F/d4Dw==" - }, - "node_modules/util": { - "version": "0.10.4", - "resolved": "https://registry.npmjs.org/util/-/util-0.10.4.tgz", - "integrity": "sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==", - "dependencies": { - "inherits": "2.0.3" - } - }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" - }, - "node_modules/util/node_modules/inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==" - }, - "node_modules/utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", - "dev": true, - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/v8flags": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-3.2.0.tgz", - "integrity": "sha512-mH8etigqMfiGWdeXpaaqGfs6BndypxusHHcv2qSHyZkGEznCd/qAXCWWRzeowtL54147cktFOC4P5y+kl8d8Jg==", - "dev": true, - "dependencies": { - "homedir-polyfill": "^1.0.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/validate-npm-package-license": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", - "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", - "dev": true, - "dependencies": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" - } - }, - "node_modules/vm-browserify": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-0.0.4.tgz", - "integrity": "sha512-NyZNR3WDah+NPkjh/YmhuWSsT4a0mF0BJYgUmvrJ70zxjTXh5Y2Asobxlh0Nfs0PCFB5FVpRJft7NozAWFMwLQ==", - "dependencies": { - "indexof": "0.0.1" - } - }, - "node_modules/w3cjs": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/w3cjs/-/w3cjs-0.4.0.tgz", - "integrity": "sha512-0J0eLWoHU+GVZm3PqoFt+M/UAcDkIPMJgdbQXFIdGbk7sJJQ4NQOsi6fXVZPeq3hffnEghV4gsKjyQC2PzgQVQ==", - "dev": true, - "dependencies": { - "commander": "^2.9.0", - "superagent": "^3.5.2", - "superagent-proxy": "^1.0.2" - }, - "bin": { - "w3cjs": "bin/w3cjs" - }, - "engines": { - "node": "*" - } - }, - "node_modules/websocket-driver": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", - "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", - "dev": true, - "dependencies": { - "http-parser-js": ">=0.5.1", - "safe-buffer": ">=5.1.0", - "websocket-extensions": ">=0.1.1" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/websocket-extensions": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", - "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/websocket-stream": { - "version": "5.5.2", - "resolved": "https://registry.npmjs.org/websocket-stream/-/websocket-stream-5.5.2.tgz", - "integrity": "sha512-8z49MKIHbGk3C4HtuHWDtYX8mYej1wWabjthC/RupM9ngeukU4IWoM46dgth1UOS/T4/IqgEdCDJuMe2039OQQ==", - "dev": true, - "dependencies": { - "duplexify": "^3.5.1", - "inherits": "^2.0.1", - "readable-stream": "^2.3.3", - "safe-buffer": "^5.1.2", - "ws": "^3.2.0", - "xtend": "^4.0.0" - } - }, - "node_modules/websocket-stream/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "node_modules/websocket-stream/node_modules/ws": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-3.3.3.tgz", - "integrity": "sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==", - "dev": true, - "dependencies": { - "async-limiter": "~1.0.0", - "safe-buffer": "~5.1.0", - "ultron": "~1.1.0" - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/which-boxed-primitive": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", - "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", - "dev": true, - "dependencies": { - "is-bigint": "^1.0.1", - "is-boolean-object": "^1.1.0", - "is-number-object": "^1.0.4", - "is-string": "^1.0.5", - "is-symbol": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/which-typed-array": { - "version": "1.1.19", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz", - "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==", - "license": "MIT", - "dependencies": { - "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.8", - "call-bound": "^1.0.4", - "for-each": "^0.3.5", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/word-wrap": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", - "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/wordwrap": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", - "integrity": "sha512-1tMA907+V4QmxV7dbRvb4/8MaRALK6q9Abid3ndMYnbyo8piisCmeONVqVSXqQA3KaP4SLt5b7ud6E2sqP8TFw==", - "dev": true, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" - }, - "node_modules/ws": { - "version": "0.4.32", - "resolved": "https://registry.npmjs.org/ws/-/ws-0.4.32.tgz", - "integrity": "sha512-htqsS0U9Z9lb3ITjidQkRvkLdVhQePrMeu475yEfOWkAYvJ6dSjQp1tOH6ugaddzX5b7sQjMPNtY71eTzrV/kA==", - "dev": true, - "hasInstallScript": true, - "dependencies": { - "commander": "~2.1.0", - "nan": "~1.0.0", - "options": ">=0.0.5", - "tinycolor": "0.x" - }, - "bin": { - "wscat": "bin/wscat" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/ws/node_modules/commander": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.1.0.tgz", - "integrity": "sha512-J2wnb6TKniXNOtoHS8TSrG9IOQluPrsmyAJ8oCUJOBmv+uLBCyPYAZkD2jFvw2DCzIXNnISIM01NIvr35TkBMQ==", - "dev": true, - "engines": { - "node": ">= 0.6.x" - } - }, - "node_modules/xmlbuilder": { - "version": "9.0.7", - "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz", - "integrity": "sha512-7YXTQc3P2l9+0rjaUbLwMKRhtmwg1M1eDf6nag7urC7pIPYLD9W/jmzQ4ptRSUbodw5S0jfoGTflLemQibSpeQ==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/xregexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/xregexp/-/xregexp-2.0.0.tgz", - "integrity": "sha512-xl/50/Cf32VsGq/1R8jJE5ajH1yMCQkpmoS10QbFZWl2Oor4H0Me64Pu2yxvsRWK3m6soJbmGfzSR7BYmDcWAA==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", - "engines": { - "node": ">=0.4" - } - }, - "node_modules/yallist": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", - "integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==", - "dev": true - }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - } - } -} diff --git a/src/Exceptionless.Web/ClientApp.angular/package.json b/src/Exceptionless.Web/ClientApp.angular/package.json deleted file mode 100644 index 120fe16e81..0000000000 --- a/src/Exceptionless.Web/ClientApp.angular/package.json +++ /dev/null @@ -1,98 +0,0 @@ -{ - "name": "exceptionless", - "private": true, - "scripts": { - "build": "npx grunt build", - "serve": "npx grunt serve", - "prod-serve": "cross-env ASPNETCORE_URLS=https://api.exceptionless.io npx grunt serve", - "lint": "eslint . --ext .js", - "lint:fix": "eslint . --ext .js --fix", - "prettier": "prettier . --write --ignore-unknown", - "prettier:check": "prettier . --check --ignore-unknown", - "prettier:staged": "prettier --write --ignore-unknown" - }, - "devDependencies": { - "cross-env": "7.0.3", - "eslint": "^8.45.0", - "eslint-config-airbnb-base": "^15.0.0", - "eslint-config-prettier": "^8.8.0", - "eslint-plugin-import": "^2.27.5", - "grunt": "1.6.1", - "grunt-angular-templates": "1.2.0", - "grunt-browser-output": "1.0.3", - "grunt-cache-bust": "1.7.0", - "grunt-connect-proxy2": "2.0.0", - "grunt-contrib-clean": "2.0.1", - "grunt-contrib-concat": "2.1.0", - "grunt-contrib-connect": "^4.0.0", - "grunt-contrib-copy": "1.0.0", - "grunt-contrib-cssmin": "5.0.0", - "grunt-contrib-htmlmin": "3.1.0", - "grunt-contrib-less": "3.0.0", - "grunt-contrib-uglify": "5.2.2", - "grunt-contrib-watch": "1.1.0", - "grunt-dom-munger": "^4.0.0", - "grunt-eslint": "^24.3.0", - "grunt-html-angular-validate": "^0.6.1", - "grunt-ng-annotate": "^4.0.0", - "grunt-replace": "^2.0.2", - "load-grunt-config": "^4.0.1", - "prettier": "^3.0.0" - }, - "dependencies": { - "angular": "1.8.3", - "angular-animate": "1.8.3", - "angular-clipboard": "1.7.0", - "angular-dialog-service": "5.3.0", - "angular-filter": "0.5.17", - "angular-gravatar": "0.4.2", - "angular-hotkeys": "1.7.0", - "angular-input-match": "0.0.3", - "angular-intercom": "2.1.1", - "angular-loading-bar": "0.9.0", - "angular-locker": "2.0.5", - "angular-messages": "1.8.3", - "angular-mocks": "1.8.3", - "angular-payments": "*", - "angular-resource": "1.8.3", - "angular-sanitize": "1.8.3", - "angular-stripe": "5.0.0", - "angular-translate": "2.19.0", - "angular-translate-loader-static-files": "2.19.0", - "angular-ui-bootstrap": "2.5.6", - "angular-ui-router": "0.4.2", - "angular-xeditable": "0.10.2", - "angularjs-toaster": "3.0.0", - "angulartics": "1.4.0", - "animate.css": "4.1.1", - "bootstrap": "3.4.1", - "bootstrap-daterangepicker": "2.1.27", - "checklist-model": "1.0.0", - "d3": "3.5.17", - "exceptionless": "1.6.4", - "font-awesome": "4.7.0", - "handlebars": "4.7.7", - "jquery": "3.7.0", - "less": "4.1.3", - "li": "1.3.0", - "lodash": "4.17.21", - "moment": "2.29.4", - "mousetrap": "1.6.5", - "ng-debounce": "1.0.1", - "ng-filters": "1.3.1", - "query-string": "4.2.3", - "restangular": "1.6.1", - "rickshaw": "1.7.1", - "satellizer": "0.15.5", - "trunk8": "0.0.1", - "twix": "1.3.0" - }, - "overrides": { - "angular": "1.8.3" - }, - "prettier": { - "printWidth": 120, - "singleQuote": false, - "trailingComma": "es5" - } -} diff --git a/src/Exceptionless.Web/ClientApp.angular/touch-icon-ipad-114.png b/src/Exceptionless.Web/ClientApp.angular/touch-icon-ipad-114.png deleted file mode 100644 index 8f546c8792..0000000000 Binary files a/src/Exceptionless.Web/ClientApp.angular/touch-icon-ipad-114.png and /dev/null differ diff --git a/src/Exceptionless.Web/ClientApp.angular/touch-icon-ipad-144.png b/src/Exceptionless.Web/ClientApp.angular/touch-icon-ipad-144.png deleted file mode 100644 index d4e2948f0b..0000000000 Binary files a/src/Exceptionless.Web/ClientApp.angular/touch-icon-ipad-144.png and /dev/null differ diff --git a/src/Exceptionless.Web/ClientApp/AGENTS.md b/src/Exceptionless.Web/ClientApp/AGENTS.md index 2a0ce1cbb2..6b99ff1405 100644 --- a/src/Exceptionless.Web/ClientApp/AGENTS.md +++ b/src/Exceptionless.Web/ClientApp/AGENTS.md @@ -1,6 +1,10 @@ # Svelte ClientApp -This directory is the Svelte 5 frontend. For frontend/UI/page/component/form/route work, use this app unless the user explicitly asks for Angular or legacy UI changes. +This directory is the only Exceptionless application frontend. Svelte 5 serves the app at `/`. All frontend/UI/page/component/form/route work belongs here. + +Use root routes and `$app/paths` resolution for maintained navigation. `#!/` and `#/` are supported only as incoming compatibility links. Keep their coverage in `legacy-links` tests, and preserve `satellizer_token` so existing sessions survive the cutover. + +Stack action links from emails and Slack require authentication and explicit confirmation. Opening, reloading, or cancelling an action link must never mutate the stack. Keep confirmation and failed-update recovery covered alongside incoming-link compatibility. ## Tooling @@ -30,7 +34,6 @@ Use focused verification while iterating. Do not run broad Svelte validation aft - Use `createQueryParameters` from `$shared/query-params` for route query parameters instead of ad-hoc URL parsing. - Prefer shared components and formatters from `$comp`, `$shared`, and `$lib` before creating new primitives. - Use installed shadcn-svelte components from `$comp/ui/*`; check `components.json` and the `src/lib/features/shared/components/ui` directory before importing a component. -- Do not copy structure, state patterns, or styling patterns from `../ClientApp.angular`. ## Applying Installed Skills @@ -40,7 +43,7 @@ Use focused verification while iterating. Do not run broad Svelte validation aft ## Local URLs -- Svelte app: `https://web-ex.dev.localhost:7131/next/` +- Svelte app: `https://web-ex.dev.localhost:7131/` - API health: `https://api-ex.dev.localhost:7111/api/v2/about` - API health fallback for command-line tools with local TLS issues: `http://api-ex.dev.localhost:7110/api/v2/about` diff --git a/src/Exceptionless.Web/ClientApp/README.md b/src/Exceptionless.Web/ClientApp/README.md index f063ab5bb1..97866228f1 100644 --- a/src/Exceptionless.Web/ClientApp/README.md +++ b/src/Exceptionless.Web/ClientApp/README.md @@ -1,25 +1,48 @@ # Exceptionless User Interface +This is the only Exceptionless application frontend: Svelte 5 and SvelteKit, served at `/`. New links use root routes; primary historical `#!/` and `#/` links remain supported by the navigation compatibility layer. The existing session storage key is retained so users stay signed in. + ## Developing -Once you've created a project and installed dependencies with `npm install`, start a development server: +For full-stack development, run `aspire run` from the repository root. Open the `App` endpoint reported by Aspire, normally `https://web-ex.dev.localhost:7131/`. Worktrees may use dynamic ports. Aspire supplies this frontend address to both the API and background jobs for OAuth and email links. Vite proxies API requests and WebSockets to the API resource. -```bash -npm run dev +For frontend commands, run from this directory and install dependencies with `npm ci`: -# or start the server and open the app in a new browser tab -npm run dev -- --open +```powershell +npm ci +npm run dev ``` +The standalone dev server needs a running API. `npm run urls` reports local Aspire endpoints; set `API_HTTPS` or `API_HTTP` to the reported API URL when starting Vite outside Aspire. Follow [AGENTS.md](AGENTS.md) for frontend conventions and verification scope. + ## Building To create a production version of your app: -```bash +```powershell npm run build ``` -You can preview the production build with `npm run preview`. +The static output goes to `build/`. Publishing `Exceptionless.Web` includes it directly in `wwwroot`; Docker app images use `build/update-config.sh` from the repository root to write public runtime configuration. Startup stops if configuration generation fails. `EX_ApiUrl` selects a separate API origin for requests and the API Reference link when configured, otherwise they use the app origin. + +`EX_EnableSsl=true` retains forced HTTPS navigation for self-hosted deployments, including proxies that forward to an HTTP-only app container. The UI upgrades the current URL before starting the router or authentication, preserving its path, query, fragment, and any explicit nonstandard port. The default remains `false`. Standalone Vite development uses `PUBLIC_ENABLE_SSL` for the same behavior. + +Set the backend's `EX_BaseURL` to the public UI URL. Email links and OAuth authorization redirects use this address, including when the API has a separate origin. + +New email, Slack, and webhook links use the root application origin even if `EX_BaseURL` still contains a historical hash suffix. Incoming compatibility links also normalize existing browser history entries so Back and Forward continue to work. + +For separate UI and API hosts, set `EX_ApiUrl` to the public API URL on both deployments. The API uses this origin for OAuth discovery, issuer, resource validation, and authentication challenges; the browser authorization page still uses `EX_BaseURL`. If `EX_ApiUrl` is unset, the API retains `EX_BaseURL` as its canonical origin. + +`npm run preview` previews the static build only; use Aspire for full-stack testing. + +## Testing + +```powershell +npm run test:unit -- --run +npm run test:e2e +``` + +See the [E2E guide](e2e/README.md) for local runtime setup and compatibility coverage. Before pushing frontend changes, run `npm run validate` and inspect formatting changes. ## Upgrading components diff --git a/src/Exceptionless.Web/ClientApp/e2e/README.md b/src/Exceptionless.Web/ClientApp/e2e/README.md index 319d8b4c1c..9716cdb2d8 100644 --- a/src/Exceptionless.Web/ClientApp/e2e/README.md +++ b/src/Exceptionless.Web/ClientApp/e2e/README.md @@ -2,6 +2,12 @@ These tests protect user-visible release workflows. They complement backend integration tests instead of repeating endpoint and repository assertions. +## Local Runtime + +Run `aspire run` from the repository root and target the reported Svelte `App` endpoint at `/`. The API and WebSocket traffic share that browser-facing endpoint through Vite. `npm run test:e2e` defaults to `https://web-ex.dev.localhost:7131`; set `E2E_URL` to the reported endpoint when Aspire assigns a dynamic port. Follow root `AGENTS.md` for external URL authorization. + +Ordinary test navigation uses root routes. `tests/legacy-links.e2e.ts` specifically covers primary historical hash links, retained sessions, browser history, and old stack action links that require confirmation and leave state unchanged when cancelled. `tests/stack-triage.e2e.ts` also covers email action login, confirmation, and failed mutation recovery. + ## Test Boundary - Use the API to arrange isolated data, wait for asynchronous ingestion, and clean up. diff --git a/src/Exceptionless.Web/ClientApp/e2e/fixtures/api-client.ts b/src/Exceptionless.Web/ClientApp/e2e/fixtures/api-client.ts index fc41eadc46..f70a8d49b4 100644 --- a/src/Exceptionless.Web/ClientApp/e2e/fixtures/api-client.ts +++ b/src/Exceptionless.Web/ClientApp/e2e/fixtures/api-client.ts @@ -262,7 +262,7 @@ export class E2EApiClient { throw new Error(`Timed out waiting for E2E event with reference id ${referenceId}`); } - public async pollForMailToken(email: string, path: 'reset-password' | 'signup', timeoutMs = 30_000): Promise { + public async pollForMailLink(email: string, path: 'reset-password' | 'signup', timeoutMs = 30_000): Promise { const deadline = Date.now() + timeoutMs; const normalizedEmail = email.toLowerCase(); @@ -288,9 +288,15 @@ export class E2EApiClient { await expectStatus(messageResponse, [200], 'read local mail'); const messageResult = toRecord(await readJson(messageResponse), 'mail message response'); const content = `${getOptionalString(messageResult, 'HTML') ?? ''}\n${getOptionalString(messageResult, 'Text') ?? ''}`; - const token = extractMailToken(content, path); - if (token) { - return token; + const link = extractMailLink(content, path); + if (link) { + // Keep the delivered path, query, and fragment; only adapt the local Aspire origin. + const delivered = new URL(link); + const local = new URL(this.environment.appUrl); + if (!delivered.hostname.endsWith('.localhost') && delivered.hostname !== 'localhost' && delivered.hostname !== '127.0.0.1') { + throw new Error('Expected a local application link in test mail.'); + } + return new URL(delivered.pathname + delivered.search + delivered.hash, local.origin).href; } } @@ -300,6 +306,11 @@ export class E2EApiClient { throw new Error(`Timed out waiting for ${path} email sent to ${email}`); } + public async pollForMailToken(email: string, path: 'reset-password' | 'signup', timeoutMs = 30_000): Promise { + const link = new URL(await this.pollForMailLink(email, path, timeoutMs)); + return path === 'signup' ? link.searchParams.get('token')! : decodeURIComponent(link.pathname.split('/').at(-1)!); + } + public async recordProductTour(token: string, tourName: string): Promise { const response = await this.request.put(this.url(`users/me/product-tours/${tourName}/record`), { headers: this.authHeaders(token) @@ -424,10 +435,14 @@ async function expectStatus(response: APIResponse, expectedStatuses: number[], o throw new Error(`${operation} failed with status ${response.status()} ${response.statusText()}${body ? `: ${body}` : ''}`); } -function extractMailToken(content: string, path: 'reset-password' | 'signup'): string | undefined { - const pattern = path === 'reset-password' ? /\/reset-password\/([^?"'<\\\s]+)/ : /\/signup\?token=([^&"'<\\\s]+)/; - const match = pattern.exec(content.replaceAll('&', '&')); - return match?.[1] ? decodeURIComponent(match[1]) : undefined; +function extractMailLink(content: string, path: 'reset-password' | 'signup'): string | undefined { + const links = content.replaceAll('&', '&').match(/https?:\/\/[^"'<\\\s]+/g) ?? []; + return links.find((link) => { + const url = new URL(link); + return path === 'signup' + ? url.pathname === '/signup' && url.searchParams.has('token') + : url.pathname.startsWith('/reset-password/') && !url.searchParams.has('cancel'); + }); } function getOptionalString(value: Record, key: string): string | undefined { diff --git a/src/Exceptionless.Web/ClientApp/e2e/support/exceptionless-journey.ts b/src/Exceptionless.Web/ClientApp/e2e/support/exceptionless-journey.ts index f3b8a303af..eca661242d 100644 --- a/src/Exceptionless.Web/ClientApp/e2e/support/exceptionless-journey.ts +++ b/src/Exceptionless.Web/ClientApp/e2e/support/exceptionless-journey.ts @@ -106,7 +106,7 @@ export class ExceptionlessE2EJourney { public async createFirstProjectAndVerifyConfigureToken(): Promise { if (this.projectId) { - await this.page.goto(`/next/project/${this.projectId}/configure`); + await this.page.goto(`/project/${this.projectId}/configure`); } else { await this.page.getByRole('link', { name: 'add a new project' }).click(); await expect(this.page.getByRole('heading', { name: 'Add Project' })).toBeVisible(); @@ -114,7 +114,7 @@ export class ExceptionlessE2EJourney { await this.page.getByLabel('Project Name', { exact: true }).fill(this.projectName); await this.page.getByRole('button', { name: 'Add Project' }).click(); - await this.page.waitForURL(/\/next\/project\/[^/]+\/configure/, { timeout: 30_000 }); + await this.page.waitForURL(/\/project\/[^/]+\/configure/, { timeout: 30_000 }); this.projectId = getIdFromUrl(this.page, /\/project\/([^/]+)\/configure/); } @@ -130,7 +130,7 @@ export class ExceptionlessE2EJourney { public async expectEventDetails(): Promise { expect(this.eventId).toBeTruthy(); - await this.page.goto(`/next/event/${this.eventId}`); + await this.page.goto(`/event/${this.eventId}`); await expect(getVisibleText(this.page, this.message)).toBeVisible({ timeout: 30_000 }); await expect(this.page.getByRole('tab', { name: 'Overview' })).toBeVisible(); await expect(this.page.getByRole('tab', { name: 'Exception' })).toBeVisible(); @@ -164,15 +164,15 @@ export class ExceptionlessE2EJourney { } public async expectEventInPrimaryViews(): Promise { - await this.page.goto('/next/event'); + await this.page.goto('/event'); await expect(this.page.getByRole('heading', { name: 'Events' })).toBeVisible(); await expect(getVisibleText(this.page, this.message)).toBeVisible({ timeout: 30_000 }); - await this.page.goto('/next/stack'); + await this.page.goto('/stack'); await expect(this.page.getByRole('heading', { name: 'Stacks' })).toBeVisible(); await expect(getVisibleText(this.page, this.message)).toBeVisible({ timeout: 30_000 }); - await this.page.goto('/next/stream'); + await this.page.goto('/stream'); await expect(this.page.getByRole('heading', { name: 'Event Stream' })).toBeVisible(); await expect(getVisibleText(this.page, this.message)).toBeVisible({ timeout: 30_000 }); } @@ -202,7 +202,7 @@ export class ExceptionlessE2EJourney { } public async signUpAndCreateOrganization(): Promise { - await this.page.goto('/next/signup'); + await this.page.goto('/signup'); await this.page.getByLabel('Name', { exact: true }).fill(this.userName); await this.page.getByLabel('Email', { exact: true }).fill(this.email); @@ -214,7 +214,7 @@ export class ExceptionlessE2EJourney { const setupHeading = this.page.getByText('Set Up Exceptionless'); if (!(await setupHeading.isVisible())) { - await this.page.goto('/next/organization/add'); + await this.page.goto('/organization/add'); } await expect(setupHeading).toBeVisible({ timeout: 30_000 }); @@ -223,7 +223,7 @@ export class ExceptionlessE2EJourney { await this.page.getByLabel('Project Name', { exact: true }).fill(this.projectName); await this.page.getByRole('button', { name: 'Continue' }).click(); - await this.page.waitForURL(/\/next\/project\/[^/]+\/configure/, { timeout: 30_000 }); + await this.page.waitForURL(/\/project\/[^/]+\/configure/, { timeout: 30_000 }); this.projectId = getIdFromUrl(this.page, /\/project\/([^/]+)\/configure/); this.organizationId = await this.getOrganizationIdByName(); await expect(this.page.getByRole('button', { name: 'Please select a project type' })).toBeVisible(); diff --git a/src/Exceptionless.Web/ClientApp/e2e/tests/api-origin.e2e.ts b/src/Exceptionless.Web/ClientApp/e2e/tests/api-origin.e2e.ts new file mode 100644 index 0000000000..385f5be688 --- /dev/null +++ b/src/Exceptionless.Web/ClientApp/e2e/tests/api-origin.e2e.ts @@ -0,0 +1,125 @@ +import { expect, test } from '../fixtures/e2e-test'; + +test.skip(process.env.E2E_ENV === 'production', 'API origin configuration coverage is local only.'); + +test('configured HTTPS preserves incoming links before starting HTTP API requests', async ({ e2eApi, page }) => { + const httpOrigin = 'http://localhost:65532'; + const httpsOrigin = 'https://localhost:65532'; + const destination = '/login?redirect=%2Fstack%2Fall#notice'; + let sslFlagInjected = false; + const insecureApiRequests: string[] = []; + page.on('request', (request) => { + const url = new URL(request.url()); + if (url.protocol === 'http:' && url.pathname.startsWith('/api/')) { + insecureApiRequests.push(url.pathname); + } + }); + + // Model a proxy exposing HTTP and HTTPS while serving the real local Svelte app. + await page.route(`${httpOrigin}/**`, async (route) => { + const url = new URL(route.request().url()); + const response = await route.fetch({ url: new URL(url.pathname + url.search, e2eApi.environment.appUrl).href }); + if (url.pathname.endsWith('virtual:env/dynamic/public')) { + sslFlagInjected = true; + await route.fulfill({ body: `${await response.text()}\nenv.PUBLIC_ENABLE_SSL = 'true';`, response }); + } else { + await route.fulfill({ response }); + } + }); + await page.route(`${httpsOrigin}/**`, (route) => route.fulfill({ body: 'Secure destination', contentType: 'text/html' })); + + await page.goto(`${httpOrigin}${destination}`, { waitUntil: 'domcontentloaded' }); + await expect(page).toHaveURL(`${httpsOrigin}${destination}`); + await expect(page.getByRole('heading', { name: 'Secure destination' })).toBeVisible(); + expect(sslFlagInjected).toBe(true); + expect(insecureApiRequests).toEqual([]); +}); + +test('the OAuth bridge opens the application login and preserves the authorization return URL', async ({ e2eApi, page, request }) => { + const query = '?client_id=local-client&state=local-cutover-check'; + const response = await request.get(`${e2eApi.environment.apiUrl}/oauth/authorize${query}`, { maxRedirects: 0 }); + expect(response.status()).toBe(302); + const destination = new URL(response.headers().location); + expect(destination.pathname).toBe('/oauth/authorize'); + expect(destination.search).toBe(query); + expect(destination.hash).toBe(''); + expect(destination.port).toBe(new URL(e2eApi.environment.appUrl).port); + + await page.goto(destination.href); + await expect(page).toHaveURL((url) => url.pathname === '/login' && url.searchParams.get('redirect') === `/oauth/authorize${query}`); + await expect(page.getByRole('button', { exact: true, name: 'Login' })).toBeVisible(); +}); + +test('status waits for the configured API to recover before returning to the application', async ({ page }) => { + await page.clock.install(); + const apiOrigin = 'https://localhost:65533'; + await page.addInitScript((origin) => localStorage.setItem('PUBLIC_BASE_URL', origin), apiOrigin); + let apiHealthy = false; + let apiHealthRequests = 0; + let sameOriginHealthRequests = 0; + await page.route('**/health', async (route) => { + if (new URL(route.request().url()).origin === apiOrigin) { + apiHealthRequests++; + await route.fulfill({ body: apiHealthy ? 'Healthy' : 'Unhealthy', status: apiHealthy ? 200 : 503 }); + } else { + sameOriginHealthRequests++; + await route.fulfill({ body: 'Healthy' }); + } + }); + + await page.goto('/status?redirect=%2Flogin'); + await expect.poll(() => apiHealthRequests).toBeGreaterThan(0); + await expect(page).toHaveURL(/\/status\?redirect=/); + await expect(page.getByText('Service Status', { exact: true })).toBeVisible(); + expect(sameOriginHealthRequests).toBe(0); + + apiHealthy = true; + // Advance the health query's retry interval while keeping real request/redirect assertions. + await page.clock.fastForward(30_001); + await expect(page).toHaveURL(/\/login$/); + expect(apiHealthRequests).toBeGreaterThan(1); + expect(sameOriginHealthRequests).toBe(0); +}); + +test('the application sends API requests and push connections to its configured origin', async ({ e2eApi, e2eScenario, page }) => { + const apiOrigin = 'https://localhost:65533'; + const telemetryOrigin = 'https://localhost:65534'; + await page.addInitScript( + ({ apiOrigin, telemetryOrigin }) => { + localStorage.setItem('PUBLIC_BASE_URL', apiOrigin); + localStorage.setItem('PUBLIC_EXCEPTIONLESS_SERVER_URL', telemetryOrigin); + localStorage.setItem('PUBLIC_EXCEPTIONLESS_API_KEY', '00000000000000000000000000000000'); + }, + { apiOrigin, telemetryOrigin } + ); + // Telemetry has a separate destination and must not affect the application API-origin assertion. + const telemetryRequests: string[] = []; + await page.route(`${telemetryOrigin}/api/v2/**`, async (route) => { + telemetryRequests.push(new URL(route.request().url()).pathname); + await route.fulfill({ json: { settings: {}, version: 1 } }); + }); + // Intercept a distinct local origin and forward HTTP requests to this test's actual API. + await page.route(`${apiOrigin}/api/v2/**`, async (route) => { + const url = new URL(route.request().url()); + const response = await route.fetch({ url: new URL(url.pathname + url.search, e2eApi.environment.appUrl).href }); + await route.fulfill({ response }); + }); + const pushConnections: string[] = []; + await page.routeWebSocket('wss://localhost:65533/api/v2/push*', (socket) => { + pushConnections.push(new URL(socket.url()).pathname); + }); + const sameOriginApiRequests: string[] = []; + page.on('request', (request) => { + const url = new URL(request.url()); + if (url.origin === new URL(e2eApi.environment.appUrl).origin && url.pathname.startsWith('/api/v2/')) { + sameOriginApiRequests.push(url.pathname); + } + }); + + await page.goto('/stack/all'); + await expect(page.getByRole('heading', { name: 'All' })).toBeVisible(); + await expect(page.getByRole('button').filter({ hasText: e2eScenario.organizationName }).filter({ visible: true }).first()).toBeVisible(); + await expect.poll(() => pushConnections.length).toBeGreaterThan(0); + await expect.poll(() => telemetryRequests).toContain('/api/v2/projects/config'); + expect(sameOriginApiRequests).toEqual([]); +}); diff --git a/src/Exceptionless.Web/ClientApp/e2e/tests/authentication.e2e.ts b/src/Exceptionless.Web/ClientApp/e2e/tests/authentication.e2e.ts index dbf6f19ca1..8acd66f60a 100644 --- a/src/Exceptionless.Web/ClientApp/e2e/tests/authentication.e2e.ts +++ b/src/Exceptionless.Web/ClientApp/e2e/tests/authentication.e2e.ts @@ -9,13 +9,13 @@ test('user can recover from a failed login, restore the session, and log out', a try { await test.step('show an actionable error for invalid credentials', async () => { - await page.goto('/next/login'); + await page.goto('/login'); await page.getByLabel('Email', { exact: true }).fill(e2eScenario.email); await page.getByPlaceholder('Enter password').fill(`${E2E_TEST_PASSWORD}-invalid`); await page.getByRole('button', { exact: true, name: 'Login' }).click(); await expect(page.getByText('Invalid email or password', { exact: true })).toBeVisible(); - await expect(page).toHaveURL(/\/next\/login(?:[?#]|$)/); + await expect(page).toHaveURL(/\/login(?:[?#]|$)/); }); await test.step('log in through the form', async () => { @@ -23,14 +23,14 @@ test('user can recover from a failed login, restore the session, and log out', a await page.getByRole('button', { exact: true, name: 'Login' }).click(); await expect(page.getByRole('heading', { name: 'All' })).toBeVisible({ timeout: 30_000 }); - await expect(page).toHaveURL(/\/next\/stack\/all(?:[?#]|$)/); + await expect(page).toHaveURL(/\/stack\/all(?:[?#]|$)/); }); await test.step('restore the authenticated application after a reload', async () => { await page.reload(); await expect(page.getByRole('heading', { name: 'All' })).toBeVisible({ timeout: 30_000 }); - await expect(page).toHaveURL(/\/next\/stack\/all(?:[?#]|$)/); + await expect(page).toHaveURL(/\/stack\/all(?:[?#]|$)/); }); await test.step('log out through the user menu', async () => { @@ -38,14 +38,14 @@ test('user can recover from a failed login, restore the session, and log out', a await page.getByRole('menuitem', { exact: true, name: 'Log Out' }).click(); await expect(page.getByRole('button', { exact: true, name: 'Login' })).toBeVisible(); - await expect(page).toHaveURL(/\/next\/login(?:[?#]|$)/); + await expect(page).toHaveURL(/\/login(?:[?#]|$)/); }); await test.step('redirect a signed-out user away from a protected route', async () => { - await page.goto('/next/stack'); + await page.goto('/stack'); await expect(page.getByRole('button', { exact: true, name: 'Login' })).toBeVisible(); - await expect(page).toHaveURL(/\/next\/login(?:[?#]|$)/); + await expect(page).toHaveURL(/\/login(?:[?#]|$)/); }); } finally { await authenticationContext.close(); @@ -55,7 +55,7 @@ test('user can recover from a failed login, restore the session, and log out', a test('login restores the full notification settings link and selected project', async ({ browser, e2eApi, e2eScenario, e2eSecondaryProject }) => { const context = await browser.newContext({ baseURL: e2eApi.environment.appUrl, ignoreHTTPSErrors: true }); const page = await context.newPage(); - const destination = `/next/account/notifications?project=${e2eSecondaryProject.projectId}&from=email%2Bnotification%26settings#project-notifications`; + const destination = `/account/notifications?project=${e2eSecondaryProject.projectId}&from=email%2Bnotification%26settings#project-notifications`; try { await test.step('preserve the complete destination when authentication is required', async () => { @@ -95,7 +95,7 @@ test('login restores a shared stack event link after sign-in and session expiry' }); const context = await browser.newContext({ baseURL: e2eApi.environment.appUrl, ignoreHTTPSErrors: true }); const page = await context.newPage(); - const destination = `/next/stack/${event.stack_id}/event/${event.id}?from=shared%2Bevent#event-details`; + const destination = `/stack/${event.stack_id}/event/${event.id}?from=shared%2Bevent#event-details`; try { await test.step('preserve the shared event destination when authentication is required', async () => { diff --git a/src/Exceptionless.Web/ClientApp/e2e/tests/chart-refresh.e2e.ts b/src/Exceptionless.Web/ClientApp/e2e/tests/chart-refresh.e2e.ts index 3972643a42..7381fa9c06 100644 --- a/src/Exceptionless.Web/ClientApp/e2e/tests/chart-refresh.e2e.ts +++ b/src/Exceptionless.Web/ClientApp/e2e/tests/chart-refresh.e2e.ts @@ -19,9 +19,9 @@ test('dashboard charts stay mounted while list data refreshes', async ({ e2eApi, ); // Act & Assert: the helper refreshes each dashboard and checks that its chart stays mounted. - await verifyChartRefresh(page, '/next/stack/all', (route) => isOrganizationEventListRequest(route, organizationId!, 'stack_frequent')); - await verifyChartRefresh(page, '/next/event/all', (route) => isOrganizationEventListRequest(route, organizationId!, 'summary')); - await verifyChartRefresh(page, '/next/sessions/all', (request) => { + await verifyChartRefresh(page, '/stack/all', (route) => isOrganizationEventListRequest(route, organizationId!, 'stack_frequent')); + await verifyChartRefresh(page, '/event/all', (route) => isOrganizationEventListRequest(route, organizationId!, 'summary')); + await verifyChartRefresh(page, '/sessions/all', (request) => { return new URL(request.url()).pathname === `/api/v2/organizations/${organizationId}/events/sessions`; }); }); diff --git a/src/Exceptionless.Web/ClientApp/e2e/tests/event-effects-chaos.e2e.ts b/src/Exceptionless.Web/ClientApp/e2e/tests/event-effects-chaos.e2e.ts index 5d87517916..fde380a715 100644 --- a/src/Exceptionless.Web/ClientApp/e2e/tests/event-effects-chaos.e2e.ts +++ b/src/Exceptionless.Web/ClientApp/e2e/tests/event-effects-chaos.e2e.ts @@ -67,7 +67,7 @@ test('event list and detail effects stay bounded through paging and background c await test.step('load the Events list with one page of results', async () => { const response = page.waitForResponse((candidate) => isEventListResponse(candidate, e2eScenario.organizationId)); - await page.goto('/next/event?filter=type%3Aerror&limit=5'); + await page.goto('/event?filter=type%3Aerror&limit=5'); const listResponse = await response; expect(listResponse.ok()).toBe(true); const events = (await listResponse.json()) as { id?: string }[]; @@ -187,7 +187,7 @@ test('event list and detail effects stay bounded through paging and background c await measureAction(diagnostics, 'event detail alias route remounts', async () => { for (let index = 0; index < 5; index++) { - await page.goto(`/next/event/${journey.eventId}`); + await page.goto(`/event/${journey.eventId}`); await expect(page.getByRole('tab', { name: 'Overview' })).toBeVisible(); } }); @@ -196,7 +196,7 @@ test('event list and detail effects stay bounded through paging and background c await measureAction(diagnostics, 'stack detail discovery route remounts', async () => { for (let index = 0; index < 5; index++) { - await page.goto(`/next/stack/${journey.stackId}`); + await page.goto(`/stack/${journey.stackId}`); await expect(page.getByRole('tab', { name: 'Overview' })).toBeVisible(); } }); @@ -206,7 +206,7 @@ test('event list and detail effects stay bounded through paging and background c await measureAction(diagnostics, 'canonical stack event route remounts', async () => { for (let index = 0; index < 5; index++) { - await page.goto(`/next/stack/${journey.stackId}/event/${journey.eventId}`); + await page.goto(`/stack/${journey.stackId}/event/${journey.eventId}`); await expect(page.getByRole('tab', { name: 'Overview' })).toBeVisible(); } }); diff --git a/src/Exceptionless.Web/ClientApp/e2e/tests/event-load-recovery.e2e.ts b/src/Exceptionless.Web/ClientApp/e2e/tests/event-load-recovery.e2e.ts index 1e13225f5d..28f7ea687c 100644 --- a/src/Exceptionless.Web/ClientApp/e2e/tests/event-load-recovery.e2e.ts +++ b/src/Exceptionless.Web/ClientApp/e2e/tests/event-load-recovery.e2e.ts @@ -25,7 +25,7 @@ test('operator can refresh Events after a transient load failure', async ({ e2eA }); await test.step('encounter a transient Events request failure', async () => { - await page.goto(`/next/event?reference=${encodeURIComponent(e2eScenario.referenceId)}&time=all`); + await page.goto(`/event?reference=${encodeURIComponent(e2eScenario.referenceId)}&time=all`); await expect.poll(() => eventsRequestFailureCount).toBeGreaterThan(0); const refreshButton = page.getByTitle('Refresh results'); diff --git a/src/Exceptionless.Web/ClientApp/e2e/tests/event-visibility.e2e.ts b/src/Exceptionless.Web/ClientApp/e2e/tests/event-visibility.e2e.ts index 9329442480..338dbf352f 100644 --- a/src/Exceptionless.Web/ClientApp/e2e/tests/event-visibility.e2e.ts +++ b/src/Exceptionless.Web/ClientApp/e2e/tests/event-visibility.e2e.ts @@ -12,7 +12,7 @@ test('new user can send an event and find it in primary views @signup', async ({ const url = new URL(response.url()); return url.pathname === `/api/v2/organizations/${e2eScenario.organizationId}/events` && url.searchParams.get('mode') === 'summary'; }); - await page.goto('/next/event'); + await page.goto('/event'); const response = await initialList; expect(response.ok()).toBe(true); expect(await response.json()).toEqual([]); diff --git a/src/Exceptionless.Web/ClientApp/e2e/tests/exie-full-page.e2e.ts b/src/Exceptionless.Web/ClientApp/e2e/tests/exie-full-page.e2e.ts index 7e471f43bb..183b7c6bb0 100644 --- a/src/Exceptionless.Web/ClientApp/e2e/tests/exie-full-page.e2e.ts +++ b/src/Exceptionless.Web/ClientApp/e2e/tests/exie-full-page.e2e.ts @@ -16,7 +16,7 @@ test('Exie opens from navigation and expands without losing the conversation', a }); }); - await page.goto('/next/event?filter=status%3Aopen'); + await page.goto('/event?filter=status%3Aopen'); await expect(page.getByText(e2eScenario.organizationName, { exact: true })).toBeVisible(); await test.step('open the full-page chat without rendering the outgoing page in the Exie layout', async () => { @@ -43,7 +43,7 @@ test('Exie opens from navigation and expands without losing the conversation', a await page.getByRole('link', { exact: true, name: 'Exie' }).click(); - await expect(page).toHaveURL(/\/next\/exie(?:[?#]|$)/); + await expect(page).toHaveURL(/\/exie(?:[?#]|$)/); await expect(page.locator('[data-assistant-page]')).toBeVisible(); await expect(page.getByRole('log', { name: 'Conversation with Exie' })).toBeVisible(); await expect(page.getByRole('button', { name: 'Clear conversation' })).toBeDisabled(); @@ -53,20 +53,20 @@ test('Exie opens from navigation and expands without losing the conversation', a await test.step('expand the side panel and retain its conversation and source URL', async () => { await page.getByRole('link', { name: 'Collapse Exie to side panel' }).click(); - await expect(page).toHaveURL(/\/next\/stack\/all(?:[?#]|$)/); + await expect(page).toHaveURL(/\/stack\/all(?:[?#]|$)/); await expect(page.locator('[data-assistant-panel]')).toBeVisible(); await page.getByRole('button', { name: 'Close Exie' }).click(); - await page.goto('/next/stack?filter=status%3Aopen'); + await page.goto('/stack?filter=status%3Aopen'); await page.getByRole('button', { name: 'Open Exie' }).click(); await page.getByRole('textbox', { name: 'Message Exie' }).fill('Keep this conversation when I expand it.'); await page.getByRole('button', { name: 'Send message' }).click(); await expect(page.getByText('This conversation followed you into the full-page chat.')).toBeVisible(); await page.getByRole('link', { name: 'Expand Exie to full page' }).click(); - await expect(page).toHaveURL(/\/next\/exie\?/); + await expect(page).toHaveURL(/\/exie\?/); const expandedUrl = new URL(page.url()); - expect(expandedUrl.searchParams.get('from')).toBe('/next/stack?filter=status%3Aopen'); + expect(expandedUrl.searchParams.get('from')).toBe('/stack?filter=status%3Aopen'); const fullPageChat = page.locator('[data-assistant-page]'); await expect(fullPageChat).toBeVisible(); diff --git a/src/Exceptionless.Web/ClientApp/e2e/tests/filter-builder.e2e.ts b/src/Exceptionless.Web/ClientApp/e2e/tests/filter-builder.e2e.ts index 55aabcfd50..d50d5d758d 100644 --- a/src/Exceptionless.Web/ClientApp/e2e/tests/filter-builder.e2e.ts +++ b/src/Exceptionless.Web/ClientApp/e2e/tests/filter-builder.e2e.ts @@ -13,7 +13,7 @@ test('events filter builder applies, persists, and clears a reference filter', a }); await test.step('apply a reference filter through the filter builder', async () => { - await page.goto('/next/event?time=all'); + await page.goto('/event?time=all'); await expect(getVisibleText(page, journey.message)).toBeVisible({ timeout: 30_000 }); await setReferenceFilter(page, missingReference); diff --git a/src/Exceptionless.Web/ClientApp/e2e/tests/invitation-acceptance.e2e.ts b/src/Exceptionless.Web/ClientApp/e2e/tests/invitation-acceptance.e2e.ts index ebf46ecc9e..9403b544ed 100644 --- a/src/Exceptionless.Web/ClientApp/e2e/tests/invitation-acceptance.e2e.ts +++ b/src/Exceptionless.Web/ClientApp/e2e/tests/invitation-acceptance.e2e.ts @@ -11,7 +11,7 @@ test('invited user can accept an organization invitation @signup', async ({ brow try { await test.step('invite a user through organization settings', async () => { - await page.goto(`/next/organization/${e2eScenario.organizationId}/users`); + await page.goto(`/organization/${e2eScenario.organizationId}/users`); await page.getByTitle('Invite User').click(); const dialog = page.getByRole('alertdialog', { name: 'Invite User' }); @@ -21,17 +21,19 @@ test('invited user can accept an organization invitation @signup', async ({ brow await expect(page.getByText('User invited successfully')).toBeVisible(); }); - const inviteToken = await test.step('read the invitation from local mail', async () => { - return await e2eApi.pollForMailToken(invitedEmail, 'signup'); + const inviteLink = await test.step('read the invitation from local mail', async () => { + return await e2eApi.pollForMailLink(invitedEmail, 'signup'); }); + const inviteToken = new URL(inviteLink).searchParams.get('token')!; + await test.step('sign up through the invitation route', async () => { const invitedContext = await browser.newContext({ baseURL: e2eApi.environment.appUrl, ignoreHTTPSErrors: true }); const invitedPage = await invitedContext.newPage(); try { - await invitedPage.goto(`/next/signup?token=${encodeURIComponent(inviteToken)}`); - await expect(invitedPage.getByRole('link', { name: 'Log In' })).toHaveAttribute('href', `/next/login?token=${encodeURIComponent(inviteToken)}`); + await invitedPage.goto(inviteLink); + await expect(invitedPage.getByRole('link', { name: 'Log In' })).toHaveAttribute('href', `/login?token=${encodeURIComponent(inviteToken)}`); await invitedPage.getByLabel('Name', { exact: true }).fill(`Invited User ${e2eScenario.run}`); await invitedPage.getByLabel('Email', { exact: true }).fill(invitedEmail); await waitForEmailValidation(invitedPage); @@ -47,7 +49,7 @@ test('invited user can accept an organization invitation @signup', async ({ brow expect((await signupResponse).ok()).toBe(true); invitedUserToken = await getUserToken(invitedPage); - await expect(invitedPage).toHaveURL(/\/next\/project\/add(?:[?#]|$)/, { timeout: 30_000 }); + await expect(invitedPage).toHaveURL(/\/project\/add(?:[?#]|$)/, { timeout: 30_000 }); await e2eApi.waitForOrganizationListed(invitedUserToken, e2eScenario.organizationId, 60_000); await invitedPage.reload(); await expect(invitedPage.getByRole('heading', { name: 'Add Project' })).toBeVisible(); @@ -105,11 +107,11 @@ test('existing invited user can accept an organization invitation when logging i return response.request().method() === 'GET' && url.pathname.endsWith('/api/v2/auth/logout'); }); - await invitedPage.goto(`/next/login?token=${encodeURIComponent(inviteToken)}`); + await invitedPage.goto(`/login?token=${encodeURIComponent(inviteToken)}`); expect((await logoutResponse).ok()).toBe(true); await expect(invitedPage.getByRole('link', { name: 'Start a free trial' })).toHaveAttribute( 'href', - `/next/signup?token=${encodeURIComponent(inviteToken)}` + `/signup?token=${encodeURIComponent(inviteToken)}` ); await invitedPage.getByLabel('Email', { exact: true }).fill(invitedEmail); await invitedPage.getByPlaceholder('Enter password').fill(E2E_TEST_PASSWORD); diff --git a/src/Exceptionless.Web/ClientApp/e2e/tests/legacy-links.e2e.ts b/src/Exceptionless.Web/ClientApp/e2e/tests/legacy-links.e2e.ts new file mode 100644 index 0000000000..e8b784500b --- /dev/null +++ b/src/Exceptionless.Web/ClientApp/e2e/tests/legacy-links.e2e.ts @@ -0,0 +1,136 @@ +import { expect, test } from '../fixtures/e2e-test'; +import { seedRepresentativeEvent } from '../support/event-data'; + +test.skip(process.env.E2E_ENV === 'production', 'Cutover coverage targets the local app.'); + +test('primary old links retain the session and require confirmation before applying stack actions', async ({ e2eApi, e2eScenario, page }) => { + const event = await seedRepresentativeEvent(e2eApi, e2eScenario.userToken, e2eScenario); + const mutations: string[] = []; + page.on('request', (request) => { + const path = new URL(request.url()).pathname; + if ((path.includes('/stacks/') && request.method() !== 'GET') || path.endsWith('/auth/logout')) mutations.push(path); + }); + + for (const link of [`/event/${event.id}`, `/#!/event/by-ref/${e2eScenario.referenceId}`]) { + await page.goto(link); + await expect(page).toHaveURL(new RegExp(`/stack/${event.stack_id}/event/${event.id}$`)); + await expect(page.getByText(e2eScenario.message, { exact: true }).filter({ visible: true }).first()).toBeVisible(); + expect(await page.evaluate(() => localStorage.getItem('satellizer_token'))).toBe(e2eScenario.userToken); + } + for (const [link, heading] of [ + [`/stack/${event.stack_id}/mark-fixed`, 'Mark Stack As Fixed'], + [`/#/stack/${event.stack_id}/ignored`, 'Ignore Stack'], + [`/stack/${event.stack_id}/discarded`, 'Discard Stack'] + ]) { + await page.goto(link); + const dialog = page.getByRole('alertdialog'); + await expect(dialog.getByRole('heading', { exact: true, name: heading })).toBeVisible(); + await expect(dialog.getByText(e2eScenario.message, { exact: true })).toBeVisible(); + expect(mutations).toEqual([]); + await dialog.getByRole('button', { exact: true, name: 'Cancel' }).click(); + await expect(page).toHaveURL(new RegExp(`/stack/${event.stack_id}/event/${event.id}$`)); + await page.reload(); + await expect(page.getByRole('button', { exact: true, name: 'Open' })).toBeVisible(); + await expect(dialog).not.toBeVisible(); + expect(await page.evaluate(() => localStorage.getItem('satellizer_token'))).toBe(e2eScenario.userToken); + } + expect(mutations).toEqual([]); +}); + +test('old project and organization dashboards select the destination organization', async ({ e2eApi, e2eScenario, e2eSecondaryOrganization, page }) => { + await seedRepresentativeEvent(e2eApi, e2eScenario.userToken, e2eSecondaryOrganization); + await page.goto('/stack/all'); + await expect(page.getByRole('heading', { name: 'All' })).toBeVisible(); + + await page.goto(`/#!/project/${e2eSecondaryOrganization.projectId}/error/timeline?time=all`); + await expect.poll(() => new URL(page.url()).pathname).toBe('/event'); + expect(new URL(page.url()).searchParams.get('project')).toBe(e2eSecondaryOrganization.projectId); + await expect(page.getByText(e2eSecondaryOrganization.message, { exact: true }).filter({ visible: true }).first()).toBeVisible(); + expect(await page.evaluate(() => JSON.parse(localStorage.getItem('organization') ?? 'null'))).toBe(e2eSecondaryOrganization.organizationId); + + await page.goto(`/organization/${e2eScenario.organizationId}/frequent?time=all`); + await expect.poll(() => new URL(page.url()).pathname).toBe('/stack'); + expect(await page.evaluate(() => JSON.parse(localStorage.getItem('organization') ?? 'null'))).toBe(e2eScenario.organizationId); + + await page.goto(`/#!/organization/${e2eScenario.organizationId}/upgrade?source=email`); + await expect(page).toHaveURL(`/organization/${e2eScenario.organizationId}/billing?source=email&changePlan=true`); + await expect(page.getByRole('dialog').getByRole('heading', { exact: true, name: 'Manage subscription' })).toBeVisible(); +}); + +test('OAuth popup responses remain readable without signing out the existing session', async ({ e2eScenario, page }) => { + const logouts: string[] = []; + page.on('request', (request) => { + if (new URL(request.url()).pathname.endsWith('/auth/logout')) logouts.push(request.url()); + }); + for (const callback of ['/?code=test-code&state=test-state', '/?error=access_denied&state=test-state', '/#access_token=test-token&state=test-state']) { + await page.goto(callback); + await expect(page.getByText('Completing sign in…')).toBeVisible(); + expect(new URL(page.url()).pathname + new URL(page.url()).search + new URL(page.url()).hash).toBe(callback); + expect(await page.evaluate(() => localStorage.getItem('satellizer_token'))).toBe(e2eScenario.userToken); + } + expect(logouts).toEqual([]); +}); + +test.describe('in-app legacy navigation', () => { + test.use({ e2eUseInvitedUser: true }); + + test('Back normalizes an existing legacy history entry without losing Forward', async ({ e2eScenario, page }) => { + await page.goto('/stack/all'); + await expect(page.getByRole('heading', { exact: true, name: 'All' })).toBeVisible(); + await page.goto(`/account/notifications?project=${e2eScenario.projectId}`); + await expect(page.getByRole('heading', { exact: true, name: 'Project Notifications' })).toBeVisible(); + const destination = page.url(); + + await page.evaluate((oldUrl) => { + window.history.replaceState(window.history.state, '', oldUrl); + const link = document.createElement('a'); + link.href = '/project/list'; + link.textContent = 'Open project list'; + document.querySelector('main')!.prepend(link); + }, `/account/manage?projectId=${e2eScenario.projectId}&tab=notifications`); + await page.getByRole('link', { exact: true, name: 'Open project list' }).click(); + await expect(page).toHaveURL(/\/project\/list$/); + + await page.goBack(); + await expect(page).toHaveURL(destination); + await expect(page.getByRole('heading', { exact: true, name: 'Project Notifications' })).toBeVisible(); + await page.goForward(); + await expect(page).toHaveURL(/\/project\/list$/); + await page.goBack(); + await expect(page).toHaveURL(destination); + await page.goBack(); + await expect(page).toHaveURL(/\/stack\/all$/); + expect(await page.evaluate(() => localStorage.getItem('satellizer_token'))).toBe(e2eScenario.userToken); + }); + + test('following a historical hash settings link preserves browser back and forward navigation', async ({ e2eScenario, page }) => { + await page.goto('/stack/all'); + await expect(page.getByRole('heading', { exact: true, name: 'All' })).toBeVisible(); + await page.goto('/project/list'); + await expect(page.getByRole('heading', { exact: true, name: 'Projects' })).toBeVisible(); + + // Exercise a historical saved link through the app router, without reloading the document. + await page.evaluate((projectId) => { + const link = document.createElement('a'); + link.href = `/#!/account/manage?projectId=${projectId}&tab=notifications&from=legacy-link`; + link.textContent = 'Historical notification settings'; + document.querySelector('main')!.prepend(link); + }, e2eScenario.projectId); + await page.getByRole('link', { exact: true, name: 'Historical notification settings' }).click(); + + await expect(page.getByRole('heading', { exact: true, name: 'Project Notifications' })).toBeVisible(); + await expect(page.getByRole('button', { exact: true, name: e2eScenario.projectName })).toBeVisible(); + const destination = page.url(); + expect(new URL(destination).pathname).toBe('/account/notifications'); + expect(new URL(destination).searchParams.get('from')).toBe('legacy-link'); + + await page.goBack(); + await expect(page).toHaveURL(/\/project\/list$/); + await expect(page.getByRole('heading', { exact: true, name: 'Projects' })).toBeVisible(); + + await page.goForward(); + await expect(page).toHaveURL(destination); + await expect(page.getByRole('heading', { exact: true, name: 'Project Notifications' })).toBeVisible(); + expect(await page.evaluate(() => localStorage.getItem('satellizer_token'))).toBe(e2eScenario.userToken); + }); +}); diff --git a/src/Exceptionless.Web/ClientApp/e2e/tests/list-query-cache.e2e.ts b/src/Exceptionless.Web/ClientApp/e2e/tests/list-query-cache.e2e.ts index 797ae09030..8c056c7508 100644 --- a/src/Exceptionless.Web/ClientApp/e2e/tests/list-query-cache.e2e.ts +++ b/src/Exceptionless.Web/ClientApp/e2e/tests/list-query-cache.e2e.ts @@ -1,6 +1,7 @@ import type { Page, Request } from '@playwright/test'; import { expect, test } from '../fixtures/e2e-test'; +import { installWebSocketTestHarness } from '../support/web-socket'; interface RequestCounts { eventList: number; @@ -11,6 +12,9 @@ interface RequestCounts { } test('stack and event queries reuse fresh parameterized data during in-app navigation', async ({ e2eApi, page }) => { + // Measure navigation cache reuse without lazy saved-view creation or other server + // broadcasts invalidating the data. Live update behavior has separate coverage. + await installWebSocketTestHarness(page, { ignoreServerMessages: true }); const userToken = await e2eApi.login(); const organizations = await e2eApi.getOrganizations(userToken); const organizationId = organizations[0]?.id; @@ -37,7 +41,7 @@ test('stack and event queries reuse fresh parameterized data during in-app navig runtimeErrors.push(message.text()); } }); - page.on('pageerror', (error) => runtimeErrors.push(error.stack ?? error.message)); + page.on('pageerror', (error) => runtimeErrors.push(error.stack || error.message)); page.on('request', (request) => recordListRequest(requestCounts, request)); page.on('requestfailed', (request) => { if (isApiRequest(request)) { @@ -88,34 +92,31 @@ function isListRequest(request: Request): boolean { async function navigateToList(page: Page, name: 'Events' | 'Stacks'): Promise { if (page.url() === 'about:blank') { - await page.goto(`/next/${name.toLowerCase().replace(/s$/, '')}/all`); + await page.goto(`/${name.toLowerCase().replace(/s$/, '')}/all`); } else { - const directLink = page.getByRole('link', { exact: true, name }); - if ((await directLink.count()) > 0) { - await directLink.click(); - } else { - const allLink = page.locator(`a[href="/next/${name.toLowerCase().replace(/s$/, '')}/all"]`); - if (!(await allLink.isVisible())) { - await page.getByRole('button', { exact: true, name }).click(); - } - - await allLink.click(); + // Predefined views turn the temporary direct link into a group after loading. + // Wait for that group's All link instead of choosing a transient control. + const allLink = page.locator(`a[href="/${name.toLowerCase().replace(/s$/, '')}/all"]`); + if (!(await allLink.isVisible())) { + await page.getByRole('button', { exact: true, name }).click(); } + + await allLink.click(); } const path = name.toLowerCase().replace(/s$/, ''); - await expect(page).toHaveURL(new RegExp(`/next/${path}(?:/all)?(?:[?#]|$)`)); + await expect(page).toHaveURL(new RegExp(`/${path}(?:/all)?(?:[?#]|$)`)); await waitForListRefresh(page); } async function navigateToStackView(page: Page, name: string, slug: string): Promise { - const link = page.locator(`a[href="/next/stack/${slug}"]`); + const link = page.locator(`a[href="/stack/${slug}"]`); if (!(await link.isVisible())) { await page.getByRole('button', { exact: true, name: 'Stacks' }).click(); } await link.click(); - await expect(page).toHaveURL(new RegExp(`/next/stack/${slug}(?:[?#]|$)`)); + await expect(page).toHaveURL(new RegExp(`/stack/${slug}(?:[?#]|$)`)); await expect(page.getByRole('heading', { exact: true, name })).toBeVisible(); await waitForListRefresh(page); } diff --git a/src/Exceptionless.Web/ClientApp/e2e/tests/migration-rerun.e2e.ts b/src/Exceptionless.Web/ClientApp/e2e/tests/migration-rerun.e2e.ts index b32c518e49..9924095cac 100644 --- a/src/Exceptionless.Web/ClientApp/e2e/tests/migration-rerun.e2e.ts +++ b/src/Exceptionless.Web/ClientApp/e2e/tests/migration-rerun.e2e.ts @@ -21,7 +21,7 @@ test('global admin can open the guarded migration rerun confirmation', async ({ }); }); - await page.goto('/next/system/migrations'); + await page.goto('/system/migrations'); await expect(page.getByRole('heading', { name: 'System Administration' })).toBeVisible(); await expect(page.getByText(e2eScenario.organizationName, { exact: false }).first()).toBeVisible(); diff --git a/src/Exceptionless.Web/ClientApp/e2e/tests/navbar-layout.e2e.ts b/src/Exceptionless.Web/ClientApp/e2e/tests/navbar-layout.e2e.ts index a2bbfb7ddf..b975bb9730 100644 --- a/src/Exceptionless.Web/ClientApp/e2e/tests/navbar-layout.e2e.ts +++ b/src/Exceptionless.Web/ClientApp/e2e/tests/navbar-layout.e2e.ts @@ -19,7 +19,7 @@ test('desktop navbar logo stays within the expanded sidebar', async ({ page }) = }); await page.route('**/api/v2/**', fulfillAppShellRequest); - await page.goto('/next/stack'); + await page.goto('/stack'); const logo = page.getByRole('link', { name: 'Exceptionless Logo' }).locator('img:visible'); const sidebar = page.locator('[data-slot="sidebar-container"]'); diff --git a/src/Exceptionless.Web/ClientApp/e2e/tests/oauth-applications.e2e.ts b/src/Exceptionless.Web/ClientApp/e2e/tests/oauth-applications.e2e.ts index 15255b9c09..617123bbd2 100644 --- a/src/Exceptionless.Web/ClientApp/e2e/tests/oauth-applications.e2e.ts +++ b/src/Exceptionless.Web/ClientApp/e2e/tests/oauth-applications.e2e.ts @@ -42,7 +42,7 @@ test('OAuth applications default to authorized and expose configuration and orga }); const initialRequest = page.waitForRequest((request) => request.url().includes('/api/v2/admin/oauth-applications?')); - await page.goto('/next/system/oauth-applications'); + await page.goto('/system/oauth-applications'); const params = new URL((await initialRequest).url()).searchParams; expect(params.get('authorized')).toBe('true'); expect(params.get('sort')).toBe('-updated_utc'); @@ -62,11 +62,11 @@ test('OAuth applications default to authorized and expose configuration and orga await expect(page.getByRole('button', { name: /copy/i })).toHaveCount(0); await expect(page.getByRole('link', { exact: true, name: 'Edit application' })).toHaveAttribute( 'href', - `/next/system/oauth-applications/${authorizedApplication.id}` + `/system/oauth-applications/${authorizedApplication.id}` ); await expect(page.getByRole('link', { exact: true, name: 'Second authorized organization' })).toHaveAttribute( 'href', - '/next/organization/000000000000000000000103/manage' + '/organization/000000000000000000000103/manage' ); await page.screenshot({ fullPage: true, path: test.info().outputPath('oauth-application-details.png') }); @@ -88,5 +88,5 @@ test('OAuth applications default to authorized and expose configuration and orga await expect(page.getByRole('button', { name: 'Filter by authorization' })).toHaveText('All applications'); await page.getByRole('link', { exact: true, name: e2eScenario.organizationName }).click(); - await expect(page).toHaveURL(new RegExp(`/next/organization/${e2eScenario.organizationId}/manage`)); + await expect(page).toHaveURL(new RegExp(`/organization/${e2eScenario.organizationId}/manage`)); }); diff --git a/src/Exceptionless.Web/ClientApp/e2e/tests/operator-navigation.e2e.ts b/src/Exceptionless.Web/ClientApp/e2e/tests/operator-navigation.e2e.ts index 789436efeb..575f7c23af 100644 --- a/src/Exceptionless.Web/ClientApp/e2e/tests/operator-navigation.e2e.ts +++ b/src/Exceptionless.Web/ClientApp/e2e/tests/operator-navigation.e2e.ts @@ -10,11 +10,11 @@ test('operator can navigate from event discovery to event and stack details thro }); await test.step('open event details from the Events table', async () => { - await page.goto('/next/stack'); + await page.goto('/stack'); await navigateToSidebarView(page, 'Events', 'Errors'); - await expect(page).toHaveURL(/\/next\/event(?:\/errors)?(?:[?#]|$)/); + await expect(page).toHaveURL(/\/event(?:\/errors)?(?:[?#]|$)/); - await page.goto(`/next/event?reference=${encodeURIComponent(journey.referenceId)}&time=all`); + await page.goto(`/event?reference=${encodeURIComponent(journey.referenceId)}&time=all`); const eventListUrl = page.url(); const eventRow = getVisibleRow(page, journey.message); await expect(eventRow).toBeVisible({ timeout: 30_000 }); @@ -58,7 +58,7 @@ test('operator can navigate from event discovery to event and stack details thro await expect(eventSheet).toBeVisible(); await eventSheet.getByRole('link', { name: 'Open details in new window' }).click(); - await expect(page).toHaveURL(new RegExp(`/next/stack/${journey.stackId}/event/${journey.eventId}(?:[?#]|$)`)); + await expect(page).toHaveURL(new RegExp(`/stack/${journey.stackId}/event/${journey.eventId}(?:[?#]|$)`)); await expect(getVisibleText(page, journey.message)).toBeVisible(); await page.goBack(); @@ -66,7 +66,7 @@ test('operator can navigate from event discovery to event and stack details thro await expect(eventSheet).toBeHidden(); await page.goForward(); - await expect(page).toHaveURL(new RegExp(`/next/stack/${journey.stackId}/event/${journey.eventId}(?:[?#]|$)`)); + await expect(page).toHaveURL(new RegExp(`/stack/${journey.stackId}/event/${journey.eventId}(?:[?#]|$)`)); await expect(getVisibleText(page, journey.message)).toBeVisible(); }); @@ -106,7 +106,7 @@ test('operator can navigate from event discovery to event and stack details thro await expect(stackSheet).toBeVisible(); await stackSheet.getByRole('link', { name: 'Open details in new window' }).click(); - await expect(page).toHaveURL(new RegExp(`/next/stack/${journey.stackId}(?:/event/${journey.eventId})?(?:[?#]|$)`)); + await expect(page).toHaveURL(new RegExp(`/stack/${journey.stackId}(?:/event/${journey.eventId})?(?:[?#]|$)`)); await expect(getVisibleText(page, journey.message)).toBeVisible(); }); }); @@ -115,8 +115,8 @@ test('a failed detail request closes the sheet without leaving a duplicate histo const journey = ExceptionlessE2EJourney.fromScenario(page, e2eApi, e2eScenario); await journey.submitRepresentativeEvent(); - await page.goto('/next/stack'); - await page.goto(`/next/event?reference=${encodeURIComponent(journey.referenceId)}&time=all`); + await page.goto('/stack'); + await page.goto(`/event?reference=${encodeURIComponent(journey.referenceId)}&time=all`); const eventRow = getVisibleRow(page, journey.message); await expect(eventRow).toBeVisible({ timeout: 30_000 }); @@ -134,5 +134,5 @@ test('a failed detail request closes the sheet without leaving a duplicate histo await expect(page.getByRole('dialog', { name: 'Event' })).toBeHidden(); await page.goBack(); - await expect(page).toHaveURL(/\/next\/stack(?:[?#]|$)/); + await expect(page).toHaveURL(/\/stack(?:[?#]|$)/); }); diff --git a/src/Exceptionless.Web/ClientApp/e2e/tests/organization-switching.e2e.ts b/src/Exceptionless.Web/ClientApp/e2e/tests/organization-switching.e2e.ts index f0e21110bb..a2e7e2156a 100644 --- a/src/Exceptionless.Web/ClientApp/e2e/tests/organization-switching.e2e.ts +++ b/src/Exceptionless.Web/ClientApp/e2e/tests/organization-switching.e2e.ts @@ -26,7 +26,7 @@ test('operator can switch organizations without leaking event data', async ({ e2 }); await test.step('show only the active organization data', async () => { - await page.goto('/next/event?time=all'); + await page.goto('/event?time=all'); await expect(getVisibleText(page, e2eScenario.message)).toBeVisible({ timeout: 30_000 }); await expect(getVisibleText(page, e2eSecondaryOrganization.message)).toBeHidden(); @@ -39,7 +39,7 @@ test('operator can switch organizations without leaking event data', async ({ e2 .poll(async () => page.evaluate(() => JSON.parse(window.localStorage.getItem('organization') ?? 'null'))) .toBe(e2eSecondaryOrganization.organizationId); - await page.goto('/next/event?time=all'); + await page.goto('/event?time=all'); await expect(getVisibleText(page, e2eSecondaryOrganization.message)).toBeVisible({ timeout: 30_000 }); await expect(getVisibleText(page, e2eScenario.message)).toBeHidden(); diff --git a/src/Exceptionless.Web/ClientApp/e2e/tests/password-recovery.e2e.ts b/src/Exceptionless.Web/ClientApp/e2e/tests/password-recovery.e2e.ts index 760d6c1d5b..81bfd692c7 100644 --- a/src/Exceptionless.Web/ClientApp/e2e/tests/password-recovery.e2e.ts +++ b/src/Exceptionless.Web/ClientApp/e2e/tests/password-recovery.e2e.ts @@ -7,25 +7,25 @@ test.use({ e2eCleanupPassword: RESET_PASSWORD, e2eInjectBrowserToken: false, e2e test('user can reset a forgotten password and log in @signup', async ({ e2eApi, e2eScenario, page }) => { await test.step('request a password reset through the UI', async () => { - await page.goto('/next/forgot-password'); + await page.goto('/forgot-password'); await page.getByLabel('Email', { exact: true }).fill(e2eScenario.email); await page.getByRole('button', { name: 'Send Reset Email' }).click(); - await expect(page).toHaveURL(/\/next\/login(?:[?#]|$)/); + await expect(page).toHaveURL(/\/login(?:[?#]|$)/); await expect(page.getByText('Please check your inbox for the password reset email.')).toBeVisible(); }); - const resetToken = await test.step('read the reset link from local mail', async () => { - return await e2eApi.pollForMailToken(e2eScenario.email, 'reset-password'); + const resetLink = await test.step('read the reset link from local mail', async () => { + return await e2eApi.pollForMailLink(e2eScenario.email, 'reset-password'); }); await test.step('change the password through the emailed route', async () => { - await page.goto(`/next/reset-password/${encodeURIComponent(resetToken)}`); + await page.goto(resetLink); await page.getByLabel('New Password', { exact: true }).fill(RESET_PASSWORD); await page.getByLabel('Confirm Password', { exact: true }).fill(RESET_PASSWORD); await page.getByRole('button', { name: 'Change Password' }).click(); - await expect(page).toHaveURL(/\/next\/login(?:[?#]|$)/); + await expect(page).toHaveURL(/\/login(?:[?#]|$)/); await expect(page.getByText('You have successfully changed your password.')).toBeVisible(); }); @@ -35,6 +35,6 @@ test('user can reset a forgotten password and log in @signup', async ({ e2eApi, await page.getByRole('button', { exact: true, name: 'Login' }).click(); await expect(page.getByRole('heading', { name: 'All' })).toBeVisible({ timeout: 30_000 }); - await expect(page).toHaveURL(/\/next\/stack\/all(?:[?#]|$)/); + await expect(page).toHaveURL(/\/stack\/all(?:[?#]|$)/); }); }); diff --git a/src/Exceptionless.Web/ClientApp/e2e/tests/product-tours.e2e.ts b/src/Exceptionless.Web/ClientApp/e2e/tests/product-tours.e2e.ts index cc0940e90d..7a04625aac 100644 --- a/src/Exceptionless.Web/ClientApp/e2e/tests/product-tours.e2e.ts +++ b/src/Exceptionless.Web/ClientApp/e2e/tests/product-tours.e2e.ts @@ -21,7 +21,7 @@ test.describe('first-run welcome', () => { await e2eApi.recordProductTour(e2eScenario.userToken, 'app-welcome'); } await mockAssistantAccess(page); - await page.goto('/next/stack'); + await page.goto('/stack'); const dismiss = page.getByRole('button', { name: dismissLabel }); await expect(dismiss).toBeVisible(); @@ -60,7 +60,7 @@ test.describe('first-run welcome', () => { // Arrange const welcome = page.getByRole('region', { name: 'Welcome to Exceptionless' }); await test.step(`show the pending welcome for ${e2eScenario.email}`, async () => { - await page.goto('/next/stack/all'); + await page.goto('/stack/all'); await expect(welcome).toBeVisible(); }); const invitationWrites: Request[] = []; @@ -86,7 +86,7 @@ test.describe('first-run welcome', () => { test('Browse Guides saves acknowledgment and opens the catalog', async ({ e2eScenario, page }, testInfo) => { // Arrange await test.step(`show the first-run prompt for ${e2eScenario.email}`, async () => { - await page.goto('/next/stack'); + await page.goto('/stack'); await expect(page.getByRole('region', { name: 'Welcome to Exceptionless' })).toBeVisible(); await expect(page.getByRole('dialog')).toBeHidden(); await page.screenshot({ path: testInfo.outputPath('welcome-desktop.png') }); @@ -117,7 +117,7 @@ test.describe('first-run welcome', () => { // Arrange await page.setViewportSize({ height: 844, width: 390 }); await page.emulateMedia({ reducedMotion: 'reduce' }); - await page.goto('/next/stack'); + await page.goto('/stack'); const welcome = page.getByRole('region', { name: 'Welcome to Exceptionless' }); await expect(welcome).toBeVisible(); @@ -138,7 +138,7 @@ test.describe('first-run welcome', () => { const persisted = page.waitForResponse(isSuccessfulTourProgress('app-welcome')); await welcome.getByRole('button', { name: 'Continue setup' }).click(); await persisted; - await expect(page).toHaveURL(new RegExp(`/next/project/(?:add|${e2eScenario.projectId}/configure)`)); + await expect(page).toHaveURL(new RegExp(`/project/(?:add|${e2eScenario.projectId}/configure)`)); await expect(welcome).toBeHidden(); }); @@ -146,7 +146,7 @@ test.describe('first-run welcome', () => { // Arrange const welcome = page.getByRole('region', { name: 'Welcome to Exceptionless' }); await test.step(`show the welcome for ${e2eScenario.email}`, async () => { - await page.goto('/next/stack'); + await page.goto('/stack'); await expect(welcome).toBeVisible(); }); const progressRoute = '**/api/v2/users/me/product-tours/app-welcome/record'; @@ -186,7 +186,7 @@ test.describe('shell and identity checkpoints', () => { // Act & Assert: each step checks a responsive or identity transition. await test.step('closing the welcome persists dismissal', async () => { // Arrange - await page.goto('/next/stack'); + await page.goto('/stack'); const welcome = page.getByRole('region', { name: 'Welcome to Exceptionless' }); await expect(welcome).toBeVisible(); const dismissed = page.waitForResponse(isSuccessfulTourProgress('app-welcome')); @@ -306,7 +306,7 @@ test.describe('shell and identity checkpoints', () => { // Act const identityTab = await page.context().newPage(); - await identityTab.goto('/next/stack'); + await identityTab.goto('/stack'); await identityTab.evaluate((organizationId) => { window.localStorage.setItem('organization', JSON.stringify(organizationId)); }, e2eSecondaryOrganization.organizationId); @@ -343,7 +343,7 @@ test.describe('shell and identity checkpoints', () => { await page.getByRole('button', { name: new RegExp(e2eScenario.userName) }).dispatchEvent('click'); await page.getByRole('menuitem', { name: 'Log Out' }).dispatchEvent('click'); // Assert - await expect(page).toHaveURL(/\/next\/login/); + await expect(page).toHaveURL(/\/login/); await expectActiveProductTour(page, false); expect(progressWrites).toHaveLength(writesBeforeLogout); @@ -357,7 +357,7 @@ for (const title of ['Explore Exceptionless', 'Create a saved view', 'Meet Exie' // Arrange await mockAssistantAccess(page); await test.step(`start ${title} for ${e2eScenario.email}`, async () => { - await page.goto('/next/event'); + await page.goto('/event'); await startTourFromCommand(page, title); }); const calloutTitle = page.locator('.driver-popover-title'); @@ -394,12 +394,12 @@ for (const [list, stepTitle, advances] of [ ] as const) { test(`the overview continues ${stepTitle} on a ${list} saved view`, async ({ e2eScenario, page }) => { // Arrange - const savedViewPath = `/next/${list}/all`; + const savedViewPath = `/${list}/all`; await page.goto(savedViewPath); await expect(page.getByRole('button', { name: new RegExp(e2eScenario.userName) })).toBeVisible(); await expect(page.locator('[data-tour="saved-view-trigger"]')).toBeVisible(); await startTourFromCommand(page, 'Explore Exceptionless'); - await expect(page).toHaveURL(/\/next\/event$/); + await expect(page).toHaveURL(/\/event$/); const callout = page.locator('.driver-popover'); const stepTitles = ['Spot repeated problems', 'See each report', 'Narrow your results']; for (let step = 0; step < advances; step++) { @@ -430,10 +430,10 @@ for (const [stepTitle, advances] of [ ] as const) { test(`the overview offers Restart when leaving ${stepTitle}`, async ({ e2eScenario, page }) => { // Arrange: start from a page without event filters or a View menu. - const projectPath = `/next/project/${e2eScenario.projectId}/manage`; + const projectPath = `/project/${e2eScenario.projectId}/manage`; await page.goto(projectPath); await startTourFromCommand(page, 'Explore Exceptionless'); - await expect(page).toHaveURL(/\/next\/event$/); + await expect(page).toHaveURL(/\/event$/); await expect(page.locator('[data-tour="event-filters"]')).toBeVisible(); const callout = page.locator('.driver-popover'); const stepTitles = ['Spot repeated problems', 'See each report', 'Narrow your results']; @@ -456,7 +456,7 @@ for (const [stepTitle, advances] of [ const restart = catalog.getByRole('button', { exact: true, name: 'Restart Explore Exceptionless' }); await expect(restart).toBeVisible(); await restart.click(); - await expect(page).toHaveURL(/\/next\/event$/); + await expect(page).toHaveURL(/\/event$/); await expect(page.locator('.driver-popover-title')).toHaveText('Spot repeated problems'); }); } @@ -466,7 +466,7 @@ test('project guide preserves the current SDK selection', async ({ e2eScenario, await page.route('**/api/v2/organizations/*/projects*', async (route) => { await route.fulfill({ json: [] }); }); - await page.goto(`/next/project/${e2eScenario.projectId}/configure?type=dotnet-legacy-mvc`); + await page.goto(`/project/${e2eScenario.projectId}/configure?type=dotnet-legacy-mvc`); await expect(page.locator('[data-tour="project-configure-platform"]')).toContainText('ASP.NET MVC'); // Act @@ -478,12 +478,12 @@ test('project guide preserves the current SDK selection', async ({ e2eScenario, await expect(page.locator('[data-tour="project-configure-platform"]')).toContainText('ASP.NET MVC'); expect(new URL(page.url()).searchParams.get('type')).toBe('dotnet-legacy-mvc'); expect(new URL(page.url()).searchParams.get('redirect')).toBe('true'); - expect(new URL(page.url()).pathname).toBe(`/next/project/${e2eScenario.projectId}/configure`); + expect(new URL(page.url()).pathname).toBe(`/project/${e2eScenario.projectId}/configure`); }); test('a saved-view guide allows submitting the form before finishing its steps', async ({ e2eScenario, page }) => { // Arrange - await page.goto('/next/event'); + await page.goto('/event'); await startTourFromCommand(page, 'Create a saved view'); const guide = page.locator('.driver-popover'); await guide.getByRole('button', { name: 'Open View' }).click(); @@ -508,9 +508,9 @@ test('domain workflows advance only on real success', async ({ e2eApi, e2eScenar // Act & Assert: each workflow below exercises and verifies its own transitions. await test.step('project configuration advances after setup and the first event', async () => { // Arrange - await page.goto('/next/stack'); + await page.goto('/stack'); await startTourFromCommand(page, 'Configure a project'); - await page.waitForURL(/\/next\/project\/(?:add|[^/]+\/configure)/); + await page.waitForURL(/\/project\/(?:add|[^/]+\/configure)/); let createdProject = false; let projectId = page.url().match(/\/project\/([^/]+)\/configure/)?.[1]; @@ -519,7 +519,7 @@ test('domain workflows advance only on real success', async ({ e2eApi, e2eScenar await expect(page.getByRole('heading', { name: 'Add Project' })).toBeVisible(); await page.getByLabel('Project Name', { exact: true }).fill(`Tour Project ${e2eScenario.run}`); await page.getByRole('button', { name: 'Continue to Client Setup' }).click(); - await page.waitForURL(/\/next\/project\/[^/]+\/configure\?redirect=true/); + await page.waitForURL(/\/project\/[^/]+\/configure\?redirect=true/); projectId = page.url().match(/\/project\/([^/]+)\/configure/)?.[1]; } else { expect(projectId).toBe(e2eScenario.projectId); @@ -572,14 +572,14 @@ test('domain workflows advance only on real success', async ({ e2eApi, e2eScenar }) ); // Assert - await expect(page).toHaveURL(/\/next\/event/); + await expect(page).toHaveURL(/\/event/); await expectActiveProductTour(page, false); await expect.poll(() => projectProgressRequests).toBe(1); await expect.poll(async () => (await e2eApi.getProject(e2eScenario.userToken, projectId!))?.is_configured).toBe(true); // Act await page.unroute(projectProgressRoute); - await page.goto(`/next/project/${projectId}/configure`); + await page.goto(`/project/${projectId}/configure`); // Assert await expectActiveProductTour(page, false); expect(projectProgressRequests).toBe(1); @@ -615,7 +615,7 @@ test('domain workflows advance only on real success', async ({ e2eApi, e2eScenar }); try { - await page.goto('/next/event'); + await page.goto('/event'); // Act await startTourFromCommand(page, 'Create a saved view'); // Assert @@ -658,7 +658,7 @@ test('domain workflows advance only on real success', async ({ e2eApi, e2eScenar projectToken: e2eScenario.projectToken, referenceId: e2eScenario.referenceId }); - await page.goto('/next/event?time=all&type=error'); + await page.goto('/event?time=all&type=error'); await expect(page.getByText(e2eScenario.message).first()).toBeVisible({ timeout: 30_000 }); // Act await startTourFromCommand(page, 'Investigate an error'); @@ -698,7 +698,7 @@ test('domain workflows advance only on real success', async ({ e2eApi, e2eScenar page.on('request', countChatRequest); try { - await page.goto('/next/stack'); + await page.goto('/stack'); // Act await startTourFromCommand(page, 'Meet Exie'); const tour = page.locator('.driver-popover'); @@ -721,7 +721,7 @@ test('the error guide keeps the start of a wide report visible on mobile', async referenceId: e2eScenario.referenceId }); await page.setViewportSize({ height: 844, width: 390 }); - await page.goto('/next/event?time=all&type=error'); + await page.goto('/event?time=all&type=error'); await expect(page.getByText(e2eScenario.message).first()).toBeVisible(); // Act @@ -741,9 +741,9 @@ for (const [stepTitle, advances] of [ ['Keep a useful view', 3] ] as const) { test(`overview restores ${stepTitle} through history with and without Search`, async ({ e2eScenario, page }) => { - await page.goto(`/next/project/${e2eScenario.projectId}/manage`); + await page.goto(`/project/${e2eScenario.projectId}/manage`); await startTourFromCommand(page, 'Explore Exceptionless'); - await expect(page).toHaveURL(/\/next\/event$/); + await expect(page).toHaveURL(/\/event$/); const guide = page.locator('.driver-popover'); for (let step = 0; step < advances; step++) { await guide.getByRole('button', { name: 'Next' }).click(); @@ -755,7 +755,7 @@ for (const [stepTitle, advances] of [ await expect(guide).toBeHidden(); await page.goForward(); - await expect(page).toHaveURL(/\/next\/event$/); + await expect(page).toHaveURL(/\/event$/); await expect(guide.getByText(stepTitle)).toBeVisible(); await test.step('restore the checkpoint after navigating with Search open', async () => { @@ -770,27 +770,27 @@ for (const [stepTitle, advances] of [ } await page.goForward(); - await expect(page).toHaveURL(/\/next\/event$/); + await expect(page).toHaveURL(/\/event$/); await expect(guide.getByText(stepTitle)).toBeVisible(); }); }); } test('overview returns to Events after navigating back to Stacks', async ({ e2eScenario, page }, testInfo) => { - await page.goto('/next/stack'); + await page.goto('/stack'); await expect(page.getByRole('button', { name: new RegExp(e2eScenario.userName) })).toBeVisible(); await startTourFromCommand(page, 'Explore Exceptionless'); const guide = page.locator('.driver-popover'); await expect(guide.getByText('Spot repeated problems')).toBeVisible(); - await expect(page).toHaveURL(/\/next\/event$/); + await expect(page).toHaveURL(/\/event$/); await page.goBack(); - await expect(page).toHaveURL(/\/next\/stack$/); + await expect(page).toHaveURL(/\/stack$/); await guide.getByRole('button', { name: 'Next' }).click(); await expect(guide.getByText('See each report')).toBeVisible(); await guide.getByRole('button', { name: 'Next' }).click(); - await expect(page).toHaveURL(/\/next\/event$/); + await expect(page).toHaveURL(/\/event$/); await expect(guide.getByText('Narrow your results')).toBeVisible(); await expectActiveProductTour(page, true); await page.screenshot({ path: testInfo.outputPath('interactive-stacks-next.png') }); @@ -799,7 +799,7 @@ test('overview returns to Events after navigating back to Stacks', async ({ e2eS test('overview navigation survives resizing between desktop and mobile', async ({ e2eScenario, page }) => { await mockAssistantAccess(page); await page.setViewportSize({ height: 900, width: 1440 }); - await page.goto('/next/stack'); + await page.goto('/stack'); expect(e2eScenario.email).toContain('@exceptionless.test'); await startTourFromCommand(page, 'Explore Exceptionless'); const tour = page.locator('.driver-popover'); @@ -825,14 +825,14 @@ test('mobile overview keeps navigation targets visible after following their lin (route) => route.fulfill({ json: [] }) ); await page.setViewportSize({ height: 900, width: 1440 }); - await page.goto(`/next/project/${e2eScenario.projectId}/manage`); + await page.goto(`/project/${e2eScenario.projectId}/manage`); await startTourFromCommand(page, 'Explore Exceptionless'); await page.setViewportSize({ height: 844, width: 390 }); const guide = page.locator('.driver-popover'); for (const [target, title, route] of [ - ['navigation-stacks', 'Spot repeated problems', /\/next\/stack$/], - ['navigation-events', 'See each report', /\/next\/event$/] + ['navigation-stacks', 'Spot repeated problems', /\/stack$/], + ['navigation-events', 'See each report', /\/event$/] ] as const) { await expect(guide.getByText(title, { exact: true })).toBeVisible(); const link = page.locator(`a[data-tour="${target}"]:visible`); @@ -849,7 +849,7 @@ test('mobile overview keeps navigation targets visible after following their lin test('overview arrows stay beside each control on desktop and mobile', async ({ e2eScenario, page }) => { // Arrange await mockAssistantAccess(page); - await page.goto('/next/stack'); + await page.goto('/stack'); expect(e2eScenario.email).toContain('@exceptionless.test'); const titles = ['Spot repeated problems', 'See each report', 'Narrow your results', 'Keep a useful view', 'Get help from Exie', 'Search and take action']; @@ -894,7 +894,7 @@ test('completion survives unavailable telemetry and session storage', async ({ e }) ); await mockAssistantAccess(page); - await page.goto('/next/stack'); + await page.goto('/stack'); // Act await startTourFromCommand(page, 'Explore Exceptionless'); for (const title of ['Spot repeated problems', 'See each report', 'Narrow your results', 'Keep a useful view', 'Get help from Exie']) { diff --git a/src/Exceptionless.Web/ClientApp/e2e/tests/project-api-key-configuration.e2e.ts b/src/Exceptionless.Web/ClientApp/e2e/tests/project-api-key-configuration.e2e.ts index f2d50411dc..166409c9cd 100644 --- a/src/Exceptionless.Web/ClientApp/e2e/tests/project-api-key-configuration.e2e.ts +++ b/src/Exceptionless.Web/ClientApp/e2e/tests/project-api-key-configuration.e2e.ts @@ -3,7 +3,7 @@ import { seedRepresentativeEvent } from '../support/event-data'; test('operator can manage project API keys and follow first-event redirect', async ({ e2eApi, e2eScenario, page }) => { await test.step('open API Keys and create another client key', async () => { - await page.goto(`/next/project/${e2eScenario.projectId}/api-keys`); + await page.goto(`/project/${e2eScenario.projectId}/api-keys`); await expect(page.getByRole('heading', { name: `${e2eScenario.projectName} Settings` })).toBeVisible(); await expect(page.getByRole('cell', { name: e2eScenario.projectToken })).toBeVisible(); @@ -14,14 +14,14 @@ test('operator can manage project API keys and follow first-event redirect', asy }); await test.step('open client setup from API Keys', async () => { - await page.locator(`a[href="/next/project/${e2eScenario.projectId}/configure"]`).filter({ hasText: 'Client setup' }).last().click(); + await page.locator(`a[href="/project/${e2eScenario.projectId}/configure"]`).filter({ hasText: 'Client setup' }).last().click(); - await expect(page).toHaveURL(new RegExp(`/next/project/${e2eScenario.projectId}/configure(?:[?#]|$)`)); + await expect(page).toHaveURL(new RegExp(`/project/${e2eScenario.projectId}/configure(?:[?#]|$)`)); await expect(page.getByRole('heading', { name: `Send Events to ${e2eScenario.projectName}` })).toBeVisible(); }); await test.step('redirect to project Events when the first event arrives', async () => { - await page.goto(`/next/project/${e2eScenario.projectId}/configure?redirect=true&type=bash`); + await page.goto(`/project/${e2eScenario.projectId}/configure?redirect=true&type=bash`); await expect(page.getByText('Waiting for your first event')).toBeVisible(); await expect(page.getByText(e2eScenario.projectToken, { exact: false }).first()).toBeVisible(); @@ -32,6 +32,6 @@ test('operator can manage project API keys and follow first-event redirect', asy referenceId: e2eScenario.referenceId }); - await expect(page).toHaveURL(new RegExp(`/next/event[?].*project=${e2eScenario.projectId}`), { timeout: 30_000 }); + await expect(page).toHaveURL(new RegExp(`/event[?].*project=${e2eScenario.projectId}`), { timeout: 30_000 }); }); }); diff --git a/src/Exceptionless.Web/ClientApp/e2e/tests/project-dropdown-layout.e2e.ts b/src/Exceptionless.Web/ClientApp/e2e/tests/project-dropdown-layout.e2e.ts index b2e15bb5eb..e021d0a838 100644 --- a/src/Exceptionless.Web/ClientApp/e2e/tests/project-dropdown-layout.e2e.ts +++ b/src/Exceptionless.Web/ClientApp/e2e/tests/project-dropdown-layout.e2e.ts @@ -14,7 +14,7 @@ test('project dropdown layout preserves its search and footer while listing many ); await page.setViewportSize({ height: 720, width: 1280 }); - await page.goto('/next/stack?time=all'); + await page.goto('/stack?time=all'); await expect(page.getByRole('heading', { name: 'Stacks' })).toBeVisible(); await expect(page.getByText('No data was found with the current filter.', { exact: true })).toBeVisible(); @@ -104,7 +104,7 @@ test('project dropdown keeps its controls visible in mobile viewports and a 200% ]) { // Arrange await page.setViewportSize({ height: viewport.height, width: viewport.width }); - await page.goto('/next/stack?time=all'); + await page.goto('/stack?time=all'); await expect(page.getByRole('heading', { name: 'Stacks' })).toBeVisible(); await expect(page.getByText('No data was found with the current filter.', { exact: true })).toBeVisible(); diff --git a/src/Exceptionless.Web/ClientApp/e2e/tests/project-scoping.e2e.ts b/src/Exceptionless.Web/ClientApp/e2e/tests/project-scoping.e2e.ts index 988c1ab8a8..7901979371 100644 --- a/src/Exceptionless.Web/ClientApp/e2e/tests/project-scoping.e2e.ts +++ b/src/Exceptionless.Web/ClientApp/e2e/tests/project-scoping.e2e.ts @@ -21,7 +21,7 @@ test('operator can scope Events to a project and clear the project filter', asyn }); await test.step('show events from both projects before scoping', async () => { - await page.goto('/next/event?time=all'); + await page.goto('/event?time=all'); await expect(getVisibleText(page, e2eScenario.message)).toBeVisible({ timeout: 30_000 }); await expect(getVisibleText(page, e2eSecondaryProject.message)).toBeVisible({ timeout: 30_000 }); @@ -81,7 +81,7 @@ test('project scope on Most Frequent Errors survives immediate reload and histor referenceId: e2eScenario.referenceId }); - await page.goto('/next/stack/most-frequent-errors'); + await page.goto('/stack/most-frequent-errors'); const stackRow = getVisibleRow(page, e2eScenario.message); await expect(stackRow).toBeVisible({ timeout: 30_000 }); await stackRow.click(); diff --git a/src/Exceptionless.Web/ClientApp/e2e/tests/rataplan-feedback.e2e.ts b/src/Exceptionless.Web/ClientApp/e2e/tests/rataplan-feedback.e2e.ts index ca4a4b242f..36a942acee 100644 --- a/src/Exceptionless.Web/ClientApp/e2e/tests/rataplan-feedback.e2e.ts +++ b/src/Exceptionless.Web/ClientApp/e2e/tests/rataplan-feedback.e2e.ts @@ -53,7 +53,7 @@ test('approved Rataplan UI feedback remains fixed', async ({ e2eApi, e2eScenario } await test.step('event details use the page scroll and preserve the selected tab when the event changes', async () => { - await page.goto(`/next/stack/${journey.stackId}/event/${firstEventId}`); + await page.goto(`/stack/${journey.stackId}/event/${firstEventId}`); const overviewTab = page.getByRole('tab', { name: 'Overview' }); await expect(overviewTab).toHaveAttribute('aria-selected', 'true'); @@ -75,7 +75,7 @@ test('approved Rataplan UI feedback remains fixed', async ({ e2eApi, e2eScenario await expect(page).not.toHaveURL(new RegExp(`/event/${firstEventId}(?:[?#]|$)`)); await expect(page.getByRole('tab', { name: 'Exception' })).toHaveAttribute('aria-selected', 'true'); - await page.goto(`/next/stack/${journey.stackId}/event/${firstEventId}`); + await page.goto(`/stack/${journey.stackId}/event/${firstEventId}`); await page.getByRole('tab', { name: 'Request' }).click(); await expect(page.getByRole('tab', { name: 'Request' })).toHaveAttribute('aria-selected', 'true'); @@ -129,7 +129,7 @@ test('approved Rataplan UI feedback remains fixed', async ({ e2eApi, e2eScenario }); await test.step('manual refresh keeps the current page', async () => { - await page.goto(`/next/stack?project=${e2eScenario.projectId}&limit=5&time=all`); + await page.goto(`/stack?project=${e2eScenario.projectId}&limit=5&time=all`); const pager = page.getByRole('navigation', { name: 'Table pagination' }); await expect(pager).toBeVisible(); await expect(pager.getByRole('button', { name: 'Rows per page' })).toContainText('5 rows'); @@ -164,20 +164,20 @@ test('approved Rataplan UI feedback remains fixed', async ({ e2eApi, e2eScenario }); await test.step('row selection clears when navigating between stack views', async () => { - await page.goto(`/next/stack/all?project=${e2eScenario.projectId}&time=all`); + await page.goto(`/stack/all?project=${e2eScenario.projectId}&time=all`); const selectedRow = page.getByRole('row').filter({ hasText: journey.message }).first(); await expect(selectedRow).toBeVisible({ timeout: 30_000 }); await selectedRow.getByRole('checkbox', { name: 'Select row' }).click(); await expect(page.getByText('1 selected', { exact: true })).toBeVisible(); await page.getByRole('link', { exact: true, name: 'Most Frequent Errors' }).filter({ visible: true }).first().click(); - await expect(page).toHaveURL(/\/next\/stack\/most-frequent-errors/); + await expect(page).toHaveURL(/\/stack\/most-frequent-errors/); await expect(page.getByText(journey.message).filter({ visible: true }).first()).toBeVisible({ timeout: 30_000 }); await expect(page.getByText('1 selected', { exact: true })).toHaveCount(0); }); await test.step('the fixed-version field has space before the dialog footer', async () => { - await page.goto(`/next/stack?filter=project:${e2eScenario.projectId}&time=all`); + await page.goto(`/stack?filter=project:${e2eScenario.projectId}&time=all`); await page.getByRole('checkbox', { name: 'Select row' }).first().click(); const bulkActionsButton = page.getByRole('button', { name: 'Actions' }); const selectionCount = page.getByText('1 selected', { exact: true }); @@ -201,7 +201,7 @@ test('approved Rataplan UI feedback remains fixed', async ({ e2eApi, e2eScenario }); await test.step('message is available as a wrappable event column', async () => { - await page.goto(`/next/stream?project=${e2eScenario.projectId}&time=all`); + await page.goto(`/stream?project=${e2eScenario.projectId}&time=all`); await expect(page.getByText(journey.message).filter({ visible: true }).first()).toBeVisible({ timeout: 30_000 }); await page.getByTitle('Manage View Settings').click(); @@ -212,7 +212,7 @@ test('approved Rataplan UI feedback remains fixed', async ({ e2eApi, e2eScenario }); await test.step('renaming a project refreshes cached stack summaries', async () => { - const stackUrl = `/next/stack?filter=project:${e2eScenario.projectId}&time=all`; + const stackUrl = `/stack?filter=project:${e2eScenario.projectId}&time=all`; await page.goto(stackUrl); await expect(page.getByText(journey.message).filter({ visible: true }).first()).toBeVisible({ timeout: 30_000 }); @@ -291,7 +291,7 @@ test('approved Rataplan UI feedback remains fixed', async ({ e2eApi, e2eScenario await expect(page.getByRole('cell', { name: journey.projectName }).first()).toBeVisible(); - await page.goto(`/next/project/${e2eScenario.projectId}/manage`); + await page.goto(`/project/${e2eScenario.projectId}/manage`); const renamedProject = `${journey.projectName} Renamed`; const updateResponse = page.waitForResponse( (response) => response.url().includes(`/api/v2/projects/${e2eScenario.projectId}`) && response.request().method() === 'PATCH' diff --git a/src/Exceptionless.Web/ClientApp/e2e/tests/resource-organization-navigation.e2e.ts b/src/Exceptionless.Web/ClientApp/e2e/tests/resource-organization-navigation.e2e.ts index 53e7b70c91..8bd00c3740 100644 --- a/src/Exceptionless.Web/ClientApp/e2e/tests/resource-organization-navigation.e2e.ts +++ b/src/Exceptionless.Web/ClientApp/e2e/tests/resource-organization-navigation.e2e.ts @@ -4,7 +4,7 @@ import { expect, test } from '../fixtures/e2e-test'; import { seedRepresentativeEvent } from '../support/event-data'; test('organization deep links select the destination organization', async ({ e2eScenario, e2eSecondaryOrganization, page }) => { - await page.goto(`/next/organization/${e2eSecondaryOrganization.organizationId}/manage?from=link#settings`); + await page.goto(`/organization/${e2eSecondaryOrganization.organizationId}/manage?from=link#settings`); await expect(page).toHaveURL(new RegExp(`/organization/${e2eSecondaryOrganization.organizationId}/manage\\?from=link#settings$`)); await expect(page.getByRole('heading', { exact: true, name: `${e2eSecondaryOrganization.organizationName} Settings` })).toBeVisible(); @@ -18,26 +18,26 @@ for (const resource of ['project', 'stack', 'event', 'stack event', 'project sta let href: string; switch (resource) { case 'event': - href = `/next/event/${event!.id}`; + href = `/event/${event!.id}`; break; case 'mismatched project stack': - href = `/next/project/${e2eScenario.projectId}/stacks/${event!.stack_id}`; + href = `/project/${e2eScenario.projectId}/stacks/${event!.stack_id}`; break; case 'project': - href = `/next/project/${e2eSecondaryOrganization.projectId}/settings`; + href = `/project/${e2eSecondaryOrganization.projectId}/settings`; break; case 'project stack': - href = `/next/project/${e2eSecondaryOrganization.projectId}/stacks/${event!.stack_id}`; + href = `/project/${e2eSecondaryOrganization.projectId}/stacks/${event!.stack_id}`; break; case 'stack': - href = `/next/stack/${event!.stack_id}`; + href = `/stack/${event!.stack_id}`; break; case 'stack event': - href = `/next/stack/${event!.stack_id}/event/${event!.id}`; + href = `/stack/${event!.stack_id}/event/${event!.id}`; break; } - await page.goto('/next/stack/all'); + await page.goto('/stack/all'); await expectOrganization(page, e2eScenario.organizationId); await page.goto(href); await expectOrganization(page, e2eSecondaryOrganization.organizationId); @@ -59,7 +59,7 @@ for (const resource of ['project', 'stack', 'event', 'stack event', 'project sta await page.getByRole('button').filter({ hasText: e2eSecondaryOrganization.organizationName }).filter({ visible: true }).first().click(); await page.getByRole('menuitem').filter({ hasText: e2eScenario.organizationName }).click(); await expectOrganization(page, e2eScenario.organizationId); - await expect(page).toHaveURL(/\/next\/stack\/all$/); + await expect(page).toHaveURL(/\/stack\/all$/); }); } @@ -104,14 +104,14 @@ test('Exie usage organization links switch context and support browser history', }); }); - await page.goto('/next/system/exie'); + await page.goto('/system/exie'); await page.getByRole('link', { exact: true, name: e2eSecondaryOrganization.organizationName }).click(); await expect(page.getByRole('heading', { exact: true, name: `${e2eSecondaryOrganization.organizationName} Settings` })).toBeVisible(); await expectOrganization(page, e2eSecondaryOrganization.organizationId); await expect(page).toHaveURL(new RegExp(`/organization/${e2eSecondaryOrganization.organizationId}/manage$`)); await test.info().attach('organization-link-destination', { body: await page.screenshot(), contentType: 'image/png' }); - await page.goto(`/next/organization/${e2eScenario.organizationId}/manage`); + await page.goto(`/organization/${e2eScenario.organizationId}/manage`); await expectOrganization(page, e2eScenario.organizationId); await expect(page.getByRole('heading', { exact: true, name: `${e2eScenario.organizationName} Settings` })).toBeVisible(); await page.goBack(); @@ -124,22 +124,22 @@ test('Exie usage organization links switch context and support browser history', test('organization users and billing links request the destination organization', async ({ e2eScenario, e2eSecondaryOrganization, page }) => { const usersResponse = page.waitForResponse((response) => response.url().includes(`/organizations/${e2eSecondaryOrganization.organizationId}/users`)); - await page.goto(`/next/organization/${e2eSecondaryOrganization.organizationId}/users`); + await page.goto(`/organization/${e2eSecondaryOrganization.organizationId}/users`); expect((await usersResponse).ok()).toBe(true); await expectOrganization(page, e2eSecondaryOrganization.organizationId); await expect(page.getByRole('button', { name: 'Invite User' })).toBeVisible(); const billingResponse = page.waitForResponse((response) => response.url().includes(`/organizations/${e2eScenario.organizationId}/invoices`)); - await page.goto(`/next/organization/${e2eScenario.organizationId}/billing`); + await page.goto(`/organization/${e2eScenario.organizationId}/billing`); expect([200, 404]).toContain((await billingResponse).status()); await expectOrganization(page, e2eScenario.organizationId); await expect(page).toHaveURL(new RegExp(`/organization/${e2eScenario.organizationId}/billing$`)); }); test('organization project links switch before redirecting to the project list', async ({ e2eSecondaryOrganization, page }) => { - await page.goto(`/next/organization/${e2eSecondaryOrganization.organizationId}/projects`); + await page.goto(`/organization/${e2eSecondaryOrganization.organizationId}/projects`); await expectOrganization(page, e2eSecondaryOrganization.organizationId); - await expect(page).toHaveURL(/\/next\/project\/list/); + await expect(page).toHaveURL(/\/project\/list/); await expect(page.getByText(e2eSecondaryOrganization.projectName, { exact: true }).filter({ visible: true }).first()).toBeVisible(); }); @@ -148,7 +148,7 @@ test('stack links select the owner when no events remain', async ({ e2eApi, e2eS await page.route(`**/api/v2/stacks/${event.stack_id}/events?*`, async (route) => { await route.fulfill({ json: [] }); }); - await page.goto(`/next/stack/${event.stack_id}`); + await page.goto(`/stack/${event.stack_id}`); await expect(page.getByText('No events available for this stack.')).toBeVisible(); await expectOrganization(page, e2eSecondaryOrganization.organizationId); await expect(page).toHaveURL(new RegExp(`/stack/${event.stack_id}$`)); @@ -170,7 +170,7 @@ test('authorized invoice links select their organization', async ({ e2eSecondary } }); }); - await page.goto(`/next/payment/${invoiceId}`); + await page.goto(`/payment/${invoiceId}`); await expect(page.getByRole('cell', { exact: true, name: e2eSecondaryOrganization.organizationName })).toBeVisible(); await expectOrganization(page, e2eSecondaryOrganization.organizationId); await expect(page).toHaveURL(new RegExp(`/payment/${invoiceId}$`)); @@ -180,10 +180,10 @@ test.describe('member access', () => { test.use({ e2eUseGeneratedUser: true }); test('members can follow links to another organization they belong to', async ({ e2eScenario, e2eSecondaryOrganization, page }) => { - await page.goto(`/next/organization/${e2eSecondaryOrganization.organizationId}/manage`); + await page.goto(`/organization/${e2eSecondaryOrganization.organizationId}/manage`); await expect(page.getByRole('heading', { exact: true, name: `${e2eSecondaryOrganization.organizationName} Settings` })).toBeVisible(); await expectOrganization(page, e2eSecondaryOrganization.organizationId); - await page.goto(`/next/project/${e2eScenario.projectId}/settings`); + await page.goto(`/project/${e2eScenario.projectId}/settings`); await expect(page.getByRole('heading', { exact: true, name: `${e2eScenario.projectName} Settings` })).toBeVisible(); await expectOrganization(page, e2eScenario.organizationId); }); @@ -197,7 +197,7 @@ test.describe('member access', () => { for (const resource of ['organization', 'project', 'stack', 'event']) { const response = page.waitForResponse((item) => item.url().includes(`/api/v2/${resource}s/${resourceId}`)); const suffix = resource === 'organization' ? '/manage' : resource === 'project' ? '/settings' : ''; - await page.goto(`/next/${resource}/${resourceId}${suffix}`); + await page.goto(`/${resource}/${resourceId}${suffix}`); expect((await response).status()).toBe(status); await expect( page diff --git a/src/Exceptionless.Web/ClientApp/e2e/tests/saved-view-auto-fill.e2e.ts b/src/Exceptionless.Web/ClientApp/e2e/tests/saved-view-auto-fill.e2e.ts index ae7c4e2af9..3a7050914f 100644 --- a/src/Exceptionless.Web/ClientApp/e2e/tests/saved-view-auto-fill.e2e.ts +++ b/src/Exceptionless.Web/ClientApp/e2e/tests/saved-view-auto-fill.e2e.ts @@ -40,7 +40,7 @@ test('saved views choose which event or stack column auto-fills', async ({ e2eAp expect(response.status(), await response.text()).toBe(201); await page.setViewportSize({ height: 900, width: 1600 }); - await page.goto(`/next/event/${viewSlug}`); + await page.goto(`/event/${viewSlug}`); await expect(getVisibleText(page, journey.message)).toBeVisible({ timeout: 30_000 }); await page.getByRole('button', { name: /^View/ }).filter({ visible: true }).first().click(); @@ -123,7 +123,7 @@ test('saved views choose which event or stack column auto-fills', async ({ e2eAp await page.getByRole('dialog', { name: 'Column Picker' }).getByRole('button', { name: 'Done' }).click(); }); - for (const route of ['/next/event/all', '/next/stack/all']) { + for (const route of ['/event/all', '/stack/all']) { await test.step(`${route} predefined view auto-fills Summary`, async () => { await page.setViewportSize({ height: 900, width: 1600 }); await page.goto(route); diff --git a/src/Exceptionless.Web/ClientApp/e2e/tests/saved-views.e2e.ts b/src/Exceptionless.Web/ClientApp/e2e/tests/saved-views.e2e.ts index 1bc5c2ee3a..ad77352d14 100644 --- a/src/Exceptionless.Web/ClientApp/e2e/tests/saved-views.e2e.ts +++ b/src/Exceptionless.Web/ClientApp/e2e/tests/saved-views.e2e.ts @@ -21,9 +21,9 @@ test('home navigation honors personal and organization saved views and survives // Act & Assert: each step exercises and verifies a home-view transition. await test.step('fall back to the first Stacks saved view when no default is configured', async () => { // Act - await page.goto('/next/'); + await page.goto('/'); // Assert - await expect(page).toHaveURL(/\/next\/stack\/all(?:[?#]|$)/); + await expect(page).toHaveURL(/\/stack\/all(?:[?#]|$)/); await expect(page.getByRole('heading', { name: 'All' })).toBeVisible({ timeout: 30_000 }); await expect.poll(() => savedViewListLimits).toContain('100'); }); @@ -31,7 +31,7 @@ test('home navigation honors personal and organization saved views and survives await test.step('prefer the personal saved view', async () => { // Arrange await journey.submitRepresentativeEvent(); - await page.goto(`/next/event?reference=${encodeURIComponent(journey.referenceId)}&time=all`); + await page.goto(`/event?reference=${encodeURIComponent(journey.referenceId)}&time=all`); await expect(getVisibleText(page, journey.message)).toBeVisible({ timeout: 30_000 }); // Act @@ -73,9 +73,9 @@ test('home navigation honors personal and organization saved views and survives .toBe(true); // Act - await page.goto('/next/'); + await page.goto('/'); // Assert - await expect(page).toHaveURL(new RegExp(`/next/event/${escapeRegExp(viewSlug)}(?:[?#]|$)`)); + await expect(page).toHaveURL(new RegExp(`/event/${escapeRegExp(viewSlug)}(?:[?#]|$)`)); }); await test.step('fall back to the organization saved view after clearing the personal preference', async () => { @@ -92,9 +92,9 @@ test('home navigation honors personal and organization saved views and survives await expect(page.getByText('Personal home view cleared.')).toBeVisible(); // Act - await page.goto('/next/'); + await page.goto('/'); // Assert - await expect(page).toHaveURL(new RegExp(`/next/event/${escapeRegExp(viewSlug)}(?:[?#]|$)`)); + await expect(page).toHaveURL(new RegExp(`/event/${escapeRegExp(viewSlug)}(?:[?#]|$)`)); }); await test.step('clear deleted defaults and return to the first Stacks saved view', async () => { @@ -117,9 +117,9 @@ test('home navigation honors personal and organization saved views and survives expect(deletion).toBe(202); // Act - await page.goto('/next/'); + await page.goto('/'); // Assert - await expect(page).toHaveURL(/\/next\/stack\/all(?:[?#]|$)/); + await expect(page).toHaveURL(/\/stack\/all(?:[?#]|$)/); }); // Assert @@ -163,7 +163,7 @@ test('saved view navigation order is personal, persistent, and resettable', asyn ) .toBe(true); - await page.goto('/next/event'); + await page.goto('/event'); const sidebar = page.locator('[data-sidebar="sidebar"]'); const sharedViewLink = sidebar.getByRole('link', { exact: true, name: sharedViewName }); const privateViewLink = sidebar.getByRole('link', { exact: true, name: privateViewName }); @@ -172,8 +172,8 @@ test('saved view navigation order is personal, persistent, and resettable', asyn await expectSavedViewBefore(sharedViewLink, privateViewLink); await sharedViewLink.click(); - await expect(page).toHaveURL(new RegExp(`/next/event/${escapeRegExp(savedViewSlug(sharedViewName))}(?:[?#]|$)`)); - await page.goto('/next/event'); + await expect(page).toHaveURL(new RegExp(`/event/${escapeRegExp(savedViewSlug(sharedViewName))}(?:[?#]|$)`)); + await page.goto('/event'); await expect(privateViewLink).toBeVisible({ timeout: 30_000 }); await expect(privateViewLink).toHaveAttribute('draggable', 'true'); @@ -242,7 +242,7 @@ test('events saved view can be saved, renamed, loaded, and deleted', async ({ e2 }); await test.step('save the filtered Events page as a view', async () => { - await page.goto(`/next/event?reference=${encodeURIComponent(journey.referenceId)}&time=all`); + await page.goto(`/event?reference=${encodeURIComponent(journey.referenceId)}&time=all`); await expect(getVisibleText(page, journey.message)).toBeVisible({ timeout: 30_000 }); await openViewMenu(page); @@ -256,7 +256,7 @@ test('events saved view can be saved, renamed, loaded, and deleted', async ({ e2 await expect(dialog).toBeHidden({ timeout: 30_000 }); await expect(page.getByRole('heading', { name: viewName })).toBeVisible({ timeout: 30_000 }); - await expect(page).toHaveURL(new RegExp(`/next/event/${escapeRegExp(viewSlug)}(?:[?#]|$)`)); + await expect(page).toHaveURL(new RegExp(`/event/${escapeRegExp(viewSlug)}(?:[?#]|$)`)); await expect(getVisibleText(page, journey.message)).toBeVisible(); }); @@ -272,19 +272,19 @@ test('events saved view can be saved, renamed, loaded, and deleted', async ({ e2 await expect(dialog).toBeHidden({ timeout: 30_000 }); await expect(page.getByRole('heading', { name: renamedViewName })).toBeVisible({ timeout: 30_000 }); - await expect(page).toHaveURL(new RegExp(`/next/event/${escapeRegExp(viewSlug)}(?:[?#]|$)`)); + await expect(page).toHaveURL(new RegExp(`/event/${escapeRegExp(viewSlug)}(?:[?#]|$)`)); await expect(getVisibleText(page, journey.message)).toBeVisible(); }); await test.step('updating a URL override clears the hidden session-local draft', async () => { - await page.goto(`/next/event/${viewSlug}?time=90d`); + await page.goto(`/event/${viewSlug}?time=90d`); const dateFilter = page.getByRole('button', { name: /^Date/ }).filter({ visible: true }).first(); await dateFilter.click(); await page.getByRole('button', { name: 'Last 30 days' }).click(); await expect(page).toHaveURL(/[?&]time=30d(?:&|$)/); await expect(page.getByLabel('Unsaved view changes')).toBeVisible(); - await page.goto(`/next/event/${viewSlug}?time=1d`); + await page.goto(`/event/${viewSlug}?time=1d`); await expect( page .getByRole('button', { name: /Date\s+Last 24 hours/ }) @@ -305,7 +305,7 @@ test('events saved view can be saved, renamed, loaded, and deleted', async ({ e2 }) .toContain('now-1d'); - await page.goto(`/next/event/${viewSlug}`); + await page.goto(`/event/${viewSlug}`); await expect( page .getByRole('button', { name: /Date\s+Last 24 hours/ }) @@ -321,7 +321,7 @@ test('events saved view can be saved, renamed, loaded, and deleted', async ({ e2 .filter({ visible: true }) .first(); - await page.goto(`/next/event/${viewSlug}?reference=`); + await page.goto(`/event/${viewSlug}?reference=`); await expect(page).toHaveURL(/[?&]reference=(?:&|$)/); await expect(referenceFilter).toHaveCount(0); await page.reload(); @@ -335,7 +335,7 @@ test('events saved view can be saved, renamed, loaded, and deleted', async ({ e2 }); await test.step('reset route-specific filter overrides to the saved view', async () => { - await page.goto(`/next/event/${viewSlug}?project=${e2eScenario.projectId}`); + await page.goto(`/event/${viewSlug}?project=${e2eScenario.projectId}`); await openViewMenu(page); await page.getByRole('menuitem', { name: 'Reset to Saved' }).click(); @@ -354,7 +354,7 @@ test('events saved view can be saved, renamed, loaded, and deleted', async ({ e2 await expect(dialog).toBeHidden({ timeout: 30_000 }); await expect(page.getByRole('heading', { name: 'Events' })).toBeVisible({ timeout: 30_000 }); - await expect(page).toHaveURL(/\/next\/event(?:[?#]|$)/); + await expect(page).toHaveURL(/\/event(?:[?#]|$)/); await expect(page.getByRole('heading', { name: renamedViewName })).toHaveCount(0); }); @@ -429,7 +429,7 @@ test('switching saved views preserves each view temporary filter overrides acros ) .toBe(true); - await page.goto(`/next/event/${firstViewSlug}`); + await page.goto(`/event/${firstViewSlug}`); await expect(page.getByRole('heading', { name: firstViewName })).toBeVisible(); const dateFilter = page.getByRole('button', { name: /^Date/ }).filter({ visible: true }).first(); await dateFilter.click(); @@ -452,7 +452,7 @@ test('switching saved views preserves each view temporary filter overrides acros const secondViewLink = page.getByRole('link', { exact: true, name: secondViewName }).first(); await secondViewLink.click(); - await expect(page).toHaveURL(new RegExp(`/next/event/${escapeRegExp(secondViewSlug)}(?:[?#]|$)`)); + await expect(page).toHaveURL(new RegExp(`/event/${escapeRegExp(secondViewSlug)}(?:[?#]|$)`)); await expect(page.getByRole('heading', { name: secondViewName })).toBeVisible(); await expect( page @@ -482,8 +482,8 @@ test('switching saved views preserves each view temporary filter overrides acros setTimeout(() => navigate(firstHref), 0); }, { - firstHref: `/next/event/${firstViewSlug}`, - secondHref: `/next/event/${secondViewSlug}` + firstHref: `/event/${firstViewSlug}`, + secondHref: `/event/${secondViewSlug}` } ); await page.waitForTimeout(100); @@ -504,7 +504,7 @@ test('switching saved views preserves each view temporary filter overrides acros } }; page.on('request', captureColdLoadEventTime); - await page.goto(`/next/event/${firstViewSlug}?project=${e2eScenario.projectId}&sort=type&status=open`); + await page.goto(`/event/${firstViewSlug}?project=${e2eScenario.projectId}&sort=type&status=open`); await expect(page.getByRole('heading', { name: firstViewName })).toBeVisible(); await expect(page).toHaveURL(new RegExp(`[?&]project=${escapeRegExp(e2eScenario.projectId)}(?:&|$)`)); await expect(page).toHaveURL(/[?&]sort=type(?:&|$)/); @@ -524,13 +524,13 @@ test('switching saved views preserves each view temporary filter overrides acros .filter({ visible: true }) .first() ).toBeVisible(); - await navigateClientSide(page, `/next/event/${firstViewSlug}?project=${e2eScenario.projectId}&sort=type&status=regressed&time=90d`); + await navigateClientSide(page, `/event/${firstViewSlug}?project=${e2eScenario.projectId}&sort=type&status=regressed&time=90d`); await expect(page).toHaveURL(/[?&]status=regressed(?:&|$)/); await secondViewLink.click(); await expect(page.getByRole('heading', { name: secondViewName })).toBeVisible(); await firstViewLink.click(); - await expect(page).toHaveURL(new RegExp(`/next/event/${escapeRegExp(firstViewSlug)}(?:[?#]|$)`)); + await expect(page).toHaveURL(new RegExp(`/event/${escapeRegExp(firstViewSlug)}(?:[?#]|$)`)); await expect(page).not.toHaveURL(/[?&]status=/); await expect(page).not.toHaveURL(/[?&]project=/); await expect(page).not.toHaveURL(/[?&]sort=/); @@ -544,7 +544,7 @@ test('switching saved views preserves each view temporary filter overrides acros await expect(page.getByLabel('Unsaved view changes')).toBeVisible(); await expectColumnBefore(page, 'User', 'Summary'); - await page.goto(`/next/event/${firstViewSlug}?time=15m`); + await page.goto(`/event/${firstViewSlug}?time=15m`); await expect( page .getByRole('button', { name: /Date\s+Last 15 minutes/ }) @@ -578,7 +578,7 @@ test('switching saved views preserves each view temporary filter overrides acros await refreshedSavedViews; await secondViewLink.click(); - await expect(page).toHaveURL(new RegExp(`/next/event/${escapeRegExp(secondViewSlug)}(?:[?#]|$)`)); + await expect(page).toHaveURL(new RegExp(`/event/${escapeRegExp(secondViewSlug)}(?:[?#]|$)`)); await expect(page.getByRole('heading', { name: secondViewName })).toBeVisible(); await expect( page @@ -602,7 +602,7 @@ test('switching saved views preserves each view temporary filter overrides acros ).toBeVisible(); await expect(page.getByLabel('Unsaved view changes')).toBeVisible(); await firstViewLink.click(); - await expect(page).toHaveURL(new RegExp(`/next/event/${escapeRegExp(firstViewSlug)}(?:[?#]|$)`)); + await expect(page).toHaveURL(new RegExp(`/event/${escapeRegExp(firstViewSlug)}(?:[?#]|$)`)); await expect(page).toHaveURL(/[?&]time=90d(?:&|$)/); await expect(page.getByRole('heading', { name: firstViewName })).toBeVisible(); await expect( @@ -619,9 +619,9 @@ test('switching saved views preserves each view temporary filter overrides acros await expect(summaryWrap).toBeChecked(); await columnDialog.getByRole('button', { name: 'Done' }).click(); - await navigateClientSide(page, '/next/stack'); - await expect(page).toHaveURL(/\/next\/stack(?:[?#]|$)/); - await navigateClientSide(page, `/next/event/${firstViewSlug}`); + await navigateClientSide(page, '/stack'); + await expect(page).toHaveURL(/\/stack(?:[?#]|$)/); + await navigateClientSide(page, `/event/${firstViewSlug}`); await expect( page .getByRole('button', { name: /Date\s+Last 90 days/ }) @@ -629,8 +629,8 @@ test('switching saved views preserves each view temporary filter overrides acros .first() ).toBeVisible(); await page.goBack(); - await expect(page).toHaveURL(/\/next\/stack(?:[?#]|$)/); - await navigateClientSide(page, `/next/event/${firstViewSlug}`); + await expect(page).toHaveURL(/\/stack(?:[?#]|$)/); + await navigateClientSide(page, `/event/${firstViewSlug}`); await expect(page.getByRole('heading', { name: firstViewName })).toBeVisible(); await openViewMenu(page); @@ -697,7 +697,7 @@ test('save completion does not overwrite a newly active saved view', async ({ e2 }) .toBe(true); - await page.goto(`/next/event/${firstViewSlug}`); + await page.goto(`/event/${firstViewSlug}`); await expect(page.getByRole('heading', { name: firstViewName })).toBeVisible(); await page.getByRole('button', { name: /^Date/ }).filter({ visible: true }).first().click(); await page.getByRole('button', { name: 'Last 90 days' }).click(); @@ -844,10 +844,10 @@ test('stream switches to a session-local saved view draft without mixing in-flig sourceRequestCompleted = true; }); - await page.goto('/next/stream'); + await page.goto('/stream'); await expect.poll(() => sourceRequestStarted).toBe(true); await page.getByRole('button', { name: 'Pause streaming updates' }).click(); - await navigateClientSide(page, `/next/stream?saved=${savedView.id}`); + await navigateClientSide(page, `/stream?saved=${savedView.id}`); releaseSourceRequest(); await expect.poll(() => sourceRequestCompleted).toBe(true); await expect(page.getByRole('heading', { name: viewName })).toBeVisible(); @@ -910,7 +910,7 @@ test('stream switches to a session-local saved view draft without mixing in-flig await expect(page.getByLabel('Unsaved view changes')).toBeVisible(); streamRequestFilters.length = 0; - await page.goto(`/next/stream?saved=${savedView.id}&filter=${encodeURIComponent(draftExpression)}`); + await page.goto(`/stream?saved=${savedView.id}&filter=${encodeURIComponent(draftExpression)}`); await expect(page.getByRole('heading', { name: viewName })).toBeVisible(); await expect(page.getByLabel('Unsaved view changes')).toBeVisible(); await expect(page.getByRole('button', { name: 'Status Fixed' })).toHaveCount(0); @@ -920,7 +920,7 @@ test('stream switches to a session-local saved view draft without mixing in-flig expect(explicitFilter).not.toContain(remoteExpression); streamRequestFilters.length = 0; - await page.goto(`/next/stream?saved=${savedView.id}&filter=`); + await page.goto(`/stream?saved=${savedView.id}&filter=`); await expect(page.getByRole('heading', { name: viewName })).toBeVisible(); await expect(page.getByLabel('Unsaved view changes')).toBeVisible(); await expect(page.getByRole('button', { name: 'Status Fixed' })).toHaveCount(0); @@ -944,7 +944,7 @@ test('stream loads default results when its saved-view lookup fails', async ({ e } }); - await page.goto('/next/stream?saved=unavailable-view'); + await page.goto('/stream?saved=unavailable-view'); await expect(page.getByRole('heading', { name: 'Event Stream' })).toBeVisible(); await expect.poll(() => eventRequests.length, { timeout: 30_000 }).toBeGreaterThan(0); }); @@ -997,7 +997,7 @@ test('reset clears a session-local draft after delayed current-user identity res await route.continue(); }); - await page.goto(`/next/event/${viewSlug}?time=1d`); + await page.goto(`/event/${viewSlug}?time=1d`); await expect(page.getByRole('heading', { name: viewName })).toBeVisible(); await openViewMenu(page); const resetMenuItem = page.getByRole('menuitem', { name: 'Reset to Saved' }); @@ -1063,7 +1063,7 @@ test('filter edits made before current-user identity resolves become session-loc await route.continue(); }); - await page.goto(`/next/event/${viewSlug}`); + await page.goto(`/event/${viewSlug}`); await expect(page.getByRole('heading', { name: viewName })).toBeVisible(); await openViewMenu(page); const chartMenuItem = page.getByRole('menuitemcheckbox', { name: 'Chart' }); @@ -1095,7 +1095,7 @@ test('filter edits made before current-user identity resolves become session-loc }) .toContain('now-90d'); - await page.goto(`/next/event/${viewSlug}`); + await page.goto(`/event/${viewSlug}`); await expect(page.getByRole('heading', { name: viewName })).toBeVisible(); await expect .poll(async () => { @@ -1181,7 +1181,7 @@ test('saved view loads server state and protects drafts when the current-user lo } }); - await page.goto(`/next/event/${viewSlug}?time=1d`); + await page.goto(`/event/${viewSlug}?time=1d`); await expect(page.getByRole('heading', { name: viewName })).toBeVisible(); await expect.poll(() => eventRequests.length, { timeout: 30_000 }).toBeGreaterThan(0); await openViewMenu(page); @@ -1189,7 +1189,7 @@ test('saved view loads server state and protects drafts when the current-user lo await expect(page.getByRole('menuitem', { name: 'Reset to Saved' })).toBeDisabled(); await page.unroute('**/api/v2/users/me'); - await page.goto(`/next/event/${viewSlug}`); + await page.goto(`/event/${viewSlug}`); await expect(page.getByRole('heading', { name: viewName })).toBeVisible(); await expect( page diff --git a/src/Exceptionless.Web/ClientApp/e2e/tests/selection-column-width.e2e.ts b/src/Exceptionless.Web/ClientApp/e2e/tests/selection-column-width.e2e.ts index de92ad8bd3..9275d9da27 100644 --- a/src/Exceptionless.Web/ClientApp/e2e/tests/selection-column-width.e2e.ts +++ b/src/Exceptionless.Web/ClientApp/e2e/tests/selection-column-width.e2e.ts @@ -8,7 +8,7 @@ test('row selection column stays fixed at desktop and narrow widths', async ({ e const journey = ExceptionlessE2EJourney.fromScenario(page, e2eApi, e2eScenario); await journey.submitRepresentativeEvent(); - await page.goto(`/next/event?reference=${encodeURIComponent(journey.referenceId)}&time=all`); + await page.goto(`/event?reference=${encodeURIComponent(journey.referenceId)}&time=all`); await expect(getVisibleText(page, journey.message)).toBeVisible({ timeout: 30_000 }); const table = page.locator('table:has(thead [role="checkbox"])').first(); diff --git a/src/Exceptionless.Web/ClientApp/e2e/tests/service-status-navigation.e2e.ts b/src/Exceptionless.Web/ClientApp/e2e/tests/service-status-navigation.e2e.ts index f312f2c486..aac26dc443 100644 --- a/src/Exceptionless.Web/ClientApp/e2e/tests/service-status-navigation.e2e.ts +++ b/src/Exceptionless.Web/ClientApp/e2e/tests/service-status-navigation.e2e.ts @@ -11,7 +11,7 @@ test('transient API failures only open Service Status when the health probe fail } }); page.on('framenavigated', (frame) => { - if (frame === page.mainFrame() && new URL(frame.url()).pathname === '/next/status') { + if (frame === page.mainFrame() && new URL(frame.url()).pathname === '/status') { serviceStatusNavigations.push(frame.url()); } }); @@ -21,7 +21,7 @@ test('transient API failures only open Service Status when the health probe fail } }); - const targetUrl = '/next/project/list?project=test-project#details'; + const targetUrl = '/project/list?project=test-project#details'; let transientFailuresRemaining = 1; await page.route('**/api/v2/projects**', async (route) => { if (transientFailuresRemaining > 0) { @@ -61,7 +61,7 @@ test('transient API failures only open Service Status when the health probe fail await test.step('coalesce the redirect and preserve the current URL when the service is unavailable', async () => { await page.reload(); - await expect(page).toHaveURL(/\/next\/status(?:[?#]|$)/); + await expect(page).toHaveURL(/\/status(?:[?#]|$)/); const statusUrl = new URL(page.url()); expect(statusUrl.searchParams.get('redirect')).toBe(targetUrl); diff --git a/src/Exceptionless.Web/ClientApp/e2e/tests/session-investigation.e2e.ts b/src/Exceptionless.Web/ClientApp/e2e/tests/session-investigation.e2e.ts index 3181bb9f4b..e917d24194 100644 --- a/src/Exceptionless.Web/ClientApp/e2e/tests/session-investigation.e2e.ts +++ b/src/Exceptionless.Web/ClientApp/e2e/tests/session-investigation.e2e.ts @@ -58,7 +58,7 @@ test('operator can find and inspect a user session', async ({ e2eApi, e2eScenari }); await test.step('open the session from the Sessions table', async () => { - await page.goto('/next/sessions?time=all'); + await page.goto('/sessions?time=all'); await expect(page.getByRole('heading', { name: 'Sessions' })).toBeVisible(); const sessionRow = getVisibleRow(page, name, identity); @@ -70,7 +70,7 @@ test('operator can find and inspect a user session', async ({ e2eApi, e2eScenari await expect(eventSheet.getByText(name).filter({ visible: true }).first()).toBeVisible(); await eventSheet.getByRole('link', { name: 'Open details in new window' }).click(); - await expect(page).toHaveURL(/\/next\/(?:event|stack\/[^/]+\/event)\//); + await expect(page).toHaveURL(/\/(?:event|stack\/[^/]+\/event)\//); await expect(getVisibleText(page, identity)).toBeVisible(); }); @@ -89,7 +89,7 @@ test('operator can find and inspect a user session', async ({ e2eApi, e2eScenari await expect.poll(() => sessionEventsTable.evaluate((element) => element.scrollWidth / element.offsetWidth)).toBeLessThanOrEqual(1.02); const eventsLink = page.getByRole('link', { name: 'Open events filtered to this session' }); - await expect(eventsLink).toHaveAttribute('href', /\/next\/event\/all\?/); + await expect(eventsLink).toHaveAttribute('href', /\/event\/all\?/); const eventsHref = await eventsLink.getAttribute('href'); const eventsUrl = new URL(eventsHref!, page.url()); expect(eventsUrl.searchParams.get('session')).toBe(sessionId); @@ -113,7 +113,7 @@ test('operator can find and inspect a user session', async ({ e2eApi, e2eScenari }); await test.step('event detail messages wrap and small alignment fixes render consistently', async () => { - await page.goto(`/next/stack/${relatedStackId}/event/${relatedEventId}`); + await page.goto(`/stack/${relatedStackId}/event/${relatedEventId}`); await expect(page.getByRole('tab', { name: 'Overview' })).toHaveAttribute('aria-selected', 'true'); const activePanel = () => page.getByRole('tabpanel').filter({ visible: true }); diff --git a/src/Exceptionless.Web/ClientApp/e2e/tests/sessions-saved-views.e2e.ts b/src/Exceptionless.Web/ClientApp/e2e/tests/sessions-saved-views.e2e.ts index aae7d272a3..93f6269c34 100644 --- a/src/Exceptionless.Web/ClientApp/e2e/tests/sessions-saved-views.e2e.ts +++ b/src/Exceptionless.Web/ClientApp/e2e/tests/sessions-saved-views.e2e.ts @@ -29,7 +29,7 @@ test('Sessions saved views persist active state, display settings, filters, and await e2eApi.submitEvent(e2eScenario.projectId, e2eScenario.projectToken, createSessionEvent({ identity, name, sessionId })); await e2eApi.pollForEventByReference(e2eScenario.userToken, e2eScenario.projectId, sessionId); - await page.goto('/next/sessions?time=all'); + await page.goto('/sessions?time=all'); await expect(page.getByRole('heading', { name: 'Sessions' })).toBeVisible(); await expect(page.getByRole('button', { name: /^Type\b/ })).toHaveCount(0); await expect(getVisibleRow(page, name, identity)).toBeVisible({ timeout: 30_000 }); @@ -83,7 +83,7 @@ test('Sessions saved views persist active state, display settings, filters, and await saveDialog.getByRole('button', { name: 'Save' }).click(); await expect(saveDialog).toBeHidden({ timeout: 30_000 }); - await expect(page).toHaveURL(new RegExp(`/next/sessions/${escapeRegExp(viewSlug)}(?:[?#]|$)`)); + await expect(page).toHaveURL(new RegExp(`/sessions/${escapeRegExp(viewSlug)}(?:[?#]|$)`)); await expect(page.getByRole('heading', { name: viewName })).toBeVisible(); await expect .poll( @@ -207,7 +207,7 @@ test('Sessions legacy raw-filter views keep URL removals and session-scoped stat }); expect(response.status(), await response.text()).toBe(201); - await page.goto(`/next/sessions/${viewSlug}`); + await page.goto(`/sessions/${viewSlug}`); await expect(page.getByRole('heading', { name: viewName })).toBeVisible(); const rawFilterButton = page .getByRole('button', { name: new RegExp(`^Raw Filter\\s+${escapeRegExp(rawFilter)}`) }) @@ -259,7 +259,7 @@ test('Sessions ignore legacy structured Type filters and Type URL parameters', a }); expect(response.status(), await response.text()).toBe(201); - await page.goto(`/next/sessions/${viewSlug}?type=log`); + await page.goto(`/sessions/${viewSlug}?type=log`); await expect(page.getByRole('heading', { name: viewName })).toBeVisible(); await expect(page.getByRole('button', { name: /^Type\b/ })).toHaveCount(0); await expect(page.getByLabel('Unsaved view changes')).toHaveCount(0); diff --git a/src/Exceptionless.Web/ClientApp/e2e/tests/stack-effects-chaos.e2e.ts b/src/Exceptionless.Web/ClientApp/e2e/tests/stack-effects-chaos.e2e.ts index b45d22c4c8..a406b1e505 100644 --- a/src/Exceptionless.Web/ClientApp/e2e/tests/stack-effects-chaos.e2e.ts +++ b/src/Exceptionless.Web/ClientApp/e2e/tests/stack-effects-chaos.e2e.ts @@ -1,4 +1,4 @@ -import type { ConsoleMessage, Request, Response } from '@playwright/test'; +import type { ConsoleMessage, Page, Request, Response } from '@playwright/test'; import { expect, test } from '../fixtures/e2e-test'; import { ExceptionlessE2EJourney } from '../support/exceptionless-journey'; @@ -34,6 +34,7 @@ interface RuntimeDiagnostics { test('stack effects stay bounded through background, paging, and navigation chaos @signup', async ({ e2eApi, e2eScenario, page }, testInfo) => { test.slow(); + await page.clock.install(); await installWebSocketTestHarness(page, { ignoreServerMessages: true }); const journey = ExceptionlessE2EJourney.fromScenario(page, e2eApi, e2eScenario); @@ -63,7 +64,7 @@ test('stack effects stay bounded through background, paging, and navigation chao await test.step('load the stack page with one page of results', async () => { const response = page.waitForResponse((candidate) => isStackListResponse(candidate, e2eScenario.organizationId)); - await page.goto('/next/stack/all?limit=5'); + await page.goto('/stack/all?limit=5'); expect((await response).ok()).toBe(true); await expect(page.getByRole('heading', { exact: true, name: 'All' })).toBeVisible(); await expect(page.locator('tbody tr:visible').first()).toBeVisible(); @@ -85,15 +86,15 @@ test('stack effects stay bounded through background, paging, and navigation chao page.on('requestfailed', recordFailedDetailRequest); try { - await page.goto(`/next/stack/${journey.stackId}`); - await expect(page).toHaveURL(new RegExp(`/next/stack/${journey.stackId}$`)); + await page.goto(`/stack/${journey.stackId}`); + await expect(page).toHaveURL(new RegExp(`/stack/${journey.stackId}$`)); await expect(page.getByRole('tab', { name: 'Overview' })).toBeVisible(); } finally { page.off('requestfailed', recordFailedDetailRequest); } expect(failedDetailRequests).toEqual([]); - await page.goto('/next/stack/all?limit=5'); + await page.goto('/stack/all?limit=5'); await expect(page.getByRole('heading', { exact: true, name: 'All' })).toBeVisible(); }); @@ -198,27 +199,43 @@ test('stack effects stay bounded through background, paging, and navigation chao expect(actionSample(diagnostics, 'removal notification trailing reconciliation').countRequests).toBe(1); expect(actionSample(diagnostics, 'removal notification trailing reconciliation').listRequests).toBe(1); + // Keep all four waves inside one throttle window even when browser round trips are slow. + // The other chaos phases still exercise real-time visibility and navigation behavior. + await page.clock.pauseAt(new Date(Date.now() + 1_000)); await measureAction(diagnostics, 'sustained stack change notifications', async () => { for (let wave = 0; wave < 4; wave++) { - await dispatchWebSocketMessages( - page, - Array.from({ length: 30 }, (_, index) => ({ - message: { - change_type: 1, - data: {}, - id: `chaos-missing-stack-${wave}-${index}`, - organization_id: e2eScenario.organizationId, - project_id: e2eScenario.projectId, - type: 'Stack' - }, - type: 'StackChanged' - })) - ); - await page.waitForTimeout(1_600); + const dispatchWave = () => + dispatchWebSocketMessages( + page, + Array.from({ length: 30 }, (_, index) => ({ + message: { + change_type: 1, + data: {}, + id: `chaos-missing-stack-${wave}-${index}`, + organization_id: e2eScenario.organizationId, + project_id: e2eScenario.projectId, + type: 'Stack' + }, + type: 'StackChanged' + })) + ); + if (wave === 0) { + await runAndWaitForDashboardRefresh(page, e2eScenario.organizationId, dispatchWave); + } else { + await dispatchWave(); + } + + const advanceWave = () => page.clock.runFor(1_600); + if (wave === 3) { + await runAndWaitForDashboardRefresh(page, e2eScenario.organizationId, advanceWave); + } else { + await advanceWave(); + } } - await page.waitForTimeout(2_000); + await page.clock.runFor(2_000); }); + await page.clock.resume(); const sustainedNotificationSample = actionSample(diagnostics, 'sustained stack change notifications'); expect(sustainedNotificationSample.countRequests).toBeGreaterThanOrEqual(1); expect(sustainedNotificationSample.countRequests).toBeLessThanOrEqual(2); @@ -228,9 +245,9 @@ test('stack effects stay bounded through background, paging, and navigation chao await measureAction(diagnostics, 'route remounts', async () => { for (let index = 0; index < 5; index++) { - await page.goto(`/next/event/${journey.eventId}`); + await page.goto(`/event/${journey.eventId}`); await expect(page.getByRole('tab', { name: 'Overview' })).toBeVisible(); - await page.goto('/next/stack?limit=5'); + await page.goto('/stack?limit=5'); await expect(page.getByRole('heading', { name: 'Stacks' })).toBeVisible(); } }); @@ -343,3 +360,17 @@ function recordRequestFailure(diagnostics: RuntimeDiagnostics, request: Request) url: request.url() }); } + +async function runAndWaitForDashboardRefresh(page: Page, organizationId: string, action: () => Promise): Promise { + const responses = Promise.all([ + page.waitForResponse((response) => isStackListResponse(response, organizationId)), + page.waitForResponse((response) => new URL(response.url()).pathname === `/api/v2/organizations/${organizationId}/events/count`) + ]); + + await action(); + for (const response of await responses) { + expect(response.ok()).toBe(true); + // Browser time advances independently of real HTTP; finish the response before the next invalidation or navigation. + expect(await response.finished()).toBeNull(); + } +} diff --git a/src/Exceptionless.Web/ClientApp/e2e/tests/stack-triage.e2e.ts b/src/Exceptionless.Web/ClientApp/e2e/tests/stack-triage.e2e.ts index efbebab3d9..958fdddf54 100644 --- a/src/Exceptionless.Web/ClientApp/e2e/tests/stack-triage.e2e.ts +++ b/src/Exceptionless.Web/ClientApp/e2e/tests/stack-triage.e2e.ts @@ -1,4 +1,5 @@ -import { expect, test } from '../fixtures/e2e-test'; +import { E2E_TEST_PASSWORD, expect, test } from '../fixtures/e2e-test'; +import { seedRepresentativeEvent } from '../support/event-data'; import { ExceptionlessE2EJourney } from '../support/exceptionless-journey'; test('new user can mark an open stack fixed from event details @signup', async ({ e2eApi, e2eScenario, page }) => { @@ -66,3 +67,92 @@ test('status update failure is visible to the user @signup', async ({ e2eApi, e2 await expect(page.getByText('Status update rejected by test.', { exact: true })).toBeVisible(); await expect(page.getByRole('heading', { name: /Discard Stack/ })).toBeVisible(); }); + +test.describe('email stack actions', () => { + test.use({ e2eInjectBrowserToken: false, e2eUseGeneratedUser: true }); + + test('email shortcuts survive login, require confirmation, and recover from rejected updates', async ({ e2eApi, e2eScenario, page }) => { + const event = await seedRepresentativeEvent(e2eApi, e2eScenario.userToken, e2eScenario); + const mutations: string[] = []; + page.on('request', (request) => { + if (new URL(request.url()).pathname.includes('/stacks/') && request.method() !== 'GET') mutations.push(request.url()); + }); + + await test.step('sign in from a previously delivered action link', async () => { + await page.goto(`/#!/stack/${event.stack_id}/ignored`); + await expect(page.getByRole('button', { exact: true, name: 'Login' })).toBeVisible(); + await expect.poll(() => new URL(page.url()).searchParams.get('redirect')).toBe(`/stack/${event.stack_id}/ignored`); + expect(mutations).toEqual([]); + await page.getByLabel('Email', { exact: true }).fill(e2eScenario.email); + await page.getByPlaceholder('Enter password').fill(E2E_TEST_PASSWORD); + await page.getByRole('button', { exact: true, name: 'Login' }).click(); + await expect(page.getByRole('heading', { exact: true, name: 'Ignore Stack' })).toBeVisible(); + expect(mutations).toEqual([]); + }); + + for (const action of [ + { button: 'Ignore Stack', heading: 'Ignore Stack', path: 'ignored', request: 'change-status', status: 'Ignored' }, + { button: 'Mark Stack Fixed', heading: 'Mark Stack As Fixed', path: 'mark-fixed', request: 'mark-fixed', status: 'Fixed' }, + { button: 'Discard Stack', heading: 'Discard Stack', path: 'discarded', request: 'change-status', status: 'Discarded' } + ]) { + await test.step(`confirm ${action.path} only after recovering from an API rejection`, async () => { + const before = mutations.length; + await page.goto(`/stack/${event.stack_id}/${action.path}`); + const dialog = page.getByRole('alertdialog'); + await expect(dialog.getByRole('heading', { exact: true, name: action.heading })).toBeVisible(); + await expect(dialog.getByText(e2eScenario.message, { exact: true })).toBeVisible(); + expect(mutations).toHaveLength(before); + + if (action.path === 'mark-fixed') { + await dialog.getByLabel('Version', { exact: true }).fill('not-a-version'); + await dialog.getByRole('button', { exact: true, name: action.button }).click(); + await expect(dialog.getByText('Version must be a valid semantic version (e.g., 1.0.0)', { exact: true })).toBeVisible(); + expect(mutations).toHaveLength(before); + await dialog.getByLabel('Version', { exact: true }).fill('1.2.3'); + } + const endpoint = `**/api/v2/stacks/${event.stack_id}/${action.request}*`; + await page.route(endpoint, (route) => + route.fulfill({ + body: JSON.stringify({ title: 'Email action rejected by test.' }), + contentType: 'application/problem+json', + status: 422 + }) + ); + await dialog.getByRole('button', { exact: true, name: action.button }).click(); + await expect(page.getByText('Email action rejected by test.', { exact: true })).toBeVisible(); + await expect(dialog).toBeVisible(); + expect(mutations).toHaveLength(before + 1); + await page.unroute(endpoint); + + const responsePromise = page.waitForResponse( + (response) => + response.request().method() === 'POST' && new URL(response.url()).pathname === `/api/v2/stacks/${event.stack_id}/${action.request}` + ); + await dialog.getByRole('button', { exact: true, name: action.button }).click(); + const response = await responsePromise; + expect(response.status()).toBe(200); + expect(new URL(response.url()).searchParams.get(action.path === 'mark-fixed' ? 'version' : 'status')).toBe( + action.path === 'mark-fixed' ? '1.2.3' : action.path + ); + await expect(dialog).not.toBeVisible(); + await expect(page).toHaveURL(new RegExp(`/stack/${event.stack_id}(?:/event/${event.id})?$`)); + await page.reload(); + await expect(page.getByRole('button', { exact: true, name: action.status })).toBeVisible(); + expect(mutations).toHaveLength(before + 2); + }); + } + + await test.step('an inaccessible stack never offers confirmation', async () => { + const before = mutations.length; + const stackRequests: string[] = []; + page.on('request', (request) => { + if (new URL(request.url()).pathname === '/api/v2/stacks/000000000000000000000001') stackRequests.push(request.url()); + }); + await page.goto('/stack/000000000000000000000001/ignored'); + await expect(page.getByText('Unable to load this stack.', { exact: true })).toBeVisible(); + await expect(page.getByRole('alertdialog')).not.toBeVisible(); + expect(stackRequests).toHaveLength(1); + expect(mutations).toHaveLength(before); + }); + }); +}); diff --git a/src/Exceptionless.Web/ClientApp/e2e/tests/stripe-loading.e2e.ts b/src/Exceptionless.Web/ClientApp/e2e/tests/stripe-loading.e2e.ts index a66b9620ec..72e2d8389c 100644 --- a/src/Exceptionless.Web/ClientApp/e2e/tests/stripe-loading.e2e.ts +++ b/src/Exceptionless.Web/ClientApp/e2e/tests/stripe-loading.e2e.ts @@ -22,7 +22,7 @@ test('authenticated application pages defer loading Stripe until billing is open } }); - await page.goto('/next/stack/all'); + await page.goto('/stack/all'); await expect(page.getByRole('heading', { exact: true, name: 'All' })).toBeVisible(); await expect(page.getByTitle('Refresh results').locator('svg')).not.toHaveClass(/animate-spin/); await page.waitForTimeout(1_000); diff --git a/src/Exceptionless.Web/ClientApp/e2e/tests/table-footer-layout.e2e.ts b/src/Exceptionless.Web/ClientApp/e2e/tests/table-footer-layout.e2e.ts index 9a1947c219..7840dac8ae 100644 --- a/src/Exceptionless.Web/ClientApp/e2e/tests/table-footer-layout.e2e.ts +++ b/src/Exceptionless.Web/ClientApp/e2e/tests/table-footer-layout.e2e.ts @@ -19,7 +19,7 @@ for (const route of ['stack', 'event', 'sessions']) { { height: 844, width: 390 } ]) { await page.setViewportSize(viewport); - await page.goto(`/next/${route}`); + await page.goto(`/${route}`); if (rowCount === 0) { await expect(page.getByText(EMPTY_MESSAGE, { exact: true })).toBeVisible(); } else { @@ -38,7 +38,7 @@ for (const route of ['stack', 'event', 'sessions']) { test('long tables keep sticky controls and release extra scroll space after paging to a short result', async ({ page }) => { await mockTablePage(page, 21); await page.setViewportSize({ height: 800, width: 1280 }); - await page.goto('/next/stack'); + await page.goto('/stack'); const toolbar = page.getByRole('toolbar', { name: 'Table controls' }); const pager = toolbar.getByRole('navigation', { name: 'Table pagination' }); diff --git a/src/Exceptionless.Web/ClientApp/e2e/tests/table-toolbar-pagination.e2e.ts b/src/Exceptionless.Web/ClientApp/e2e/tests/table-toolbar-pagination.e2e.ts index feb6733956..f8b6093d78 100644 --- a/src/Exceptionless.Web/ClientApp/e2e/tests/table-toolbar-pagination.e2e.ts +++ b/src/Exceptionless.Web/ClientApp/e2e/tests/table-toolbar-pagination.e2e.ts @@ -14,7 +14,7 @@ test('floating table controls keep the first row visible between different-heigh } await page.setViewportSize({ height: 720, width: 1280 }); - await page.goto('/next/event?limit=10&time=all'); + await page.goto('/event?limit=10&time=all'); const toolbar = page.locator('[data-slot="data-table-footer"]'); const pager = toolbar.getByRole('navigation', { name: 'Table pagination' }); diff --git a/src/Exceptionless.Web/ClientApp/e2e/tests/tag-suggestions-live.e2e.ts b/src/Exceptionless.Web/ClientApp/e2e/tests/tag-suggestions-live.e2e.ts index 2e3cbe385c..b8d399c960 100644 --- a/src/Exceptionless.Web/ClientApp/e2e/tests/tag-suggestions-live.e2e.ts +++ b/src/Exceptionless.Web/ClientApp/e2e/tests/tag-suggestions-live.e2e.ts @@ -14,7 +14,7 @@ test('tag suggestions from indexed events can be selected and survive reload', a }); await e2eApi.pollForEventByReference(e2eScenario.userToken, e2eScenario.projectId, e2eScenario.referenceId); - await page.goto('/next/event?tag=AlphaTag&time=all'); + await page.goto('/event?tag=AlphaTag&time=all'); await expect(getVisibleText(page, e2eScenario.message)).toBeVisible({ timeout: 30_000 }); await page.getByRole('button', { name: /^Tag\s+AlphaTag/ }).click(); await expect(page.getByRole('option', { exact: true, name: 'BetaTag' })).toBeVisible(); diff --git a/src/Exceptionless.Web/ClientApp/e2e/tests/tag-suggestions.e2e.ts b/src/Exceptionless.Web/ClientApp/e2e/tests/tag-suggestions.e2e.ts index 2c193fe300..60c6104b75 100644 --- a/src/Exceptionless.Web/ClientApp/e2e/tests/tag-suggestions.e2e.ts +++ b/src/Exceptionless.Web/ClientApp/e2e/tests/tag-suggestions.e2e.ts @@ -14,7 +14,7 @@ test('complete tag suggestions filter locally without changing selected tags', a }); }); await test.step('filter a complete cache locally while preserving the selection', async () => { - await page.goto('/next/event?tag=Selected&time=%5Bnow-24h%20TO%20now%5D&project=000000000000000000000003'); + await page.goto('/event?tag=Selected&time=%5Bnow-24h%20TO%20now%5D&project=000000000000000000000003'); await page.getByRole('button', { name: /^Tag\s+Selected/ }).click(); await expect(page.getByRole('option', { exact: true, name: 'Alpha' })).toBeVisible(); await input.fill('Be'); @@ -81,7 +81,7 @@ test('incomplete suggestions debounce remote search, reuse cache and preserve se }); }); await test.step('debounce searches and add a tag without dropping the selection', async () => { - await page.goto('/next/event?tag=Selected'); + await page.goto('/event?tag=Selected'); await page.getByRole('button', { name: /^Tag\s+Selected/ }).click(); await expect(page.getByRole('option', { exact: true, name: 'Common' })).toBeVisible(); await input.fill('R'); @@ -126,7 +126,7 @@ test('initial failure retries the initial query and keeps saved tag selections', await route.fulfill({ json: tags(['Alpha']) }); } }); - await page.goto('/next/event?tag=Selected'); + await page.goto('/event?tag=Selected'); await page.getByRole('button', { name: /^Tag\s+Selected/ }).click(); await expect(page.getByText('Could not load tags.')).toBeVisible(); await page.getByRole('button', { exact: true, name: 'Retry' }).click(); @@ -142,7 +142,7 @@ test('closing during debounce cancels the search and tag actions keep working', requests.push(aggregation); await route.fulfill({ json: tags(['Common'], 1) }); }); - await page.goto('/next/event?tag=Selected'); + await page.goto('/event?tag=Selected'); await page.getByRole('button', { name: /^Tag\s+Selected/ }).click(); await expect(page.getByRole('option', { exact: true, name: 'Common' })).toBeVisible(); const input = page.getByPlaceholder('Tag', { exact: true }); @@ -168,14 +168,14 @@ test('switching organizations discards the old picker and loads only the new org requestedOrganizations.push(organizationId); await route.fulfill({ json: tags([organizationId === ORGANIZATION_ID ? 'FirstOrganizationTag' : 'SecondOrganizationTag']) }); }); - await page.goto('/next/event?tag=Selected'); + await page.goto('/event?tag=Selected'); await page.getByRole('button', { name: /^Tag\s+Selected/ }).click(); await expect(page.getByRole('option', { exact: true, name: 'FirstOrganizationTag' })).toBeVisible(); await page.getByPlaceholder('Tag', { exact: true }).press('Escape'); await page.getByRole('button', { name: /Test Organization.*Unlimited/ }).click(); await page.getByRole('menuitem', { name: /Other Organization/ }).click(); await expect(page.getByRole('button', { name: /Other Organization.*Unlimited/ })).toBeVisible(); - await page.goto('/next/event?tag=OtherSelected'); + await page.goto('/event?tag=OtherSelected'); await page.getByRole('button', { name: /^Tag\s+OtherSelected/ }).click(); await expect(page.getByRole('option', { exact: true, name: 'SecondOrganizationTag' })).toBeVisible(); await expect(page.getByRole('option', { exact: true, name: 'FirstOrganizationTag' })).toHaveCount(0); diff --git a/src/Exceptionless.Web/ClientApp/e2e/tests/timestamp-tooltips.e2e.ts b/src/Exceptionless.Web/ClientApp/e2e/tests/timestamp-tooltips.e2e.ts index 1311849820..1f87588405 100644 --- a/src/Exceptionless.Web/ClientApp/e2e/tests/timestamp-tooltips.e2e.ts +++ b/src/Exceptionless.Web/ClientApp/e2e/tests/timestamp-tooltips.e2e.ts @@ -30,7 +30,7 @@ test('event and stack timestamp titles remain accessible across locales and time { organizationId: e2eScenario.organizationId, token: e2eScenario.userToken } ); const page = await context.newPage(); - for (const route of ['/next/event?time=all', '/next/stack?time=all']) { + for (const route of ['/event?time=all', '/stack?time=all']) { await page.goto(route); const timestamp = page.locator('time').first(); await expect(timestamp).toBeVisible(); diff --git a/src/Exceptionless.Web/ClientApp/package.json b/src/Exceptionless.Web/ClientApp/package.json index 99ddee2c32..1efa80feae 100644 --- a/src/Exceptionless.Web/ClientApp/package.json +++ b/src/Exceptionless.Web/ClientApp/package.json @@ -4,7 +4,7 @@ "private": true, "scripts": { "dev": "cross-env NODE_OPTIONS=--trace-warnings vite dev", - "dev:api": "cross-env API_HTTP=https://dev-collector.exceptionless.io OLDAPP_HTTP=https://dev-app.exceptionless.io vite dev", + "dev:api": "cross-env API_HTTP=https://dev-collector.exceptionless.io vite dev", "urls": "node scripts/resolve-url.mjs", "build": "vite build", "preview": "vite preview", diff --git a/src/Exceptionless.Web/ClientApp/scripts/resolve-url.mjs b/src/Exceptionless.Web/ClientApp/scripts/resolve-url.mjs index b39dee9466..b36362db34 100644 --- a/src/Exceptionless.Web/ClientApp/scripts/resolve-url.mjs +++ b/src/Exceptionless.Web/ClientApp/scripts/resolve-url.mjs @@ -51,19 +51,17 @@ function git(args) { } const isAppResource = (resource) => /^app$/i.test(resource.displayName ?? '') || /^app$/i.test(resource.name ?? ''); -const isOldAppResource = (resource) => /^oldapp$/i.test(resource.displayName ?? '') || /^oldapp$/i.test(resource.name ?? ''); const isApiResource = (resource) => /^api$/i.test(resource.displayName ?? '') || /^api$/i.test(resource.name ?? ''); export function resolveUrls() { const clean = (url) => url && url.replace(/\/$/, ''); const scope = deriveScope(); - const { apiUrl, appUrl, found, oldAppUrl } = fromAspire(); + const { apiUrl, appUrl, found } = fromAspire(); if (appUrl) { return { apiUrl: clean(apiUrl), appUrl: clean(appUrl), - oldAppUrl: clean(oldAppUrl), scope, status: 'ready' }; @@ -113,14 +111,12 @@ function fromAspire() { let found = false; let appUrl; - let oldAppUrl; let apiUrl; for (const resource of resources) { const app = isAppResource(resource); - const oldApp = isOldAppResource(resource); const api = isApiResource(resource); - if (!app && !oldApp && !api) { + if (!app && !api) { continue; } @@ -135,14 +131,12 @@ function fromAspire() { const urls = (resource.urls ?? []).filter((url) => /^https?:\/\//i.test(url.url ?? '')); if (app) { appUrl ??= (urls.find((url) => /open app$/i.test(url.displayName ?? '')) ?? urls[0])?.url; - } else if (oldApp) { - oldAppUrl ??= (urls.find((url) => /old/i.test(url.displayName ?? '')) ?? urls[0])?.url; } else if (api) { apiUrl ??= (urls.find((url) => /open api|api/i.test(url.displayName ?? '')) ?? urls[0])?.url; } } - return { apiUrl, appUrl, found, oldAppUrl }; + return { apiUrl, appUrl, found }; } if (process.argv[1] && pathToFileURL(process.argv[1]).href === import.meta.url) { diff --git a/src/Exceptionless.Web/ClientApp/src/hooks.client.ts b/src/Exceptionless.Web/ClientApp/src/hooks.client.ts index 7dc20473c0..ab9b6d7df2 100644 --- a/src/Exceptionless.Web/ClientApp/src/hooks.client.ts +++ b/src/Exceptionless.Web/ClientApp/src/hooks.client.ts @@ -4,6 +4,8 @@ import { dev } from '$app/environment'; import { page } from '$app/state'; import { env } from '$env/dynamic/public'; import { configureSessions } from '$features/auth/exceptionless-session'; +import { getHttpsRedirectUrl } from '$features/navigation/https-redirect'; +import { canonicalAppUrl } from '$features/navigation/legacy-links'; import { normalizePath, normalizeRouteId } from '$lib/telemetry'; import { installSvelteEffectDepthDiagnostics } from '$lib/telemetry/svelte-effect-depth-diagnostics'; import { Exceptionless, guid, toError } from '@exceptionless/browser'; @@ -28,7 +30,26 @@ if (PUBLIC_EXCEPTIONLESS_SERVER_URL) { env.PUBLIC_EXCEPTIONLESS_SERVER_URL = PUBLIC_EXCEPTIONLESS_SERVER_URL; } +function normalizeCurrentAppUrl() { + const canonical = canonicalAppUrl(new URL(window.location.href)); + if (canonical.href !== window.location.href) { + window.history.replaceState(window.history.state, '', canonical); + } +} + export const init: ClientInit = async () => { + const httpsDestination = getHttpsRedirectUrl(new URL(window.location.href), env.PUBLIC_ENABLE_SSL); + if (httpsDestination) { + window.location.replace(httpsDestination.href); + // Keep the router, authentication, and telemetry from starting on the HTTP page. + await new Promise(() => {}); + return; + } + + normalizeCurrentAppUrl(); + // Normalize existing entries before the router reads them; cancelling popstate would undo Back/Forward. + window.addEventListener('popstate', normalizeCurrentAppUrl, { capture: true }); + if (!env.PUBLIC_EXCEPTIONLESS_API_KEY) { return; } diff --git a/src/Exceptionless.Web/ClientApp/src/hooks.ts b/src/Exceptionless.Web/ClientApp/src/hooks.ts new file mode 100644 index 0000000000..604ad1bc5f --- /dev/null +++ b/src/Exceptionless.Web/ClientApp/src/hooks.ts @@ -0,0 +1,8 @@ +import type { Reroute } from '@sveltejs/kit'; + +import { canonicalAppUrl, isSignInCallback } from '$features/navigation/legacy-links'; + +export const reroute: Reroute = ({ url }) => { + const canonical = canonicalAppUrl(url); + return isSignInCallback(canonical) ? '/oauth/callback' : canonical.pathname; +}; diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/assistant/assistant-links.test.ts b/src/Exceptionless.Web/ClientApp/src/lib/features/assistant/assistant-links.test.ts index 233056f910..da38de9ea6 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/assistant/assistant-links.test.ts +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/assistant/assistant-links.test.ts @@ -1,6 +1,6 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'; -const paths = vi.hoisted(() => ({ base: '/next' })); +const paths = vi.hoisted(() => ({ base: '' })); vi.mock('$app/paths', () => ({ resolve: (path: string) => `${paths.base}${path}` })); vi.mock('$app/state', () => ({ page: { url: new URL('https://exceptionless.local/') } })); @@ -10,7 +10,7 @@ import type { AssistantToolActivity } from './models'; import { addAssistantResourceLinks, normalizeAssistantUrl } from './assistant-links'; beforeEach(() => { - paths.base = '/next'; + paths.base = ''; }); function toolResult(items: unknown[]): AssistantToolActivity { @@ -25,18 +25,16 @@ function toolResult(items: unknown[]): AssistantToolActivity { describe('normalizeAssistantUrl', () => { it('converts absolute Exceptionless app routes to same-origin paths', () => { - expect(normalizeAssistantUrl('https://exceptionless.local/next/stack/stack-id?mode=summary#event', 'href')).toBe( - '/next/stack/stack-id?mode=summary#event' - ); + expect(normalizeAssistantUrl('https://exceptionless.local/stack/stack-id?mode=summary#event', 'href')).toBe('/stack/stack-id?mode=summary#event'); }); it('leaves relative and genuinely external links unchanged', () => { - expect(normalizeAssistantUrl('/next/stack/stack-id', 'href')).toBe('/next/stack/stack-id'); + expect(normalizeAssistantUrl('/stack/stack-id', 'href')).toBe('/stack/stack-id'); expect(normalizeAssistantUrl('https://docs.exceptionless.com/product/errors', 'href')).toBe('https://docs.exceptionless.com/product/errors'); }); it('does not rewrite image sources', () => { - const source = 'https://example.com/next/assets/chart.png'; + const source = 'https://example.com/assets/chart.png'; expect(normalizeAssistantUrl(source, 'src')).toBe(source); }); @@ -49,8 +47,9 @@ describe('normalizeAssistantUrl', () => { expect(normalizeAssistantUrl('/stack/stack-id', 'href')).toBe('/stack/stack-id'); }); - it('does not confuse a similar prefix with the app base', () => { - expect(normalizeAssistantUrl('https://example.com/nextdoor/stack/stack-id', 'href')).toBe('https://example.com/nextdoor/stack/stack-id'); + it('does not confuse a similar prefix with an explicit app base', () => { + paths.base = '/custom'; + expect(normalizeAssistantUrl('https://example.com/customdoor/stack/stack-id', 'href')).toBe('https://example.com/customdoor/stack/stack-id'); }); }); @@ -81,15 +80,15 @@ describe('addAssistantResourceLinks', () => { **Connection refused (localhost:9200)** is the most active stack.`; const result = addAssistantResourceLinks(content, [ - toolResult([{ id: 'stack-1', title: 'Connection refused (localhost:9200)', webUrl: '/next/stack/stack-1' }]) + toolResult([{ id: 'stack-1', title: 'Connection refused (localhost:9200)', webUrl: '/stack/stack-1' }]) ]); - expect(result).toContain('| Error | [Connection refused (localhost:9200)](/next/stack/stack-1) |'); - expect(result).toContain('**[Connection refused (localhost:9200)](/next/stack/stack-1)**'); + expect(result).toContain('| Error | [Connection refused (localhost:9200)](/stack/stack-1) |'); + expect(result).toContain('**[Connection refused (localhost:9200)](/stack/stack-1)**'); }); it('preserves existing links and code examples', () => { - const content = `[Timeout expired](/next/stack/existing) is already linked. + const content = `[Timeout expired](/stack/existing) is already linked. \`Timeout expired\` @@ -97,13 +96,13 @@ describe('addAssistantResourceLinks', () => { Timeout expired \`\`\``; - expect(addAssistantResourceLinks(content, [toolResult([{ title: 'Timeout expired', webUrl: '/next/stack/stack-1' }])])).toBe(content); + expect(addAssistantResourceLinks(content, [toolResult([{ title: 'Timeout expired', webUrl: '/stack/stack-1' }])])).toBe(content); }); it('preserves complete inline-link destinations with balanced parentheses', () => { const content = '[docs](https://example.test/(guide)/API)'; - expect(addAssistantResourceLinks(content, [toolResult([{ name: 'API', webUrl: '/next/project/api' }])])).toBe(content); + expect(addAssistantResourceLinks(content, [toolResult([{ name: 'API', webUrl: '/project/api' }])])).toBe(content); }); it('preserves resource labels inside indented code blocks', () => { @@ -111,9 +110,9 @@ Timeout expired Use API.`; - expect(addAssistantResourceLinks(content, [toolResult([{ name: 'API', webUrl: '/next/project/api' }])])).toBe(` API.connect() + expect(addAssistantResourceLinks(content, [toolResult([{ name: 'API', webUrl: '/project/api' }])])).toBe(` API.connect() -Use [API](/next/project/api).`); +Use [API](/project/api).`); }); it('preserves indented code nested inside blockquotes', () => { @@ -121,77 +120,75 @@ Use [API](/next/project/api).`); Use API.`; - expect(addAssistantResourceLinks(content, [toolResult([{ name: 'API', webUrl: '/next/project/api' }])])).toBe(`> API.connect() + expect(addAssistantResourceLinks(content, [toolResult([{ name: 'API', webUrl: '/project/api' }])])).toBe(`> API.connect() -Use [API](/next/project/api).`); +Use [API](/project/api).`); }); it('preserves multi-backtick code spans containing shorter backtick runs', () => { const content = '``code ` API`` and API.'; - expect(addAssistantResourceLinks(content, [toolResult([{ name: 'API', webUrl: '/next/project/api' }])])).toBe( - '``code ` API`` and [API](/next/project/api).' - ); + expect(addAssistantResourceLinks(content, [toolResult([{ name: 'API', webUrl: '/project/api' }])])).toBe('``code ` API`` and [API](/project/api).'); }); it('preserves fenced code when the delimiter occurs mid-line', () => { const content = ['```ts', 'const marker = "```";', 'API.connect()', '```', '', 'Use API.'].join('\n'); - const expected = ['```ts', 'const marker = "```";', 'API.connect()', '```', '', 'Use [API](/next/project/api).'].join('\n'); + const expected = ['```ts', 'const marker = "```";', 'API.connect()', '```', '', 'Use [API](/project/api).'].join('\n'); - expect(addAssistantResourceLinks(content, [toolResult([{ name: 'API', webUrl: '/next/project/api' }])])).toBe(expected); + expect(addAssistantResourceLinks(content, [toolResult([{ name: 'API', webUrl: '/project/api' }])])).toBe(expected); }); it('preserves fenced code nested inside blockquotes', () => { const content = ['> ```ts', '> API.connect()', '> ```', '', 'Use API.'].join('\n'); - const expected = ['> ```ts', '> API.connect()', '> ```', '', 'Use [API](/next/project/api).'].join('\n'); + const expected = ['> ```ts', '> API.connect()', '> ```', '', 'Use [API](/project/api).'].join('\n'); - expect(addAssistantResourceLinks(content, [toolResult([{ name: 'API', webUrl: '/next/project/api' }])])).toBe(expected); + expect(addAssistantResourceLinks(content, [toolResult([{ name: 'API', webUrl: '/project/api' }])])).toBe(expected); }); it('preserves tilde-fenced code nested inside lists', () => { const content = ['- ~~~ts', ' API.connect()', ' ~~~', '', 'Use API.'].join('\n'); - const expected = ['- ~~~ts', ' API.connect()', ' ~~~', '', 'Use [API](/next/project/api).'].join('\n'); + const expected = ['- ~~~ts', ' API.connect()', ' ~~~', '', 'Use [API](/project/api).'].join('\n'); - expect(addAssistantResourceLinks(content, [toolResult([{ name: 'API', webUrl: '/next/project/api' }])])).toBe(expected); + expect(addAssistantResourceLinks(content, [toolResult([{ name: 'API', webUrl: '/project/api' }])])).toBe(expected); }); it('preserves resource labels inside email addresses', () => { const content = 'Contact API@example.com before opening API.'; - expect(addAssistantResourceLinks(content, [toolResult([{ name: 'API', webUrl: '/next/project/api' }])])).toBe( - 'Contact API@example.com before opening [API](/next/project/api).' + expect(addAssistantResourceLinks(content, [toolResult([{ name: 'API', webUrl: '/project/api' }])])).toBe( + 'Contact API@example.com before opening [API](/project/api).' ); }); it('preserves resource labels inside bare hostnames', () => { const content = 'Connect to API.example.com before opening API.'; - expect(addAssistantResourceLinks(content, [toolResult([{ name: 'API', webUrl: '/next/project/api' }])])).toBe( - 'Connect to API.example.com before opening [API](/next/project/api).' + expect(addAssistantResourceLinks(content, [toolResult([{ name: 'API', webUrl: '/project/api' }])])).toBe( + 'Connect to API.example.com before opening [API](/project/api).' ); }); it('preserves reference-style and shortcut links', () => { const content = `[Timeout expired][stack], [Timeout expired][], and [Timeout expired] are already linked. -[stack]: /next/stack/existing`; +[stack]: /stack/existing`; - expect(addAssistantResourceLinks(content, [toolResult([{ title: 'Timeout expired', webUrl: '/next/stack/stack-1' }])])).toBe(content); + expect(addAssistantResourceLinks(content, [toolResult([{ title: 'Timeout expired', webUrl: '/stack/stack-1' }])])).toBe(content); }); it('links only complete resource labels', () => { const content = 'APIClient uses the API project.'; - expect(addAssistantResourceLinks(content, [toolResult([{ name: 'API', webUrl: '/next/project/api' }])])).toBe( - 'APIClient uses the [API](/next/project/api) project.' + expect(addAssistantResourceLinks(content, [toolResult([{ name: 'API', webUrl: '/project/api' }])])).toBe( + 'APIClient uses the [API](/project/api) project.' ); }); it('preserves labels inside bare absolute and relative URLs', () => { - const content = 'See https://example.test/API and /next/project/API before opening API.'; + const content = 'See https://example.test/API and /project/API before opening API.'; - expect(addAssistantResourceLinks(content, [toolResult([{ name: 'API', webUrl: '/next/project/api' }])])).toBe( - 'See https://example.test/API and /next/project/API before opening [API](/next/project/api).' + expect(addAssistantResourceLinks(content, [toolResult([{ name: 'API', webUrl: '/project/api' }])])).toBe( + 'See https://example.test/API and /project/API before opening [API](/project/api).' ); }); @@ -201,15 +198,15 @@ Use [API](/next/project/api).`); | Name | Count | | --- | --- |`; - expect(addAssistantResourceLinks(content, [toolResult([{ name: '-', webUrl: '/next/project/dash' }])])).toBe(content); + expect(addAssistantResourceLinks(content, [toolResult([{ name: '-', webUrl: '/project/dash' }])])).toBe(content); }); it('does not guess when duplicate titles refer to different resources', () => { const content = 'Investigate Timeout expired.'; const tools = [ toolResult([ - { title: 'Timeout expired', webUrl: '/next/stack/stack-1' }, - { title: 'Timeout expired', webUrl: '/next/stack/stack-2' } + { title: 'Timeout expired', webUrl: '/stack/stack-1' }, + { title: 'Timeout expired', webUrl: '/stack/stack-2' } ]) ]; @@ -221,7 +218,7 @@ Use [API](/next/project/api).`); const tools = [ toolResult([ { - data: { title: 'this title', webUrl: '/next/stack/untrusted' }, + data: { title: 'this title', webUrl: '/stack/untrusted' }, title: 'External title', webUrl: 'https://example.com/stack/1' } diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/assistant/assistant-request.test.ts b/src/Exceptionless.Web/ClientApp/src/lib/features/assistant/assistant-request.test.ts index 96025ae508..3f844343cf 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/assistant/assistant-request.test.ts +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/assistant/assistant-request.test.ts @@ -21,12 +21,12 @@ describe('createAssistantChatRequest', () => { isSuggestedAction: true, role: 'user', suggestedActionLabel: 'Follow up', - suggestedActionPath: '/next/stack/source-stack', + suggestedActionPath: '/stack/source-stack', tools: [] } ]; - expect(createAssistantChatRequest(messages, 'conversation-id', 'organization-id', '/next/stack/stack-id', 'project-id')).toEqual({ + expect(createAssistantChatRequest(messages, 'conversation-id', 'organization-id', '/stack/stack-id', 'project-id')).toEqual({ conversation_id: 'conversation-id', messages: [ { content: 'First question', role: 'user' }, @@ -36,11 +36,11 @@ describe('createAssistantChatRequest', () => { is_suggested_action: true, role: 'user', suggested_action_label: 'Follow up', - suggested_action_path: '/next/stack/source-stack' + suggested_action_path: '/stack/source-stack' } ], organization_id: 'organization-id', - path: '/next/stack/stack-id', + path: '/stack/stack-id', project_id: 'project-id' }); }); diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/assistant/components/assistant-message.svelte.test.ts b/src/Exceptionless.Web/ClientApp/src/lib/features/assistant/components/assistant-message.svelte.test.ts index ac3494b45d..6d7ca7ed0e 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/assistant/components/assistant-message.svelte.test.ts +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/assistant/components/assistant-message.svelte.test.ts @@ -24,7 +24,7 @@ describe('AssistantMessage', () => { { id: 'stack-1', title: 'Timeout expired', - webUrl: '/next/stack/stack-1' + webUrl: '/stack/stack-1' } ] }, @@ -40,7 +40,7 @@ describe('AssistantMessage', () => { expect(screen.getByLabelText('Exie').textContent).toContain('Timeout expired is the best issue to investigate next.'); expect(screen.getByText('Searched error stacks')).not.toBeNull(); const stackLink = screen.getByRole('link', { name: 'Timeout expired' }); - expect(stackLink.getAttribute('href')).toBe('/next/stack/stack-1'); + expect(stackLink.getAttribute('href')).toBe('/stack/stack-1'); const linkClasses = stackLink.className.split(/\s+/); expect(linkClasses).toContain('text-foreground'); expect(linkClasses).not.toContain('text-primary'); diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/assistant/components/assistant-panel.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/assistant/components/assistant-panel.svelte index 370ce56395..b973ab0337 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/assistant/components/assistant-panel.svelte +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/assistant/components/assistant-panel.svelte @@ -6,6 +6,7 @@ import { Button } from '$comp/ui/button'; import * as Sheet from '$comp/ui/sheet'; import { accessToken } from '$features/auth/index.svelte'; + import { getApiUrl } from '$shared/api/urls'; import ArrowDown from '@lucide/svelte/icons/arrow-down'; import Bot from '@lucide/svelte/icons/bot'; import CircleAlert from '@lucide/svelte/icons/circle-alert'; @@ -473,7 +474,7 @@ try { await scrollToLatest('smooth', true); - const response = await fetch('/api/v2/assistant/chat', { + const response = await fetch(getApiUrl('assistant/chat'), { body: JSON.stringify(request), headers: { Authorization: `Bearer ${accessToken.current}`, diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/assistant/components/assistant-panel.svelte.test.ts b/src/Exceptionless.Web/ClientApp/src/lib/features/assistant/components/assistant-panel.svelte.test.ts index 2c016c44ee..6ae129fc66 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/assistant/components/assistant-panel.svelte.test.ts +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/assistant/components/assistant-panel.svelte.test.ts @@ -2,6 +2,9 @@ import { resolve } from '$app/paths'; import { fireEvent, render, screen, waitFor } from '@testing-library/svelte'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; +const { env } = vi.hoisted(() => ({ env: { PUBLIC_BASE_URL: '' } })); +vi.mock('$env/dynamic/public', () => ({ env })); + vi.mock('$features/auth/index.svelte', () => ({ accessToken: { current: 'access-token' } })); vi.mock('$features/billing/stripe.svelte', () => ({ isStripeEnabled: () => true })); vi.mock('katex/dist/katex.min.css', () => ({})); @@ -17,6 +20,7 @@ import AssistantPanel from './assistant-panel.svelte'; describe('AssistantPanel', () => { beforeEach(() => { + env.PUBLIC_BASE_URL = ''; HTMLElement.prototype.scrollIntoView = vi.fn(); HTMLElement.prototype.scrollTo = vi.fn(); }); @@ -36,13 +40,13 @@ describe('AssistantPanel', () => { }); it('records opens only when becoming visible, not when typing or changing views', async () => { - const props = { open: true, organizationId: 'organization-1', path: '/next/stack' }; + const props = { open: true, organizationId: 'organization-1', path: '/stack' }; const view = render(AssistantPanel, { props }); await screen.findByRole('textbox', { name: 'Message Exie' }); expect(submitFeatureUsage.mock.calls.filter(([feature]) => feature === 'assistant.Opened')).toHaveLength(1); await fireEvent.input(screen.getByRole('textbox', { name: 'Message Exie' }), { target: { value: 'Unsent draft' } }); - await view.rerender({ ...props, mode: 'page', path: '/next/event' }); + await view.rerender({ ...props, mode: 'page', path: '/event' }); await waitFor(() => expect(submitFeatureUsage).toHaveBeenCalledWith('assistant.ViewChanged', expect.anything())); expect(submitFeatureUsage.mock.calls.filter(([feature]) => feature === 'assistant.Opened')).toHaveLength(1); @@ -52,6 +56,15 @@ describe('AssistantPanel', () => { await waitFor(() => expect(submitFeatureUsage.mock.calls.filter(([feature]) => feature === 'assistant.Opened')).toHaveLength(2)); }); + it('streams responses from the configured API origin', async () => { + env.PUBLIC_BASE_URL = 'https://localhost:8443'; + const fetchMock = vi.fn().mockResolvedValue(new Response('{"type":"text_delta","text":"Configured origin response"}\n{"type":"done"}\n')); + vi.stubGlobal('fetch', fetchMock); + render(AssistantPanel, { props: { open: true, organizationId: 'organization-1', promptRequest: { id: 'origin-prompt', prompt: 'Hello' } } }); + await screen.findByText('Configured origin response'); + expect(fetchMock.mock.calls[0]?.[0]).toBe('https://localhost:8443/api/v2/assistant/chat'); + }); + it('correlates message outcomes and feedback without recording chat text', async () => { const fetchMock = vi.fn().mockResolvedValue(new Response('{"type":"text_delta","text":"The answer"}\n{"type":"done"}\n')); vi.stubGlobal('fetch', fetchMock); @@ -59,7 +72,7 @@ describe('AssistantPanel', () => { props: { open: true, organizationId: 'organization-1', - path: '/next/stack/stack-1?filter=private', + path: '/stack/stack-1?filter=private', promptRequest: { id: 'prompt-request', prompt: 'My question' } } }); @@ -71,7 +84,7 @@ describe('AssistantPanel', () => { const prompt = eventData('assistant.MessageSent'); expect(prompt.conversation_id).toMatch(/^[0-9a-f]{32}$/); expect(JSON.parse(fetchMock.mock.calls[0]?.[1]?.body as string).conversation_id).toBe(prompt.conversation_id); - expect(prompt).toMatchObject({ organization_id: 'organization-1', path: '/next/stack/stack-1', prompt_source: 'queued', role: 'user' }); + expect(prompt).toMatchObject({ organization_id: 'organization-1', path: '/stack/stack-1', prompt_source: 'queued', role: 'user' }); expect(eventData('assistant.ResponseCompleted')).toMatchObject({ assistant_message_id: prompt.assistant_message_id, conversation_id: prompt.conversation_id, @@ -340,7 +353,7 @@ describe('AssistantPanel', () => { const onCollapse = vi.fn(); render(AssistantPanel, { props: { - collapseHref: '/next/stack', + collapseHref: '/stack', mode: 'page', onCollapse, open: false, @@ -351,7 +364,7 @@ describe('AssistantPanel', () => { expect(screen.getByRole('log', { name: 'Conversation with Exie' })).toBeTruthy(); expect(screen.getByRole('button', { name: 'Clear conversation' }).hasAttribute('disabled')).toBe(true); const collapseLink = screen.getByRole('link', { name: 'Collapse Exie to side panel' }); - expect(collapseLink.getAttribute('href')).toBe('/next/stack'); + expect(collapseLink.getAttribute('href')).toBe('/stack'); collapseLink.addEventListener('click', (event) => event.preventDefault()); await fireEvent.click(collapseLink); @@ -368,18 +381,18 @@ describe('AssistantPanel', () => { const view = render(AssistantPanel, { props: { - expandHref: '/next/exie?from=%2Fnext%2Fstack', + expandHref: '/exie?from=%2Fstack', open: true, organizationId: 'organization-1', promptRequest: { id: 'request-1', prompt: 'Keep this conversation.' } } }); - expect(screen.getByRole('link', { name: 'Expand Exie to full page' }).getAttribute('href')).toBe('/next/exie?from=%2Fnext%2Fstack'); + expect(screen.getByRole('link', { name: 'Expand Exie to full page' }).getAttribute('href')).toBe('/exie?from=%2Fstack'); expect(await screen.findByText('The conversation is still here.')).toBeTruthy(); await view.rerender({ - collapseHref: '/next/stack', + collapseHref: '/stack', mode: 'page', open: true, organizationId: 'organization-1', @@ -439,7 +452,7 @@ describe('AssistantPanel', () => { props: { open: true, organizationId: 'organization-1', - path: '/next/stack/stack-a', + path: '/stack/stack-a', promptRequest: { id: 'request-1', prompt: 'Analyze this stack.' } } }); @@ -448,7 +461,7 @@ describe('AssistantPanel', () => { await view.rerender({ open: true, organizationId: 'organization-1', - path: '/next/stack/stack-b', + path: '/stack/stack-b', promptRequest: { id: 'request-1', prompt: 'Analyze this stack.' } }); await fireEvent.click(action); @@ -459,8 +472,8 @@ describe('AssistantPanel', () => { messages: Array<{ suggested_action_path?: string }>; path: string; }; - expect(payload.path).toBe('/next/stack/stack-b'); - expect(payload.messages.at(-1)?.suggested_action_path).toBe('/next/stack/stack-a'); + expect(payload.path).toBe('/stack/stack-b'); + expect(payload.messages.at(-1)?.suggested_action_path).toBe('/stack/stack-a'); }); it('hides tool calls by default and toggles them locally with /tools', async () => { diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/auth/exceptionless-session.ts b/src/Exceptionless.Web/ClientApp/src/lib/features/auth/exceptionless-session.ts index 93ca39bf51..2931b40ffe 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/auth/exceptionless-session.ts +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/auth/exceptionless-session.ts @@ -79,7 +79,6 @@ export async function setUserIdentity(userId: string, userName?: string): Promis /** * Submits a feature usage event for telemetry tracking. - * Mirrors the legacy Angular $ExceptionlessClient.submitFeatureUsage pattern. */ export async function submitFeatureUsage(feature: string, properties?: Record): Promise { const Exceptionless = await getExceptionless(); diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/auth/navigation.test.ts b/src/Exceptionless.Web/ClientApp/src/lib/features/auth/navigation.test.ts index 48908e93af..91f165b666 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/auth/navigation.test.ts +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/auth/navigation.test.ts @@ -2,8 +2,8 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'; const { goto, page, paths } = vi.hoisted(() => ({ goto: vi.fn(), - page: { url: new URL('https://localhost/next/') }, - paths: { base: '/next' } + page: { url: new URL('https://localhost/') }, + paths: { base: '' } })); vi.mock('$app/navigation', () => ({ goto })); @@ -19,41 +19,41 @@ import { gotoLogin } from './index.svelte'; describe('gotoLogin', () => { beforeEach(() => { goto.mockReset(); - paths.base = '/next'; + paths.base = ''; }); it.each([ - '/next/account/verify?token=verification%2Btoken%26value', - '/next/account/notifications?project=project-id&from=email#project-notifications', - '/next/organization/organization-id/billing?changePlan=true#billing', - '/next/event?filter=message%3A%22A%26B%22&tag=one&tag=two#details', - '/next/stack/stack-id' + '/account/verify?token=verification%2Btoken%26value', + '/account/notifications?project=project-id&from=email#project-notifications', + '/organization/organization-id/billing?changePlan=true#billing', + '/event?filter=message%3A%22A%26B%22&tag=one&tag=two#details', + '/stack/stack-id' ])('preserves the complete return destination %s', async (destination) => { page.url = new URL(destination, 'https://localhost'); await gotoLogin(); - expect(goto).toHaveBeenCalledExactlyOnceWith(`/next/login?redirect=${encodeURIComponent(destination)}`, { replaceState: true }); + expect(goto).toHaveBeenCalledExactlyOnceWith(`/login?redirect=${encodeURIComponent(destination)}`, { replaceState: true }); const loginUrl = new URL(goto.mock.calls[0]![0], page.url.origin); expect(loginUrl.searchParams.get('redirect')).toBe(destination); expect([...loginUrl.searchParams.keys()]).toEqual(['redirect']); expect(loginUrl.hash).toBe(''); }); - it.each(['/next/', '/next/login', '/next/login/'])('does not nest a login redirect from %s', async (path) => { + it.each(['/', '/login', '/login/'])('does not nest a login redirect from %s', async (path) => { page.url = new URL(path, 'https://localhost'); await gotoLogin(); - expect(goto).toHaveBeenCalledExactlyOnceWith('/next/login', { replaceState: true }); + expect(goto).toHaveBeenCalledExactlyOnceWith('/login', { replaceState: true }); }); - it('uses the configured login route when the app is hosted at root', async () => { - paths.base = ''; - page.url = new URL('https://localhost/login'); + it('uses a configured login route under an explicit base', async () => { + paths.base = '/custom'; + page.url = new URL('https://localhost/custom/login'); await gotoLogin(); - expect(goto).toHaveBeenCalledExactlyOnceWith('/login', { replaceState: true }); + expect(goto).toHaveBeenCalledExactlyOnceWith('/custom/login', { replaceState: true }); }); }); diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/stack-trace/source-map-status.svelte.test.ts b/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/stack-trace/source-map-status.svelte.test.ts index 69a4d199a2..27bd8870b3 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/stack-trace/source-map-status.svelte.test.ts +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/stack-trace/source-map-status.svelte.test.ts @@ -33,7 +33,7 @@ describe('SourceMapStatus', () => { expect(screen.getByText('https://cdn.example.com/assets/app.min.js')).toBeTruthy(); expect(screen.getByText(/downloaded source map is invalid or unsupported/i)).toBeTruthy(); - expect(screen.getByRole('link', { name: 'Manage source maps' }).getAttribute('href')).toBe(`/next/project/${projectId}/source-maps`); + expect(screen.getByRole('link', { name: 'Manage source maps' }).getAttribute('href')).toBe(`/project/${projectId}/source-maps`); }); it('does not render without failure metadata', () => { diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/summary/summary.svelte.test.ts b/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/summary/summary.svelte.test.ts index 5054a68281..8d5d443361 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/summary/summary.svelte.test.ts +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/summary/summary.svelte.test.ts @@ -19,7 +19,7 @@ describe('Summary', () => { render(Summary, { showStatus: false, showType: false, summary }); - expect(screen.getByRole('link', { name: 'Anonymous session' }).getAttribute('href')).toBe('/next/event/anonymous-session-event'); + expect(screen.getByRole('link', { name: 'Anonymous session' }).getAttribute('href')).toBe('/event/anonymous-session-event'); }); it('links an event summary to that event details page', () => { @@ -38,7 +38,7 @@ describe('Summary', () => { render(Summary, { showStatus: false, summary }); - expect(screen.getByRole('link', { name: summary.data.Message }).getAttribute('href')).toBe('/next/event/event-id'); + expect(screen.getByRole('link', { name: summary.data.Message }).getAttribute('href')).toBe('/event/event-id'); }); it('renders an event summary without an internal link when linking is disabled', () => { @@ -83,6 +83,6 @@ describe('Summary', () => { render(Summary, { showStatus: false, summary }); - expect(screen.getByRole('link', { name: summary.title }).getAttribute('href')).toBe('/next/stack/stack-id'); + expect(screen.getByRole('link', { name: summary.title }).getAttribute('href')).toBe('/stack/stack-id'); }); }); diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/tours/investigation-list.svelte.test.ts b/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/tours/investigation-list.svelte.test.ts index 8ddf1ea770..9c13d5568a 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/tours/investigation-list.svelte.test.ts +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/tours/investigation-list.svelte.test.ts @@ -23,7 +23,7 @@ describe('InvestigationListTour', () => { ); target = document.createElement('div'); target.dataset.tour = 'event-list'; - target.innerHTML = 'Error'; + target.innerHTML = 'Error'; target.querySelector('tr')!.scrollIntoView = vi.fn(); filters = document.createElement('button'); filters.dataset.tour = 'event-filters'; @@ -64,7 +64,7 @@ describe('InvestigationListTour', () => { expect(screen.queryByRole('button', { name: 'Open error' })).toBeNull(); // Act - target.querySelector('a')!.setAttribute('href', '/next/event/loaded-error'); + target.querySelector('a')!.setAttribute('href', '/event/loaded-error'); await component.rerender({ firstErrorId: 'loaded-error', onOpenError }); await fireEvent.click(await screen.findByRole('button', { name: 'Open error' })); @@ -95,8 +95,8 @@ describe('InvestigationListTour', () => { // Arrange: the event list has a newer log before the selected error. const errorId = '507f1f77bcf86cd799439012'; target.innerHTML = ` - Newer log - Checkout error + Newer log + Checkout error `; const errorRow = target.querySelectorAll('tr')[1]; const onOpenError = vi.fn(); diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/views/session-events-navigation.test.ts b/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/views/session-events-navigation.test.ts index ae7c45f935..9f80fdd887 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/views/session-events-navigation.test.ts +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/views/session-events-navigation.test.ts @@ -26,51 +26,51 @@ describe('getSessionEventsPath', () => { }); it('uses the Events All saved view when it exists', () => { - expect(getSessionEventsPath([createSavedView()])).toBe('/next/event/all'); + expect(getSessionEventsPath([createSavedView()])).toBe('/event/all'); }); it('uses the immutable predefined key after the Events All view is renamed', () => { - expect(getSessionEventsPath([createSavedView({ name: 'Everything', slug: 'everything' })])).toBe('/next/event/everything'); + expect(getSessionEventsPath([createSavedView({ name: 'Everything', slug: 'everything' })])).toBe('/event/everything'); }); it('prefers the shared All view when a private view already owns the all slug', () => { const privateAll = createSavedView({ id: 'private-all', user_id: 'user-id' }); const sharedAll = createSavedView({ id: 'shared-all', slug: 'all-2' }); - expect(getSessionEventsPath([privateAll, sharedAll])).toBe('/next/event/all-2'); + expect(getSessionEventsPath([privateAll, sharedAll])).toBe('/event/all-2'); }); it('does not use a private All view when no shared All view exists', () => { - expect(getSessionEventsPath([createSavedView({ user_id: 'user-id' })])).toBe('/next/event'); + expect(getSessionEventsPath([createSavedView({ user_id: 'user-id' })])).toBe('/event'); }); it('falls back to the generic Events route when All is unavailable', () => { - expect(getSessionEventsPath([createSavedView({ name: 'Errors', predefined_key: 'events:errors', slug: 'errors' })])).toBe('/next/event'); + expect(getSessionEventsPath([createSavedView({ name: 'Errors', predefined_key: 'events:errors', slug: 'errors' })])).toBe('/event'); }); it('does not mistake an unrelated all-N slug for the All view', () => { - expect(getSessionEventsPath([createSavedView({ name: 'Everything', predefined_key: undefined, slug: 'all-2' })])).toBe('/next/event'); + expect(getSessionEventsPath([createSavedView({ name: 'Everything', predefined_key: undefined, slug: 'all-2' })])).toBe('/event'); }); it('supports a legacy unkeyed shared All view', () => { - expect(getSessionEventsPath([createSavedView({ predefined_key: undefined })])).toBe('/next/event/all'); + expect(getSessionEventsPath([createSavedView({ predefined_key: undefined })])).toBe('/event/all'); }); it('does not use a differently keyed view that was renamed to All', () => { - expect(getSessionEventsPath([createSavedView({ predefined_key: 'events:errors' })])).toBe('/next/event'); + expect(getSessionEventsPath([createSavedView({ predefined_key: 'events:errors' })])).toBe('/event'); }); it('does not use an All view belonging to another resource', () => { - expect(getSessionEventsPath([createSavedView({ view_type: 'stacks' })])).toBe('/next/event'); + expect(getSessionEventsPath([createSavedView({ view_type: 'stacks' })])).toBe('/event'); }); }); describe('getSessionEventsHref', () => { it('keeps the saved view path while replacing every saved filter with the session', () => { - const href = getSessionEventsHref('/next/event/all-2', 'session-id'); + const href = getSessionEventsHref('/event/all-2', 'session-id'); const url = new URL(href!, 'https://example.test'); - expect(url.pathname).toBe('/next/event/all-2'); + expect(url.pathname).toBe('/event/all-2'); expect(Object.fromEntries(url.searchParams)).toEqual({ bot: '', filter: '', @@ -90,6 +90,6 @@ describe('getSessionEventsHref', () => { it('does not enable navigation without both a path and session id', () => { expect(getSessionEventsHref(undefined, 'session-id')).toBeUndefined(); - expect(getSessionEventsHref('/next/event', undefined)).toBeUndefined(); + expect(getSessionEventsHref('/event', undefined)).toBeUndefined(); }); }); diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/events/premium-filter.test.ts b/src/Exceptionless.Web/ClientApp/src/lib/features/events/premium-filter.test.ts index 17a5584183..8f71f40eda 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/events/premium-filter.test.ts +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/events/premium-filter.test.ts @@ -84,11 +84,11 @@ describe('filterUsesPremiumFeatures', () => { }); describe('getSearchResourceForPathname', () => { - it.each(['/stack', '/next/stack/saved-view'])('identifies stack-mode event search routes: %s', (pathname) => { + it.each(['/stack', '/stack/saved-view'])('identifies stack-mode event search routes: %s', (pathname) => { expect(getSearchResourceForPathname(pathname)).toBe('event-stack'); }); - it.each(['/project/537650f3b77efe23a47914f4/stacks', '/next/project/537650f3b77efe23a47914f4/stacks/537650f3b77efe23a47914f5'])( + it.each(['/project/537650f3b77efe23a47914f4/stacks', '/project/537650f3b77efe23a47914f4/stacks/537650f3b77efe23a47914f5'])( 'identifies direct stack search routes: %s', (pathname) => { expect(getSearchResourceForPathname(pathname)).toBe('stack'); diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/intercom/intercom-shell.svelte.test.ts b/src/Exceptionless.Web/ClientApp/src/lib/features/intercom/intercom-shell.svelte.test.ts index a09ab0e06d..844cef6232 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/intercom/intercom-shell.svelte.test.ts +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/intercom/intercom-shell.svelte.test.ts @@ -9,6 +9,8 @@ import IntercomShellTestHarness from './intercom-shell.test-harness.svelte'; const intercomShowMessages = vi.hoisted(() => vi.fn()); const intercomUpdate = vi.hoisted(() => vi.fn()); +vi.mock('$env/dynamic/public', () => ({ env: {} })); + vi.mock('$features/auth/index.svelte', () => ({ accessToken: { current: 'token_123' } })); diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/intercom/updates.test.ts b/src/Exceptionless.Web/ClientApp/src/lib/features/intercom/updates.test.ts index 646684f360..fe0b1dc6bd 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/intercom/updates.test.ts +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/intercom/updates.test.ts @@ -69,11 +69,11 @@ describe('Intercom updates', () => { it('uses the normalized route ID instead of resource identifiers in the pathname', () => { const routeId = '/(app)/project/[projectId]/event/[eventId]'; - expect(getIntercomRouteKey(routeId, '/next/project/project-a/event/event-a')).toBe(routeId); - expect(getIntercomRouteKey(routeId, '/next/project/project-a/event/event-b')).toBe(routeId); + expect(getIntercomRouteKey(routeId, '/project/project-a/event/event-a')).toBe(routeId); + expect(getIntercomRouteKey(routeId, '/project/project-a/event/event-b')).toBe(routeId); }); it('falls back to the pathname when SvelteKit has no route ID', () => { - expect(getIntercomRouteKey(null, '/next/status')).toBe('/next/status'); + expect(getIntercomRouteKey(null, '/status')).toBe('/status'); }); }); diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/navigation/https-redirect.test.ts b/src/Exceptionless.Web/ClientApp/src/lib/features/navigation/https-redirect.test.ts new file mode 100644 index 0000000000..8d4701a2b7 --- /dev/null +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/navigation/https-redirect.test.ts @@ -0,0 +1,22 @@ +import { describe, expect, it } from 'vitest'; + +import { getHttpsRedirectUrl } from './https-redirect'; + +describe('configured HTTPS navigation', () => { + it.each([undefined, '', 'false'])('leaves HTTP available when EnableSsl is %s', (setting) => { + expect(getHttpsRedirectUrl(new URL('http://localhost/login'), setting)).toBeUndefined(); + }); + + it.each([ + ['http://localhost/login?redirect=%2Fstack%2Fall#notice', 'https://localhost/login?redirect=%2Fstack%2Fall#notice'], + ['http://localhost:8080/#!/event/by-ref/order.123?project=abc', 'https://localhost:8080/#!/event/by-ref/order.123?project=abc'] + ])('upgrades %s without changing its destination', (source, expected) => { + const url = new URL(source); + expect(getHttpsRedirectUrl(url, 'true')?.href).toBe(expected); + expect(url.href).toBe(source); + }); + + it('does not redirect an HTTPS page again', () => { + expect(getHttpsRedirectUrl(new URL('https://localhost/login'), 'true')).toBeUndefined(); + }); +}); diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/navigation/https-redirect.ts b/src/Exceptionless.Web/ClientApp/src/lib/features/navigation/https-redirect.ts new file mode 100644 index 0000000000..97576ff886 --- /dev/null +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/navigation/https-redirect.ts @@ -0,0 +1,9 @@ +export function getHttpsRedirectUrl(url: URL, enableSsl: string | undefined): undefined | URL { + if (enableSsl !== 'true' || url.protocol !== 'http:') { + return; + } + + const destination = new URL(url); + destination.protocol = 'https:'; + return destination; +} diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/navigation/legacy-links.test.ts b/src/Exceptionless.Web/ClientApp/src/lib/features/navigation/legacy-links.test.ts new file mode 100644 index 0000000000..8bb71754e3 --- /dev/null +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/navigation/legacy-links.test.ts @@ -0,0 +1,52 @@ +import { describe, expect, it } from 'vitest'; + +import { canonicalAppUrl, isSignInCallback } from './legacy-links'; + +const origin = 'https://localhost:7131'; + +describe('primary legacy links', () => { + it.each([ + ['/#!/event/by-ref/order.123?project=abc', '/event/by-ref/order.123?project=abc'], + ['/#/signup?token=a%2Bb%2Fc%3D', '/signup?token=a%2Bb%2Fc%3D'], + ['/#!/account/verify?token=verify-token', '/account/verify?token=verify-token'], + ['/account/manage?projectId=abc&tab=notifications', '/account/notifications?project=abc'], + ['/organization/abc/manage?tab=billing', '/organization/abc/billing'], + ['/#!/organization/abc/upgrade', '/organization/abc/upgrade'], + ['/organization/abc/frequent', '/organization/abc/dashboard?view=stacks'], + ['/project/abc/error/timeline', '/project/abc/dashboard?type=error'], + ['/project/abc/error/frequent?time=all', '/project/abc/dashboard?time=all&type=error&view=stacks'], + ['/project/abc/error/new', '/project/abc/dashboard?type=error&view=stacks'], + ['/project/abc/manage?tab=integrations&code=slack-code', '/project/abc/integrations?code=slack-code'], + ['/#!/stack/abc/mark-fixed', '/stack/abc/mark-fixed'], + ['/#/stack/abc/ignored', '/stack/abc/ignored'], + ['/stack/abc/stop-notifications', '/stack/abc/ignored'], + ['/unknown/old/page', '/unknown/old/page'], + ['/event/abc#data', '/event/abc#data'] + ])('maps %s to %s without a second redirect', (input, expected) => { + const result = canonicalAppUrl(new URL(input, origin)); + expect(result.href).toBe(origin + expected); + expect(canonicalAppUrl(result).href).toBe(result.href); + }); + + it('retains outer query parameters in hash-router links', () => { + const result = canonicalAppUrl(new URL('/?source=email#!/signup?token=invite', origin)); + expect(result.searchParams.get('source')).toBe('email'); + expect(result.searchParams.get('token')).toBe('invite'); + }); + + it.each(['/#//evil.example/login', '/#!//evil.example/login'])('never follows a protocol-relative hash: %s', (input) => { + expect(canonicalAppUrl(new URL(input, origin)).origin).toBe(origin); + }); +}); + +describe('social sign-in callback', () => { + it.each(['/?code=secret&state=state', '/?error=access_denied', '/#access_token=secret&state=state'])('leaves the response at the origin: %s', (input) => { + const url = new URL(input, origin); + expect(isSignInCallback(url)).toBe(true); + expect(canonicalAppUrl(url).href).toBe(url.href); + }); + + it.each(['/project/abc/integrations?code=slack', '/signup?token=invite', '/#!/event/abc'])('does not intercept %s', (input) => { + expect(isSignInCallback(new URL(input, origin))).toBe(false); + }); +}); diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/navigation/legacy-links.ts b/src/Exceptionless.Web/ClientApp/src/lib/features/navigation/legacy-links.ts new file mode 100644 index 0000000000..3ae2f6ec15 --- /dev/null +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/navigation/legacy-links.ts @@ -0,0 +1,63 @@ +/** Primary public bookmarks from the old UI. Unknown routes remain unknown. */ +export function canonicalAppUrl(source: URL): URL { + const url = new URL(source); + if (url.hash.startsWith('#!/') || url.hash.startsWith('#/')) { + const route = url.hash.replace(/^#!?/, ''); + // Never interpret a protocol-relative hash as another origin. + if (!route.startsWith('//')) { + const legacy = new URL(route, url.origin); + legacy.searchParams.forEach((value, key) => url.searchParams.set(key, value)); + url.pathname = legacy.pathname; + url.hash = legacy.hash; + } + } + + const path = url.pathname.replace(/\/$/, '') || '/'; + const tab = url.searchParams.get('tab'); + + if (path === '/account/manage' && tab === 'notifications') { + url.pathname = '/account/notifications'; + url.searchParams.delete('tab'); + const projectId = url.searchParams.get('projectId'); + if (projectId) { + url.searchParams.set('project', projectId); + url.searchParams.delete('projectId'); + } + } + + const organization = /^\/organization\/([^/]+)\/(manage|frequent)$/.exec(path); + if (organization) { + const [, id, action] = organization; + if (action === 'manage' && tab === 'billing') { + url.pathname = `/organization/${id}/billing`; + url.searchParams.delete('tab'); + } else if (action === 'frequent') { + url.pathname = `/organization/${id}/dashboard`; + url.searchParams.set('view', 'stacks'); + } + } + + const project = /^\/project\/([^/]+)\/(?:(error|log|usage|session)\/)?(timeline|frequent|new|dashboard)$/.exec(path); + if (project) { + const [, id, type, view] = project; + url.pathname = `/project/${id}/dashboard`; + if (type) url.searchParams.set('type', type); + if (view === 'frequent' || view === 'new') url.searchParams.set('view', 'stacks'); + } + + if (/^\/project\/[^/]+\/manage$/.test(path) && tab === 'integrations') { + url.pathname = path.replace(/\/manage$/, '/integrations'); + url.searchParams.delete('tab'); + } + + // Action routes show a confirmation before changing stack state. + url.pathname = url.pathname.replace(/^(\/stack\/[^/]+)\/stop-notifications\/?$/, '$1/ignored'); + return url; +} + +/** Social sign-in popups return to the origin. Leave the response available to the opener. */ +export function isSignInCallback(url: URL): boolean { + if (url.pathname !== '/') return false; + const hash = new URLSearchParams(url.hash.slice(1)); + return ['code', 'error', 'access_token'].some((key) => url.searchParams.has(key) || hash.has(key)); +} diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/navigation/scoped-dashboard.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/navigation/scoped-dashboard.svelte new file mode 100644 index 0000000000..9c2fed76ee --- /dev/null +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/navigation/scoped-dashboard.svelte @@ -0,0 +1,49 @@ + + +{#if projectQuery.isError || organizationQuery.isError} + This dashboard could not be opened. Check that you still have access and try again. +{:else} + Opening dashboard… +{/if} diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/notifications/components/notifications.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/notifications/components/notifications.svelte index ea4b9b0bb3..239309d107 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/notifications/components/notifications.svelte +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/notifications/components/notifications.svelte @@ -97,7 +97,9 @@ const queryTarget = $derived(normalizeSystemNotificationTarget(currentNotificationQuery.data?.target)); const effectiveTarget = $derived(hasRealtimeSystemNotification ? systemTarget : queryTarget); - const showForModern = $derived(effectiveTarget === 'Both' || effectiveTarget === 'Modern'); + // Existing notifications and WebSocket messages retain their public target values. + // Do not surface a previously hidden banner merely because the other UI was removed. + const showSystemNotification = $derived(effectiveTarget === 'Both' || effectiveTarget === 'Modern'); const displayMessage = $derived( hasRealtimeSystemNotification ? systemMessage || fallbackMessage : currentNotificationQuery.data?.message || fallbackMessage @@ -140,7 +142,7 @@ }); -{#if displayMessage && showForModern && systemNotificationKey !== dismissedSystemNotificationKey} +{#if displayMessage && showSystemNotification && systemNotificationKey !== dismissedSystemNotificationKey} {@const LevelIcon = levelIconMap[displayLevel]} {#snippet icon()} diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/product-tours/catalog.test.ts b/src/Exceptionless.Web/ClientApp/src/lib/features/product-tours/catalog.test.ts index 83790f8909..282527b27c 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/product-tours/catalog.test.ts +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/product-tours/catalog.test.ts @@ -10,7 +10,7 @@ function context(overrides: Partial = {}): ProductTourContex isProjectConfigurePage: false, isSetupPage: false, organizationId: 'organization-id', - pathname: '/next', + pathname: '', projects: [], ...overrides }; @@ -189,11 +189,11 @@ describe('product tour catalog', () => { const platform = definition.start(context({ projects: [{ id: 'project-id', is_configured: false }] })); // Assert - expect(organization).toEqual({ checkpointName: 'organization-name', route: '/next/organization/add' }); - expect(project).toEqual({ checkpointName: 'project-name', route: '/next/project/add' }); + expect(organization).toEqual({ checkpointName: 'organization-name', route: '/organization/add' }); + expect(project).toEqual({ checkpointName: 'project-name', route: '/project/add' }); expect(platform).toEqual({ checkpointName: 'choose-platform', - route: '/next/project/project-id/configure?redirect=true' + route: '/project/project-id/configure?redirect=true' }); }); @@ -202,7 +202,7 @@ describe('product tour catalog', () => { const definition = productTourCatalog.find((tour) => tour.name === 'project-configure')!; const currentContext = context({ isProjectConfigurePage: true, - pathname: '/next/project/current-project/configure', + pathname: '/project/current-project/configure', projects: [ { id: 'other-project', is_configured: false }, { id: 'current-project', is_configured: true } @@ -214,7 +214,7 @@ describe('product tour catalog', () => { const start = definition.start(currentContext); // Assert - expect(start).toEqual({ checkpointName: 'choose-platform', route: '/next/project/current-project/configure?type=dotnet-legacy-mvc&redirect=true' }); + expect(start).toEqual({ checkpointName: 'choose-platform', route: '/project/current-project/configure?type=dotnet-legacy-mvc&redirect=true' }); }); it('does not carry another page SDK selection into project setup', () => { @@ -225,7 +225,7 @@ describe('product tour catalog', () => { const start = definition.start(context({ projects: [{ id: 'project-id', is_configured: false }], search: '?type=error' })); // Assert - expect(start).toEqual({ checkpointName: 'choose-platform', route: '/next/project/project-id/configure?redirect=true' }); + expect(start).toEqual({ checkpointName: 'choose-platform', route: '/project/project-id/configure?redirect=true' }); }); it('keeps Client Setup when the organization project list has not caught up', () => { @@ -237,14 +237,14 @@ describe('product tour catalog', () => { context({ isProjectConfigurePage: true, organizationId: undefined, - pathname: '/next/project/current-project/configure', + pathname: '/project/current-project/configure', projects: [], search: '?type=dotnet-legacy-mvc' }) ); // Assert - expect(start).toEqual({ checkpointName: 'choose-platform', route: '/next/project/current-project/configure?type=dotnet-legacy-mvc&redirect=true' }); + expect(start).toEqual({ checkpointName: 'choose-platform', route: '/project/current-project/configure?type=dotnet-legacy-mvc&redirect=true' }); }); it('requires actual Exie access', () => { diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/product-tours/components/alerts/product-tour-welcome.svelte.test.ts b/src/Exceptionless.Web/ClientApp/src/lib/features/product-tours/components/alerts/product-tour-welcome.svelte.test.ts index 289edf4c59..f46a0101e0 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/product-tours/components/alerts/product-tour-welcome.svelte.test.ts +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/product-tours/components/alerts/product-tour-welcome.svelte.test.ts @@ -9,7 +9,7 @@ const recommended = { currentAvailability: { available: true }, description: 'Learn navigation and search.', name: 'app-overview' as const, - start: vi.fn(() => ({ checkpointName: 'navigation' as const, route: '/next' })), + start: vi.fn(() => ({ checkpointName: 'navigation' as const, route: '/' })), stateKey: 'app_overview' as const, title: 'Explore Exceptionless' }; diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/product-tours/components/dialogs/product-tour-catalog-dialog.svelte.test.ts b/src/Exceptionless.Web/ClientApp/src/lib/features/product-tours/components/dialogs/product-tour-catalog-dialog.svelte.test.ts index 403ee28598..f8b1221836 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/product-tours/components/dialogs/product-tour-catalog-dialog.svelte.test.ts +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/product-tours/components/dialogs/product-tour-catalog-dialog.svelte.test.ts @@ -19,7 +19,7 @@ describe('ProductTourCatalogDialog', () => { isProjectConfigurePage: false, isSetupPage: false, organizationId: 'organization', - pathname: '/next', + pathname: '/', projects: [] }, { app_overview: '2026-09-08T00:00:00Z' } @@ -54,7 +54,7 @@ describe('ProductTourCatalogDialog', () => { errorEventAvailability: 'empty', isProjectConfigurePage: false, isSetupPage: false, - pathname: '/next', + pathname: '/', projects: [] }); const onStart = vi.fn(); diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/product-tours/components/product-tour-host.svelte.test.ts b/src/Exceptionless.Web/ClientApp/src/lib/features/product-tours/components/product-tour-host.svelte.test.ts index 869dccb6c7..972e173a50 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/product-tours/components/product-tour-host.svelte.test.ts +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/product-tours/components/product-tour-host.svelte.test.ts @@ -41,7 +41,7 @@ function props() { isSetupPage: false, openAssistant: vi.fn(async () => {}), organizationId: 'organization', - pathname: '/next/stack/all', + pathname: '/stack/all', setMobileNavigationOpen: vi.fn(), stateSettled: true }; @@ -133,7 +133,7 @@ describe('ProductTourHost', () => { // Arrange mocks.page.route.id = `/(app)/${list}/[slug=savedview]`; const active = productTourCheckpoint.start('app-overview', 'saved-views', 'user', 'organization'); - const { component } = render(ProductTourHost, { ...props(), isAnyOverlayOpen: true, pathname: `/next/${list}/saved-view` }); + const { component } = render(ProductTourHost, { ...props(), isAnyOverlayOpen: true, pathname: `/${list}/saved-view` }); // Act await component.openCatalog(); diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/product-tours/components/product-tour-shell-spotlight.svelte.test.ts b/src/Exceptionless.Web/ClientApp/src/lib/features/product-tours/components/product-tour-shell-spotlight.svelte.test.ts index 6c6dc45838..0c8334125c 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/product-tours/components/product-tour-shell-spotlight.svelte.test.ts +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/product-tours/components/product-tour-shell-spotlight.svelte.test.ts @@ -11,7 +11,7 @@ vi.mock('../activity', () => ({ submitProductTourActivity: vi.fn() })); const navigation = vi.hoisted(() => ({ goto: vi.fn() })); vi.mock('$app/navigation', () => navigation); vi.mock('$app/state', () => ({ page: { route: { id: '/(app)/stack' } } })); -vi.mock('$app/paths', () => ({ resolve: () => '/next/event' })); +vi.mock('$app/paths', () => ({ resolve: () => '/event' })); let targets: HTMLDivElement; @@ -61,7 +61,7 @@ describe('ProductTourShellSpotlight', () => { await fireEvent.click(await screen.findByRole('button', { name: button })); - expect(navigation.goto).toHaveBeenCalledWith('/next/event'); + expect(navigation.goto).toHaveBeenCalledWith('/event'); expect(productTourCheckpoint.current).toBe(checkpoint); pending.resolve(); await waitFor(() => expect(productTourCheckpoint.current?.checkpointName).toBe('filters')); diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/saved-views/defaults.test.ts b/src/Exceptionless.Web/ClientApp/src/lib/features/saved-views/defaults.test.ts index 3803c5b81c..3737ca8f02 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/saved-views/defaults.test.ts +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/saved-views/defaults.test.ts @@ -34,7 +34,7 @@ describe('getSavedViewDefaultHref', () => { savedViews }); - expect(getSavedViewDefaultHref(defaults, savedViews)).toBe('/next/event/my-home'); + expect(getSavedViewDefaultHref(defaults, savedViews)).toBe('/event/my-home'); }); it('uses the organization default when there is no personal default', () => { @@ -45,7 +45,7 @@ describe('getSavedViewDefaultHref', () => { savedViews }); - expect(getSavedViewDefaultHref(defaults, savedViews)).toBe('/next/stack/organization-home'); + expect(getSavedViewDefaultHref(defaults, savedViews)).toBe('/stack/organization-home'); }); it('skips missing duplicate personal defaults', () => { @@ -57,7 +57,7 @@ describe('getSavedViewDefaultHref', () => { }); expect(defaults.userDefault?.id).toBe('valid-default'); - expect(getSavedViewDefaultHref(defaults, savedViews)).toBe('/next/stack/valid-home'); + expect(getSavedViewDefaultHref(defaults, savedViews)).toBe('/stack/valid-home'); }); it('ignores a private organization default', () => { @@ -72,7 +72,7 @@ describe('getSavedViewDefaultHref', () => { }); expect(defaults.organizationDefault).toBeUndefined(); - expect(getSavedViewDefaultHref(defaults, savedViews)).toBe('/next/stack/all'); + expect(getSavedViewDefaultHref(defaults, savedViews)).toBe('/stack/all'); }); it('falls back to the first Stacks saved view when no configured default is available', () => { @@ -82,12 +82,12 @@ describe('getSavedViewDefaultHref', () => { savedView({ id: 'second-stack-view', slug: 'errors' }) ]; - expect(getSavedViewDefaultHref({}, savedViews)).toBe('/next/stack/all'); + expect(getSavedViewDefaultHref({}, savedViews)).toBe('/stack/all'); }); it('falls back to the built-in Stacks route when there are no Stacks saved views', () => { - expect(getSavedViewDefaultHref({}, [])).toBe('/next/stack'); - expect(getSavedViewDefaultHref({}, undefined)).toBe('/next/stack'); + expect(getSavedViewDefaultHref({}, [])).toBe('/stack'); + expect(getSavedViewDefaultHref({}, undefined)).toBe('/stack'); }); it('builds stream saved view links using the saved view identifier', () => { @@ -98,7 +98,7 @@ describe('getSavedViewDefaultHref', () => { savedViews }); - expect(getSavedViewDefaultHref(defaults, savedViews)).toBe('/next/stream?saved=stream-default'); + expect(getSavedViewDefaultHref(defaults, savedViews)).toBe('/stream?saved=stream-default'); }); it('builds Sessions saved view links for a configured home view', () => { @@ -109,7 +109,7 @@ describe('getSavedViewDefaultHref', () => { savedViews }); - expect(getSavedViewDefaultHref(defaults, savedViews)).toBe('/next/sessions/active'); + expect(getSavedViewDefaultHref(defaults, savedViews)).toBe('/sessions/active'); }); }); diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/saved-views/use-saved-views.test.ts b/src/Exceptionless.Web/ClientApp/src/lib/features/saved-views/use-saved-views.test.ts index 5b763401ca..f2a722503b 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/saved-views/use-saved-views.test.ts +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/saved-views/use-saved-views.test.ts @@ -147,18 +147,18 @@ describe('useSavedViews', () => { describe('saved view URL override signatures', () => { it('ignores pagination-only query changes', () => { - const first = new URL('https://example.test/next/event/errors?time=90d&project=project-1&page=1&limit=10'); - const second = new URL('https://example.test/next/event/errors?project=project-1&time=90d&page=4&limit=100'); + const first = new URL('https://example.test/event/errors?time=90d&project=project-1&page=1&limit=10'); + const second = new URL('https://example.test/event/errors?project=project-1&time=90d&page=4&limit=100'); expect(getSavedViewOverrideSignature(first)).toBe(getSavedViewOverrideSignature(second)); }); it('detects additions, removals, and changes to saved view overrides', () => { - const baseline = getSavedViewOverrideSignature(new URL('https://example.test/next/event/errors?time=90d&status=open')); + const baseline = getSavedViewOverrideSignature(new URL('https://example.test/event/errors?time=90d&status=open')); - expect(getSavedViewOverrideSignature(new URL('https://example.test/next/event/errors?time=30d&status=open'))).not.toBe(baseline); - expect(getSavedViewOverrideSignature(new URL('https://example.test/next/event/errors?time=90d'))).not.toBe(baseline); - expect(getSavedViewOverrideSignature(new URL('https://example.test/next/event/errors?time=90d&status=open&sort=type'))).not.toBe(baseline); + expect(getSavedViewOverrideSignature(new URL('https://example.test/event/errors?time=30d&status=open'))).not.toBe(baseline); + expect(getSavedViewOverrideSignature(new URL('https://example.test/event/errors?time=90d'))).not.toBe(baseline); + expect(getSavedViewOverrideSignature(new URL('https://example.test/event/errors?time=90d&status=open&sort=type'))).not.toBe(baseline); }); }); @@ -199,13 +199,13 @@ describe('useSavedViews', () => { const href = savedViewHref(savedView); // Assert - expect(href).toBe('/next/event/legacy-saved-view'); + expect(href).toBe('/event/legacy-saved-view'); }); it('builds Sessions saved-view URLs on the Sessions route', () => { const savedView = buildSavedView({ id: 'view-1', name: 'Active Sessions', slug: 'active', view_type: 'sessions' }); - expect(savedViewHref(savedView)).toBe('/next/sessions/active'); + expect(savedViewHref(savedView)).toBe('/sessions/active'); }); }); diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/api/api.svelte.test.ts b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/api/api.svelte.test.ts new file mode 100644 index 0000000000..ba4739762b --- /dev/null +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/api/api.svelte.test.ts @@ -0,0 +1,19 @@ +import { afterEach, expect, it, vi } from 'vitest'; + +import { fetchApiJson } from './api.svelte'; + +vi.mock('$env/dynamic/public', () => ({ env: { PUBLIC_BASE_URL: 'https://localhost:8443/' } })); +vi.mock('$features/auth/index.svelte', () => ({ accessToken: { current: 'existing-session' } })); + +afterEach(() => vi.unstubAllGlobals()); + +it('sends authenticated direct requests to the configured API origin', async () => { + const fetch = vi.fn().mockResolvedValue(Response.json({ recorded: true })); + vi.stubGlobal('fetch', fetch); + + expect(await fetchApiJson('users/me/product-tours/welcome/record', { method: 'PUT' })).toEqual({ recorded: true }); + const [url, init] = fetch.mock.calls[0]!; + expect(url).toBe('https://localhost:8443/api/v2/users/me/product-tours/welcome/record'); + expect(init?.method).toBe('PUT'); + expect(new Headers(init?.headers).get('Authorization')).toBe('Bearer existing-session'); +}); diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/api/api.svelte.ts b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/api/api.svelte.ts index 2d7ef8f509..fc8c25290b 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/api/api.svelte.ts +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/api/api.svelte.ts @@ -2,6 +2,8 @@ import { accessToken } from '$features/auth/index.svelte'; import { type FetchClient, type FetchClientProvider, getCurrentProvider, ProblemDetails } from '@foundatiofx/fetchclient'; import { SvelteDate } from 'svelte/reactivity'; +import { getApiUrl } from './urls'; + export { DEFAULT_LIMIT } from './constants'; /** @@ -31,7 +33,7 @@ export async function fetchApiJson(path: string, init: RequestInit): Promise< headers.set('Authorization', `Bearer ${accessToken.current}`); } - const response = await fetch(`/api/v2/${path.replace(/^\/+/, '')}`, { + const response = await fetch(getApiUrl(path), { ...init, headers }); diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/api/urls.test.ts b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/api/urls.test.ts new file mode 100644 index 0000000000..676d54fddd --- /dev/null +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/api/urls.test.ts @@ -0,0 +1,32 @@ +import { beforeEach, describe, expect, it, vi } from 'vitest'; + +import { getApiUrl, getServerUrl } from './urls'; + +const { env } = vi.hoisted(() => ({ env: { PUBLIC_BASE_URL: '' } })); +vi.mock('$env/dynamic/public', () => ({ env })); + +beforeEach(() => { + vi.resetModules(); + env.PUBLIC_BASE_URL = ''; +}); + +describe('API hosting URLs', () => { + it('defaults to same-origin API, health, push and documentation endpoints', async () => { + expect(getApiUrl()).toBe('/api/v2'); + expect(getApiUrl('/auth/login')).toBe('/api/v2/auth/login'); + expect(getServerUrl('health')).toBe('/health'); + expect(getServerUrl('/api/v2/push')).toBe('/api/v2/push'); + expect((await import('../help-links')).apiReferenceHref).toBe('/docs'); + }); + + it('uses the configured API origin for ordinary, streaming, push, health and documentation requests', async () => { + env.PUBLIC_BASE_URL = ' https://localhost:8443/ '; + expect(getApiUrl()).toBe('https://localhost:8443/api/v2'); + expect(getApiUrl('auth/login')).toBe('https://localhost:8443/api/v2/auth/login'); + expect(getApiUrl('assistant/chat')).toBe('https://localhost:8443/api/v2/assistant/chat'); + expect(getServerUrl('/api/v2/push')).toBe('https://localhost:8443/api/v2/push'); + expect(getServerUrl('health')).toBe('https://localhost:8443/health'); + expect(getServerUrl('mcp')).toBe('https://localhost:8443/mcp'); + expect((await import('../help-links')).apiReferenceHref).toBe('https://localhost:8443/docs'); + }); +}); diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/api/urls.ts b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/api/urls.ts new file mode 100644 index 0000000000..ff4001de06 --- /dev/null +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/api/urls.ts @@ -0,0 +1,11 @@ +import { env } from '$env/dynamic/public'; + +export function getApiUrl(path = ''): string { + return getServerUrl(`api/v2${path ? `/${path.replace(/^\/+/, '')}` : ''}`); +} + +/** EX_ApiUrl may point to a separate API origin; an unset value uses the UI origin. */ +export function getServerUrl(path: string): string { + const base = (env.PUBLIC_BASE_URL ?? '').trim().replace(/\/+$/, ''); + return `${base}/${path.replace(/^\/+/, '')}`; +} diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/data-table/data-table-body.svelte.test.ts b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/data-table/data-table-body.svelte.test.ts index 953a9499bf..a1e506aa62 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/data-table/data-table-body.svelte.test.ts +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/data-table/data-table-body.svelte.test.ts @@ -224,7 +224,7 @@ describe('DataTableBody', () => { const messageLink = screen.getByText('Unexpected end of Stream, the content may have already been read by another component.').closest('a'); - expect(messageLink?.getAttribute('href')).toBe('/next/event/event-id'); + expect(messageLink?.getAttribute('href')).toBe('/event/event-id'); }); it('opens stack details for normal clicks anywhere in a stack summary', async () => { @@ -243,6 +243,6 @@ describe('DataTableBody', () => { const messageLink = screen.getByText('Unexpected end of Stream, the content may have already been read by another component.').closest('a'); - expect(messageLink?.getAttribute('href')).toBe('/next/stack/stack-id'); + expect(messageLink?.getAttribute('href')).toBe('/stack/stack-id'); }); }); diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/help-links.ts b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/help-links.ts index 4045189948..05c6a8c3b3 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/help-links.ts +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/help-links.ts @@ -1,4 +1,6 @@ -export const apiReferenceHref = '/docs/index.html'; +import { getServerUrl } from './api/urls'; + +export const apiReferenceHref = getServerUrl('docs'); export const documentationHref = 'https://exceptionless.com/docs/'; export const githubRepositoryHref = 'https://github.com/exceptionless/Exceptionless'; export const supportIssuesHref = `${githubRepositoryHref}/issues`; diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/validation.test.ts b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/validation.test.ts index 2a8675cda7..0f810c5241 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/validation.test.ts +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/validation.test.ts @@ -33,14 +33,27 @@ describe('getProblemMessage', () => { }); describe('problemDetailsToFormErrors', () => { - it('uses the resolved problem message for non-validation form errors', () => { - const problem = { - status: 400, - title: 'An organization cannot be deleted if it has a subscription.' - }; - - expect(problemDetailsToFormErrors(problem as never)).toEqual({ - form: 'An organization cannot be deleted if it has a subscription.' - }); + it.each([{ status: 400 }, { status: 422 }, { errors: {}, status: 422 }, { errors: { version: [] }, status: 422 }])( + 'shows the problem message when there are no field errors: %j', + (details) => { + const problem = { + ...details, + title: 'An organization cannot be deleted if it has a subscription.' + }; + + expect(problemDetailsToFormErrors(problem as never)).toEqual({ + form: 'An organization cannot be deleted if it has a subscription.' + }); + } + ); + + it('preserves specific field errors without adding the generic title', () => { + expect( + problemDetailsToFormErrors({ + errors: { version: ['Version is invalid.'] }, + status: 422, + title: 'Validation failed.' + } as never) + ).toEqual({ fields: { version: 'Version is invalid.' } }); }); }); diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/validation.ts b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/validation.ts index e58afef852..989415d1c5 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/validation.ts +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/validation.ts @@ -204,7 +204,7 @@ export function problemDetailsToFormErrors(problem: null | ProblemDetails): null const generalErrors = problem.errors?.['general']?.join(', '); if (generalErrors) { result.form = generalErrors; - } else if (problem.status !== 422) { + } else if (problem.status !== 422 || !Object.values(problem.errors ?? {}).some((errors) => Boolean(errors?.length))) { const message = getProblemMessage(problem, ''); if (message) { result.form = message; @@ -213,15 +213,20 @@ export function problemDetailsToFormErrors(problem: null | ProblemDetails): null // Handle field-level errors (422 validation errors) if (problem.status === 422 && problem.errors) { - result.fields = {}; + const fields: Record = {}; for (const key in problem.errors) { if (key === 'general') { continue; } - const errors = problem.errors[key] as string[]; + const errors = problem.errors[key]; // TODO: Convert snake_case field names to match form field names?? - result.fields[key] = errors.join(', '); + if (errors?.length) { + fields[key] = errors.join(', '); + } + } + if (Object.keys(fields).length > 0) { + result.fields = fields; } } diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/stacks/components/dialogs/mark-stack-discarded-dialog.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/stacks/components/dialogs/mark-stack-discarded-dialog.svelte index 1337be78e8..d0be3f1840 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/stacks/components/dialogs/mark-stack-discarded-dialog.svelte +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/stacks/components/dialogs/mark-stack-discarded-dialog.svelte @@ -1,7 +1,7 @@ @@ -35,6 +43,7 @@ {/if} + {#if stackTitle}{stackTitle}{/if} Are you sure you want to discard all current {#if count === 1} stack events @@ -46,21 +55,15 @@ All future occurrences will be discarded and will not count against your event limit. - Cancel - + Cancel + Discard {#if count === 1} Stack {:else} Stacks {/if} - + diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/stacks/components/dialogs/mark-stack-fixed-in-version-dialog.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/stacks/components/dialogs/mark-stack-fixed-in-version-dialog.svelte index 2bab02e1bc..b0f25f40b3 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/stacks/components/dialogs/mark-stack-fixed-in-version-dialog.svelte +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/stacks/components/dialogs/mark-stack-fixed-in-version-dialog.svelte @@ -3,6 +3,7 @@ import Number from '$comp/formatters/number.svelte'; import { A, P } from '$comp/typography'; import * as AlertDialog from '$comp/ui/alert-dialog'; + import { Button } from '$comp/ui/button'; import * as Field from '$comp/ui/field'; import { Input } from '$comp/ui/input'; import { getFormErrorMessages, mapFieldErrors, problemDetailsToFormErrors } from '$shared/validation'; @@ -17,9 +18,10 @@ count?: number; open: boolean; save: (version?: string) => Promise; + stackTitle?: string; } - let { count = 1, open = $bindable(), save }: Props = $props(); + let { count = 1, open = $bindable(), save, stackTitle }: Props = $props(); const form = createForm(() => ({ defaultValues: { @@ -93,6 +95,7 @@ {/if} + {#if stackTitle}{stackTitle}{/if} Marks {#if count === 1} the stack @@ -142,18 +145,22 @@ {/snippet} - - Cancel - - Mark - {#if count === 1} - Stack - {:else} - Stacks - {/if} - Fixed - - + state.isSubmitting}> + {#snippet children(submitting)} + + Cancel + + Mark + {#if count === 1} + Stack + {:else} + Stacks + {/if} + Fixed + + + {/snippet} + diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/stacks/components/dialogs/mark-stack-ignored-dialog.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/stacks/components/dialogs/mark-stack-ignored-dialog.svelte new file mode 100644 index 0000000000..a0d930e13b --- /dev/null +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/stacks/components/dialogs/mark-stack-ignored-dialog.svelte @@ -0,0 +1,45 @@ + + + + + + Ignore Stack + + Stop sending occurrence notifications for {stackTitle}? Future events will still be collected. + + + + Cancel + Ignore Stack + + + diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/stacks/components/stack-action-page.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/stacks/components/stack-action-page.svelte new file mode 100644 index 0000000000..2010886a1a --- /dev/null +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/stacks/components/stack-action-page.svelte @@ -0,0 +1,93 @@ + + + + + +{#if stack} + {#if action === 'mark-fixed'} + open, handleOpenChange} {save} stackTitle={stack.title} /> + {:else if action === 'ignored'} + open, handleOpenChange} {ignore} stackTitle={stack.title} /> + {:else} + open, handleOpenChange} {discard} stackTitle={stack.title} /> + {/if} +{/if} diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/status/api.svelte.ts b/src/Exceptionless.Web/ClientApp/src/lib/features/status/api.svelte.ts index 6fd3bbb2a7..2ef13e21e4 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/status/api.svelte.ts +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/status/api.svelte.ts @@ -1,3 +1,4 @@ +import { getServerUrl } from '$features/shared/api/urls'; import { type ProblemDetails, useFetchClient } from '@foundatiofx/fetchclient'; import { createQuery } from '@tanstack/svelte-query'; @@ -27,7 +28,7 @@ export function getHealthQuery() { return createQuery(() => ({ queryFn: async ({ signal }: { signal: AbortSignal }) => { const client = useFetchClient({ - baseUrl: '' + baseUrl: getServerUrl('') }); const response = await client.get('health', { signal diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/status/service-status-redirect.test.ts b/src/Exceptionless.Web/ClientApp/src/lib/features/status/service-status-redirect.test.ts index 1cbadc2ce5..e978e9ddaa 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/status/service-status-redirect.test.ts +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/status/service-status-redirect.test.ts @@ -4,13 +4,11 @@ import { buildServiceStatusUrl, createServiceStatusRedirector } from './service- describe('buildServiceStatusUrl', () => { it('preserves the current path, query, and hash as an encoded redirect', () => { - const url = new URL('https://example.test/next/stack/most-frequent-errors?project=project-1&filter=status%3Aopen#details'); + const url = new URL('https://example.test/stack/most-frequent-errors?project=project-1&filter=status%3Aopen#details'); - const result = buildServiceStatusUrl('/next/status', url); + const result = buildServiceStatusUrl('/status', url); - expect(new URL(result, url.origin).searchParams.get('redirect')).toBe( - '/next/stack/most-frequent-errors?project=project-1&filter=status%3Aopen#details' - ); + expect(new URL(result, url.origin).searchParams.get('redirect')).toBe('/stack/most-frequent-errors?project=project-1&filter=status%3Aopen#details'); }); }); diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/websockets/web-socket-client.svelte.ts b/src/Exceptionless.Web/ClientApp/src/lib/features/websockets/web-socket-client.svelte.ts index d18b9df698..189376f1b8 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/websockets/web-socket-client.svelte.ts +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/websockets/web-socket-client.svelte.ts @@ -1,11 +1,13 @@ +import { getServerUrl } from '$shared/api/urls'; import { DocumentVisibility } from '$shared/document-visibility.svelte'; +import { SvelteURL } from 'svelte/reactivity'; import { accessToken } from '../auth/index.svelte'; export interface WebSocketClientOptions { /** * Base URL for WebSocket connection (e.g., 'ws://localhost:1234') - * If not provided, constructs from window.location + * If not provided, uses the configured API origin or window.location */ baseUrl?: string; /** @@ -26,16 +28,16 @@ export class WebSocketClient { /** * Lazy getter for WebSocket URL. - * Constructed on first access. Uses baseUrl from options if provided, otherwise constructs from window.location. + * Constructed on first access. Uses baseUrl from options if provided, otherwise uses the configured API origin or window.location. */ public get url(): string { if (this._url === null) { if (this._options.baseUrl) { this._url = `${this._options.baseUrl}${this._path}`; } else { - const { host, protocol } = window.location; - const wsProtocol = protocol === 'https:' ? 'wss://' : 'ws://'; - this._url = `${wsProtocol}${host}${this._path}`; + const url = new SvelteURL(getServerUrl(this._path), window.location.origin); + url.protocol = url.protocol === 'https:' ? 'wss:' : 'ws:'; + this._url = url.href; } } diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/websockets/web-socket-client.test.ts b/src/Exceptionless.Web/ClientApp/src/lib/features/websockets/web-socket-client.test.ts index 9f7c125c8c..f8d1cbebc4 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/websockets/web-socket-client.test.ts +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/websockets/web-socket-client.test.ts @@ -3,6 +3,9 @@ import WS from 'vitest-websocket-mock'; import { WebSocketClient, type WebSocketClientOptions } from './web-socket-client.svelte'; +const { env } = vi.hoisted(() => ({ env: { PUBLIC_BASE_URL: '' } })); +vi.mock('$env/dynamic/public', () => ({ env })); + // Mock the auth module vi.mock('../auth/index.svelte', () => ({ accessToken: { @@ -27,6 +30,8 @@ beforeEach(() => { afterEach(() => { WS.clean(); + env.PUBLIC_BASE_URL = ''; + vi.unstubAllGlobals(); }); function createClient(path?: string, options?: WebSocketClientOptions): WebSocketClient { @@ -38,6 +43,15 @@ function createClient(path?: string, options?: WebSocketClientOptions): WebSocke } describe('WebSocketClient', () => { + it.each([ + ['https://localhost:8443/', 'wss://localhost:8443/api/v2/push'], + ['http://localhost:8080', 'ws://localhost:8080/api/v2/push'] + ])('uses the configured API origin %s', (baseUrl, expected) => { + env.PUBLIC_BASE_URL = baseUrl; + vi.stubGlobal('window', { location: { origin: 'https://localhost:7131' } }); + expect(new WebSocketClient().url).toBe(expected); + }); + describe('Connection Lifecycle', () => { it('should connect successfully', async () => { const client = createClient(); diff --git a/src/Exceptionless.Web/ClientApp/src/lib/telemetry/route.test.ts b/src/Exceptionless.Web/ClientApp/src/lib/telemetry/route.test.ts index 5a73e92ad8..b5ab44f9a0 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/telemetry/route.test.ts +++ b/src/Exceptionless.Web/ClientApp/src/lib/telemetry/route.test.ts @@ -1,6 +1,6 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'; -const paths = vi.hoisted(() => ({ base: '/next' })); +const paths = vi.hoisted(() => ({ base: '' })); vi.mock('$app/paths', () => ({ resolve: (path: string) => `${paths.base}${path}` })); @@ -8,10 +8,10 @@ import { normalizePath } from './route'; describe('normalizePath', () => { beforeEach(() => { - paths.base = '/next'; + paths.base = ''; }); - it.each(['/next', ''])('normalizes telemetry under the app base %j', (base) => { + it.each(['', '/custom'])('normalizes telemetry under the app base %j', (base) => { paths.base = base; expect(normalizePath(`${base}/stack/507f1f77bcf86cd799439011/`)).toBe('/stack/:id'); @@ -21,12 +21,13 @@ describe('normalizePath', () => { }); it('does not strip a partial base segment', () => { - expect(normalizePath('/nextdoor/event/123')).toBe('/nextdoor/event/:id'); - expect(normalizePath('/next')).toBe('/'); + paths.base = '/custom'; + expect(normalizePath('/customdoor/event/123')).toBe('/customdoor/event/:id'); + expect(normalizePath('/custom')).toBe('/'); }); it('preserves support for an explicit normalization base', () => { expect(normalizePath('/custom/event/123', '/custom')).toBe('/event/:id'); - expect(normalizePath('/next/event/123', '')).toBe('/next/event/:id'); + expect(normalizePath('/custom/event/123', '')).toBe('/custom/event/:id'); }); }); diff --git a/src/Exceptionless.Web/ClientApp/src/params/stackaction.ts b/src/Exceptionless.Web/ClientApp/src/params/stackaction.ts new file mode 100644 index 0000000000..18381ee8c5 --- /dev/null +++ b/src/Exceptionless.Web/ClientApp/src/params/stackaction.ts @@ -0,0 +1,3 @@ +export function match(param: string): param is 'discarded' | 'ignored' | 'mark-fixed' { + return param === 'mark-fixed' || param === 'ignored' || param === 'discarded'; +} diff --git a/src/Exceptionless.Web/ClientApp/src/routes/(app)/(components)/navigation-command.svelte.test.ts b/src/Exceptionless.Web/ClientApp/src/routes/(app)/(components)/navigation-command.svelte.test.ts index b9427f74d5..c6f767cc6a 100644 --- a/src/Exceptionless.Web/ClientApp/src/routes/(app)/(components)/navigation-command.svelte.test.ts +++ b/src/Exceptionless.Web/ClientApp/src/routes/(app)/(components)/navigation-command.svelte.test.ts @@ -19,6 +19,7 @@ const project = vi.hoisted(() => ({ organization_id: 'organization-id' })); +vi.mock('$env/dynamic/public', () => ({ env: {} })); vi.mock('$app/navigation', () => ({ goto })); vi.mock('$features/auth/api.svelte', () => ({ logout })); vi.mock('$features/auth/index.svelte', () => ({ accessToken: { current: 'access-token' } })); @@ -67,7 +68,7 @@ type RenderOptions = { const sessionsRoute: NavigationItem = { group: 'Dashboards', - href: '/next/sessions', + href: '/sessions', icon: undefined as never, title: 'Sessions' }; @@ -111,14 +112,14 @@ describe('NavigationCommand project actions', () => { }); it.each([ - ['Open Project', `/next/project/${project.id}/manage`], - ['Project Stacks', `/next/stack?filter=project:${project.id}`], - ['Project Events', `/next/event?project=${project.id}`], - ['Project API Keys', `/next/project/${project.id}/api-keys`], - ['Project Webhooks & Integrations', `/next/project/${project.id}/integrations`], - ['Project Source Maps', `/next/project/${project.id}/source-maps`], - ['Project Notifications', `/next/account/notifications?project=${project.id}`], - ['Client Setup', `/next/project/${project.id}/configure`] + ['Open Project', `/project/${project.id}/manage`], + ['Project Stacks', `/stack?filter=project:${project.id}`], + ['Project Events', `/event?project=${project.id}`], + ['Project API Keys', `/project/${project.id}/api-keys`], + ['Project Webhooks & Integrations', `/project/${project.id}/integrations`], + ['Project Source Maps', `/project/${project.id}/source-maps`], + ['Project Notifications', `/account/notifications?project=${project.id}`], + ['Client Setup', `/project/${project.id}/configure`] ])('links %s to the selected project', async (action, expectedHref) => { renderCommandPalette(); @@ -133,7 +134,7 @@ describe('NavigationCommand project actions', () => { renderCommandPalette([ { group: 'My Account', - href: '/next/account/ai-tools', + href: '/account/ai-tools', icon: undefined as never, keywords: ['MCP'], title: 'AI Tools' @@ -226,7 +227,7 @@ describe('NavigationCommand project actions', () => { const usersLink = screen.getByText('View Organization Users').closest('a'); - expect(usersLink?.getAttribute('href')).toBe('/next/organization/organization-id/users'); + expect(usersLink?.getAttribute('href')).toBe('/organization/organization-id/users'); }); it('opens Exie from the command palette', async () => { @@ -276,7 +277,7 @@ describe('NavigationCommand project actions', () => { await fireEvent.click(screen.getByText('Switch to Other Organization')); expect(organizationState.current).toBe('other-organization-id'); - await waitFor(() => expect(goto).toHaveBeenCalledWith('/next/')); + await waitFor(() => expect(goto).toHaveBeenCalledWith('/')); }); it('opens support chat', async () => { @@ -318,7 +319,7 @@ describe('NavigationCommand project actions', () => { renderCommandPalette(); await fireEvent.click(screen.getByText('Log Out')); await waitFor(() => expect(logout).toHaveBeenCalledOnce()); - expect(goto).toHaveBeenCalledWith('/next/login'); + expect(goto).toHaveBeenCalledWith('/login'); }); }); diff --git a/src/Exceptionless.Web/ClientApp/src/routes/(app)/account/ai-tools/+page.svelte b/src/Exceptionless.Web/ClientApp/src/routes/(app)/account/ai-tools/+page.svelte index 618500d12e..e346d4e3d8 100644 --- a/src/Exceptionless.Web/ClientApp/src/routes/(app)/account/ai-tools/+page.svelte +++ b/src/Exceptionless.Web/ClientApp/src/routes/(app)/account/ai-tools/+page.svelte @@ -4,6 +4,7 @@ import { CodeBlock, Muted, P } from '$comp/typography'; import * as Card from '$comp/ui/card'; import * as Select from '$comp/ui/select'; + import { getServerUrl } from '$shared/api/urls'; type AiTool = { description: string; @@ -27,8 +28,9 @@ $effect(() => { if (browser) { - mcpEndpoint = `${window.location.origin}/mcp`; - mcpServerName = getMcpServerName(window.location.hostname); + const endpoint = new URL(getServerUrl('mcp'), window.location.origin); + mcpEndpoint = endpoint.href; + mcpServerName = getMcpServerName(endpoint.hostname); } }); diff --git a/src/Exceptionless.Web/ClientApp/src/routes/(app)/organization/[organizationId]/dashboard/+page@(app).svelte b/src/Exceptionless.Web/ClientApp/src/routes/(app)/organization/[organizationId]/dashboard/+page@(app).svelte new file mode 100644 index 0000000000..69e241542e --- /dev/null +++ b/src/Exceptionless.Web/ClientApp/src/routes/(app)/organization/[organizationId]/dashboard/+page@(app).svelte @@ -0,0 +1,6 @@ + + + diff --git a/src/Exceptionless.Web/ClientApp/src/routes/(app)/organization/[organizationId]/upgrade/+page.svelte b/src/Exceptionless.Web/ClientApp/src/routes/(app)/organization/[organizationId]/upgrade/+page.svelte new file mode 100644 index 0000000000..697da5183a --- /dev/null +++ b/src/Exceptionless.Web/ClientApp/src/routes/(app)/organization/[organizationId]/upgrade/+page.svelte @@ -0,0 +1,22 @@ + + +Opening billing… diff --git a/src/Exceptionless.Web/ClientApp/src/routes/(app)/project/[projectId]/dashboard/+page@(app).svelte b/src/Exceptionless.Web/ClientApp/src/routes/(app)/project/[projectId]/dashboard/+page@(app).svelte new file mode 100644 index 0000000000..1a56e090dd --- /dev/null +++ b/src/Exceptionless.Web/ClientApp/src/routes/(app)/project/[projectId]/dashboard/+page@(app).svelte @@ -0,0 +1,6 @@ + + + diff --git a/src/Exceptionless.Web/ClientApp/src/routes/(app)/stack/[stackId=objectid]/[action=stackaction]/+page.svelte b/src/Exceptionless.Web/ClientApp/src/routes/(app)/stack/[stackId=objectid]/[action=stackaction]/+page.svelte new file mode 100644 index 0000000000..c8e0fe26a5 --- /dev/null +++ b/src/Exceptionless.Web/ClientApp/src/routes/(app)/stack/[stackId=objectid]/[action=stackaction]/+page.svelte @@ -0,0 +1,24 @@ + + + + Manage Stack - Exceptionless + + +{#key `${params.stackId}/${params.action}`} + +{/key} diff --git a/src/Exceptionless.Web/ClientApp/src/routes/(app)/system/notifications/+page.svelte b/src/Exceptionless.Web/ClientApp/src/routes/(app)/system/notifications/+page.svelte index 4938c5826a..1177b16037 100644 --- a/src/Exceptionless.Web/ClientApp/src/routes/(app)/system/notifications/+page.svelte +++ b/src/Exceptionless.Web/ClientApp/src/routes/(app)/system/notifications/+page.svelte @@ -24,7 +24,6 @@ let systemMessage = $state(''); let systemLevel = $state<'Error' | 'Info' | 'Warning'>('Info'); - let systemTarget = $state<'Both' | 'Legacy' | 'Modern'>('Both'); let loadedNotificationKey = $state(null); const currentNotification = $derived(currentNotificationQuery.data); @@ -54,12 +53,6 @@ Warning: AlertTriangle } as const; - const targetLabelMap = { - Both: 'Both UIs', - Legacy: 'Legacy UI only', - Modern: 'New UI only' - } as const; - $effect(() => { if (loadedNotificationKey === currentNotificationKey) { return; @@ -70,14 +63,12 @@ if (currentNotification?.message) { systemMessage = currentNotification.message; systemLevel = currentNotification.level ?? 'Info'; - systemTarget = currentNotification.target ?? 'Both'; return; } if (!currentNotificationQuery.isLoading) { systemMessage = ''; systemLevel = 'Info'; - systemTarget = 'Both'; } }); @@ -87,7 +78,6 @@ toast.success('System notification cleared.'); systemMessage = ''; systemLevel = 'Info'; - systemTarget = 'Both'; } catch { toast.error('Failed to clear system notification.'); } @@ -98,7 +88,8 @@ await setSystemNotification.mutateAsync({ level: systemLevel, message: systemMessage, - target: systemTarget + // Keep the public notification contract; newly published banners reach the current application. + target: 'Both' }); toast.success('System notification set successfully.'); } catch { @@ -141,11 +132,14 @@ {@html previewNotificationHtml} - Level: {systemLevel} · Target: {targetLabelMap[systemTarget]} + Level: {systemLevel} {#if currentNotification?.date} · Set {new Date(currentNotification.date).toLocaleString()} {/if} + {#if currentNotification?.target === 'Legacy'} + This saved notification is hidden from the application. Updating it will publish it to the current UI. + {/if} {:else} (no active notification) @@ -158,34 +152,20 @@ HTML is supported and will be sanitized before display. - - - Level - - - {systemLevel} - - + + Level + + + {systemLevel} + + + Info Warning Error - - - - - - Target - - - {targetLabelMap[systemTarget]} - - - Both UIs - Legacy UI only - New UI only - - - + + + diff --git a/src/Exceptionless.Web/ClientApp/src/routes/(app)/system/notifications/system-notifications.svelte.test.ts b/src/Exceptionless.Web/ClientApp/src/routes/(app)/system/notifications/system-notifications.svelte.test.ts new file mode 100644 index 0000000000..ce3894ec9d --- /dev/null +++ b/src/Exceptionless.Web/ClientApp/src/routes/(app)/system/notifications/system-notifications.svelte.test.ts @@ -0,0 +1,58 @@ +import type { SystemNotification } from '$features/websockets/models'; + +import { fireEvent, render, screen, waitFor } from '@testing-library/svelte'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; + +import SystemNotifications from './+page.svelte'; + +const mocks = vi.hoisted(() => ({ + clear: vi.fn(), + notification: { data: null as null | SystemNotification, isLoading: false }, + save: vi.fn() +})); + +vi.mock('$features/notifications/api.svelte', () => ({ + clearSystemNotificationMutation: () => ({ isPending: false, mutateAsync: mocks.clear }), + getCurrentSystemNotificationQuery: () => mocks.notification, + setSystemNotificationMutation: () => ({ isPending: false, mutateAsync: mocks.save }) +})); + +describe('system notifications after UI cutover', () => { + beforeEach(() => { + mocks.notification.data = null; + mocks.save.mockReset().mockResolvedValue({}); + mocks.clear.mockReset().mockResolvedValue(undefined); + }); + + it('publishes new banners without offering a removed application target', async () => { + render(SystemNotifications); + + expect(screen.getByRole('button', { name: 'Level' })).toBeInTheDocument(); + expect(screen.queryByText('Target')).not.toBeInTheDocument(); + expect(screen.queryByText(/Both UIs|Legacy UI only|New UI only/)).not.toBeInTheDocument(); + await fireEvent.input(screen.getByLabelText('Message'), { target: { value: 'Planned maintenance' } }); + await fireEvent.click(screen.getByRole('button', { name: 'Set Notification' })); + + await waitFor(() => expect(mocks.save).toHaveBeenCalledExactlyOnceWith({ level: 'Info', message: 'Planned maintenance', target: 'Both' })); + }); + + it.each(['Legacy', 'Modern', 'Both', undefined] as const)('keeps a saved %s banner unchanged until explicitly republished', async (target) => { + const notification = { date: '2026-10-01T00:00:00Z', level: 'Warning' as const, message: 'Saved banner', target }; + mocks.notification.data = notification; + render(SystemNotifications); + + await waitFor(() => expect(screen.getByLabelText('Message')).toHaveValue('Saved banner')); + expect(mocks.save).not.toHaveBeenCalled(); + expect(mocks.clear).not.toHaveBeenCalled(); + expect(notification.target).toBe(target); + if (target === 'Legacy') { + expect(screen.getByText(/This saved notification is hidden/)).toBeInTheDocument(); + } else { + expect(screen.queryByText(/This saved notification is hidden/)).not.toBeInTheDocument(); + } + + await fireEvent.click(screen.getByRole('button', { name: 'Update Notification' })); + + await waitFor(() => expect(mocks.save).toHaveBeenCalledExactlyOnceWith({ level: 'Warning', message: 'Saved banner', target: 'Both' })); + }); +}); diff --git a/src/Exceptionless.Web/ClientApp/src/routes/+layout.svelte b/src/Exceptionless.Web/ClientApp/src/routes/+layout.svelte index 3a8cfb960c..8cc87b1719 100644 --- a/src/Exceptionless.Web/ClientApp/src/routes/+layout.svelte +++ b/src/Exceptionless.Web/ClientApp/src/routes/+layout.svelte @@ -1,16 +1,18 @@
<exceptionless apiKey=\"API_KEY_HERE\" />
Exceptionless
ExceptionlessClient.Default.Startup(\"{{apiKey}}\")
services.AddExceptionless(\"{{apiKey}}\")
app.UseExceptionless()
Exceptionless.ExceptionlessClient.Default.RegisterNancy(pipelines)
ExceptionlessClient.Default.Register()
ExceptionlessClient.Default.RegisterWebApi(config)
Exceptionless.ExceptionlessClient.Default.RegisterWebApi(GlobalConfiguration.Configuration)
{{sampleCode}}
This dashboard could not be opened. Check that you still have access and try again.
Opening dashboard…
Opening billing…