diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml
index 0a7c8f38..9cc0e762 100644
--- a/.github/workflows/docker.yml
+++ b/.github/workflows/docker.yml
@@ -54,14 +54,16 @@ jobs:
echo "VERSION=$NEW_VERSION" >> $GITHUB_OUTPUT
echo "$CURRENT → $NEW_VERSION ($BUMP)"
- # Update both package.json files
+ # Update package.json files and Helm chart
cd server && npm version "$NEW_VERSION" --no-git-tag-version && cd ..
cd client && npm version "$NEW_VERSION" --no-git-tag-version && cd ..
+ sed -i "s/^version: .*/version: $NEW_VERSION/" charts/trek/Chart.yaml
+ sed -i "s/^appVersion: .*/appVersion: \"$NEW_VERSION\"/" charts/trek/Chart.yaml
# Commit and tag
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
- git add server/package.json server/package-lock.json client/package.json client/package-lock.json
+ git add server/package.json server/package-lock.json client/package.json client/package-lock.json charts/trek/Chart.yaml
git commit -m "chore: bump version to $NEW_VERSION [skip ci]"
git tag "v$NEW_VERSION"
git push origin main --follow-tags
@@ -151,3 +153,18 @@ jobs:
- name: Inspect manifest
run: docker buildx imagetools inspect mauriceboe/trek:latest
+
+ release-helm:
+ runs-on: ubuntu-latest
+ needs: version-bump
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+ with:
+ ref: main
+
+ - name: Publish Helm chart
+ uses: stefanprodan/helm-gh-pages@v1.7.0
+ with:
+ token: ${{ secrets.GITHUB_TOKEN }}
+ charts_dir: charts
diff --git a/MCP.md b/MCP.md
index d6db9fa6..cbd924ad 100644
--- a/MCP.md
+++ b/MCP.md
@@ -9,6 +9,10 @@ structured API.
## Table of Contents
- [Setup](#setup)
+ - [Option A: OAuth 2.1 (recommended)](#option-a-oauth-21-recommended)
+ - [Option B: Static API Token (deprecated)](#option-b-static-api-token-deprecated)
+- [Authentication](#authentication)
+- [OAuth Scopes](#oauth-scopes)
- [Limitations & Important Notes](#limitations--important-notes)
- [Resources (read-only)](#resources-read-only)
- [Tools (read-write)](#tools-read-write)
@@ -22,22 +26,51 @@ structured API.
### 1. Enable the MCP addon (admin)
An administrator must first enable the MCP addon from the **Admin Panel > Addons** page. Until enabled, the `/mcp`
-endpoint returns `403 Forbidden` and the MCP section does not appear in user settings.
+endpoint returns `404` and the MCP section does not appear in user settings.
-### 2. Create an API token
+### 2. Connect your MCP client
-Once MCP is enabled, go to **Settings > MCP Configuration** and create an API token:
+#### Option A: OAuth 2.1 (recommended)
-1. Click **Create New Token**
-2. Give it a descriptive name (e.g. "Claude Desktop", "Work laptop")
-3. **Copy the token immediately** — it is shown only once and cannot be recovered
+MCP clients that support OAuth 2.1 (such as Claude Desktop via `mcp-remote`) authenticate automatically. No token
+management required — just provide the server URL:
-Each user can create up to **10 tokens**.
+```json
+{
+ "mcpServers": {
+ "trek": {
+ "command": "npx",
+ "args": [
+ "mcp-remote",
+ "https://your-trek-instance.com/mcp"
+ ]
+ }
+ }
+}
+```
-### 3. Configure your MCP client
+> The path to `npx` may need to be adjusted for your system (e.g. `C:\PROGRA~1\nodejs\npx.cmd` on Windows).
-The Settings page shows a ready-to-copy client configuration snippet. For **Claude Desktop**, add the following to your
-`claude_desktop_config.json`:
+**What happens automatically:**
+1. The client fetches `/.well-known/oauth-authorization-server` to discover the TREK authorization server.
+2. The client registers itself via [Dynamic Client Registration (RFC 7591)](https://www.rfc-editor.org/rfc/rfc7591).
+3. Your browser opens TREK's consent screen, where you choose which scopes (permissions) to grant.
+4. The client receives a short-lived access token and a rotating refresh token — no re-authorization needed.
+
+> **Requirement:** The `APP_URL` environment variable must be set to your TREK instance's public URL for OAuth
+> discovery to work correctly.
+
+**For more control over scopes or to use confidential client mode**, pre-create an OAuth client in
+**Settings > Integrations > MCP > OAuth Clients** before connecting. Clients created there have a client secret
+(`trekcs_` prefix) and fixed scopes that you define up front.
+
+#### Option B: Static API Token (deprecated)
+
+> **Deprecated:** Static API tokens will stop working in a future version. Migrate to OAuth 2.1 above.
+
+1. Go to **Settings > Integrations > MCP** and create an API token.
+2. Click **Create New Token**, give it a name, and **copy the token immediately** — it is shown only once.
+3. Add it to your `claude_desktop_config.json`:
```json
{
@@ -55,7 +88,65 @@ The Settings page shows a ready-to-copy client configuration snippet. For **Clau
}
```
-> The path to `npx` may need to be adjusted for your system (e.g. `C:\PROGRA~1\nodejs\npx.cmd` on Windows).
+Static tokens grant full access to all tools and resources (no scope restrictions). Sessions authenticated with a
+static token will receive deprecation warnings in the AI client via server instructions and tool results.
+
+Each user can create up to **10 static tokens**.
+
+---
+
+## Authentication
+
+TREK's MCP server supports three authentication methods. OAuth 2.1 is the recommended path for all external clients.
+
+| Method | Token prefix | Access level | TTL | Notes |
+|--------|-------------|-------------|-----|-------|
+| **OAuth 2.1** | `trekoa_` | Scoped (per-consent) | 1 hour | Recommended. Automatically refreshed via 30-day rolling refresh tokens (`trekrf_` prefix). Replay-detected rotation — replayed tokens cascade-revoke the entire chain. |
+| **Static API token** | `trek_` | Full access | No expiry | **Deprecated.** Triggers deprecation warnings in AI clients. Will be removed in a future release. |
+| **Web session JWT** | — | Full access | Session-based | Used internally by the TREK web UI. Not intended for external clients. |
+
+All methods require the `Authorization: Bearer ` header (strict scheme enforcement — `Bearer` required).
+
+---
+
+## OAuth Scopes
+
+When connecting via OAuth 2.1, you grant specific scopes during the consent step. TREK registers only the MCP tools
+that match your granted scopes for that session.
+
+| Scope | Permission | Group |
+|-------|-----------|-------|
+| `trips:read` | View trips & itineraries | Trips |
+| `trips:write` | Edit trips & itineraries | Trips |
+| `trips:delete` | Delete trips (irreversible) | Trips |
+| `trips:share` | Manage share links | Trips |
+| `places:read` | View places & map data | Places |
+| `places:write` | Manage places | Places |
+| `atlas:read` | View Atlas | Atlas |
+| `atlas:write` | Manage Atlas | Atlas |
+| `packing:read` | View packing lists | Packing |
+| `packing:write` | Manage packing lists | Packing |
+| `todos:read` | View to-do lists | To-dos |
+| `todos:write` | Manage to-do lists | To-dos |
+| `budget:read` | View budget | Budget |
+| `budget:write` | Manage budget | Budget |
+| `reservations:read` | View reservations | Reservations |
+| `reservations:write` | Manage reservations | Reservations |
+| `collab:read` | View collaboration | Collaboration |
+| `collab:write` | Manage collaboration | Collaboration |
+| `notifications:read` | View notifications | Notifications |
+| `notifications:write` | Manage notifications | Notifications |
+| `vacay:read` | View vacation plans | Vacation |
+| `vacay:write` | Manage vacation plans | Vacation |
+| `geo:read` | Maps & geocoding | Geo |
+| `weather:read` | Weather forecasts | Weather |
+
+**Scope rules:**
+- A `:write` scope implies `:read` access for the same group (e.g. `budget:write` also grants budget read access).
+- Any `trips:*` scope (`trips:read`, `trips:write`, `trips:delete`, or `trips:share`) grants trip read access.
+- `list_trips` and `get_trip_summary` are **always available** regardless of scopes — they are navigation tools.
+- Static tokens and web session JWTs have full access to all tools (equivalent to all scopes).
+- Addon-gated tools (Atlas Extended, Collab, Vacay) require both the relevant scope **and** the addon to be enabled.
---
@@ -68,10 +159,13 @@ The Settings page shows a ready-to-copy client configuration snippet. For **Clau
| **No image uploads** | Cover images cannot be set through MCP. Use the web UI to upload trip covers. |
| **Reservations are created as pending** | When the AI creates a reservation, it starts with `pending` status. You must confirm it manually or ask the AI to set the status to `confirmed`. |
| **Demo mode restrictions** | If TREK is running in demo mode, all write operations through MCP are blocked. |
-| **Rate limiting** | 60 requests per minute per user. Exceeding this returns a `429` error. |
-| **Session limits** | Maximum 5 concurrent MCP sessions per user. Sessions expire after 1 hour of inactivity. |
-| **Token limits** | Maximum 10 API tokens per user. |
-| **Token revocation** | Deleting a token immediately terminates all active MCP sessions for that user. |
+| **Rate limiting** | 300 requests per minute per user (configurable via `MCP_RATE_LIMIT`). Exceeding this returns a `429` error. |
+| **Per-client rate limiting** | Rate limits are tracked per user-client pair, so each OAuth client has its own independent rate limit window. |
+| **Session limits** | Maximum 20 concurrent MCP sessions per user (configurable via `MCP_MAX_SESSION_PER_USER`). Sessions expire after 1 hour of inactivity. |
+| **Token limits** | Maximum 10 static API tokens per user. Maximum 10 OAuth clients per user. |
+| **Token revocation** | Deleting a static token or revoking an OAuth session immediately terminates all active MCP sessions for that token/client. |
+| **OAuth scope enforcement** | Only tools matching your granted OAuth scopes are registered in the session. Calling an out-of-scope tool returns an error. |
+| **Addon toggle invalidation** | When an admin enables or disables an addon, all active MCP sessions are invalidated and must be re-established. |
| **Real-time sync** | Changes made through MCP are broadcast to all connected clients in real-time via WebSocket, just like changes made through the web UI. |
| **Addon-gated features** | Some resources and tools are only available when the corresponding addon (Atlas, Collab, Vacay) is enabled by an admin. |
@@ -356,11 +450,12 @@ trip in a single call.
MCP prompts are pre-built context loaders your AI client can invoke to get a structured starting point for common tasks.
-| Prompt | Description |
-|-------------------|---------------------------------------------------------------------------------|
-| `trip-summary` | Load a formatted summary of a trip (dates, members, days, budget, packing, reservations) before planning or modifying it. |
-| `packing-list` | Get a formatted packing checklist for a trip, grouped by category. |
-| `budget-overview` | Get a formatted budget summary with totals by category and per-person cost. |
+| Prompt | Description |
+|----------------------|---------------------------------------------------------------------------------|
+| `trip-summary` | Load a formatted summary of a trip (dates, members, days, budget, packing, reservations) before planning or modifying it. |
+| `packing-list` | Get a formatted packing checklist for a trip, grouped by category. |
+| `budget-overview` | Get a formatted budget summary with totals by category and per-person cost. |
+| `token_auth_notice` | Static token deprecation notice and migration guide. Only available in sessions authenticated with a legacy `trek_` token. |
---
diff --git a/README.md b/README.md
index e0983450..629ed01d 100644
--- a/README.md
+++ b/README.md
@@ -9,7 +9,7 @@
-
+
@@ -77,7 +77,8 @@
- **Dashboard Widgets** — Currency converter and timezone clock, toggleable per user
### AI / MCP Integration
-- **MCP Server** — Built-in [Model Context Protocol](MCP.md) server exposes 80+ tools and 27 resources so AI assistants (Claude, Cursor, etc.) can read and modify your trips
+- **MCP Server** — Built-in [Model Context Protocol](MCP.md) server with OAuth 2.1 authentication exposes 80+ tools and 27 resources so AI assistants (Claude, Cursor, etc.) can read and modify your trips
+- **Granular Scopes** — 24 OAuth scopes across 13 permission groups let you control exactly what data your AI client can access
- **Full Trip Automation** — AI can create trips, plan itineraries, build packing lists, manage budgets, send collab messages, mark countries visited, and more in a single conversation
- **Prompts** — Pre-built `trip-summary`, `packing-list`, and `budget-overview` prompts give AI clients instant structured context
- **Addon-Aware** — Atlas, Collab, and Vacay features are exposed automatically when those addons are enabled
@@ -97,11 +98,23 @@
- **PWA**: vite-plugin-pwa + Workbox
- **Real-Time**: WebSocket (`ws`)
- **State**: Zustand
-- **Auth**: JWT + OIDC + TOTP (MFA)
+- **Auth**: JWT + OAuth 2.1 + OIDC + TOTP (MFA)
- **Maps**: Leaflet + react-leaflet-cluster + Google Places API (optional)
- **Weather**: Open-Meteo API (free, no key required)
- **Icons**: lucide-react
+## Helm (Kubernetes)
+
+A hosted Helm repository is available:
+
+```sh
+helm repo add trek https://mauriceboe.github.io/TREK
+helm repo update
+helm install trek trek/trek
+```
+
+See [`charts/README.md`](charts/README.md) for configuration options.
+
## Quick Start
```bash
@@ -149,9 +162,9 @@ services:
- TZ=${TZ:-UTC} # Timezone for logs, reminders and scheduled tasks (e.g. Europe/Berlin)
- LOG_LEVEL=${LOG_LEVEL:-info} # info = concise user actions; debug = verbose admin-level details
- ALLOWED_ORIGINS=${ALLOWED_ORIGINS:-} # Comma-separated origins for CORS and email notification links
- - FORCE_HTTPS=true # Redirect HTTP to HTTPS when behind a TLS-terminating proxy
- # - COOKIE_SECURE=false # Uncomment if accessing over plain HTTP (no HTTPS). Not recommended for production.
- - TRUST_PROXY=1 # Number of trusted proxies for X-Forwarded-For
+ # - FORCE_HTTPS=true # Optional. Enables HTTPS redirect, HSTS, CSP upgrade-insecure-requests, and secure cookies behind a TLS proxy
+ # - COOKIE_SECURE=false # Escape hatch: force session cookies over plain HTTP even in production. Not recommended.
+ # - TRUST_PROXY=1 # Trusted proxy count for X-Forwarded-For / X-Forwarded-Proto. Required for FORCE_HTTPS to work.
# - ALLOW_INTERNAL_NETWORK=true # Uncomment if Immich or other services are on your local network (RFC-1918 IPs)
- APP_URL=${APP_URL:-} # Base URL of this instance — required when OIDC is enabled; must match the redirect URI registered with your IdP; Also used as the base URL for email notifications and other external links
# - OIDC_ISSUER=https://auth.example.com # OpenID Connect provider URL
@@ -166,8 +179,8 @@ services:
# - DEMO_MODE=false # Enable demo mode (resets data hourly)
# - ADMIN_EMAIL=admin@trek.local # Initial admin e-mail — only used on first boot when no users exist
# - ADMIN_PASSWORD=changeme # Initial admin password — only used on first boot when no users exist
- # - MCP_RATE_LIMIT=60 # Max MCP API requests per user per minute (default: 60)
- # - MCP_MAX_SESSION_PER_USER=5 # Max concurrent MCP sessions per user (default: 5)
+ # - MCP_RATE_LIMIT=300 # Max MCP API requests per user per minute (default: 300)
+ # - MCP_MAX_SESSION_PER_USER=20 # Max concurrent MCP sessions per user (default: 20)
volumes:
- ./data:/app/data
- ./uploads:/app/uploads
@@ -180,7 +193,13 @@ services:
start_period: 15s
```
-This example is aimed at reverse-proxy deployments. If you access TREK directly on `http://:3000` without nginx, Caddy, Traefik, or another TLS-terminating proxy in front of it, set `FORCE_HTTPS=false` and remove `TRUST_PROXY` to avoid redirects to a non-existent HTTPS endpoint.
+This example is aimed at reverse-proxy deployments where nginx, Caddy, Traefik, or a similar proxy terminates TLS in front of TREK. The three HTTPS-related variables work together:
+
+- **`FORCE_HTTPS`** is 100% optional. When set to `true` it does four things: adds an HTTP-to-HTTPS 301 redirect, sends an HSTS header (`max-age=31536000`), adds the CSP `upgrade-insecure-requests` directive, and forces the session cookie `secure` flag on. It only makes sense behind a TLS-terminating proxy.
+- **`TRUST_PROXY`** tells Express how many proxies sit in front of TREK so it can read the real client IP from `X-Forwarded-For` and the protocol from `X-Forwarded-Proto`. Without it, `FORCE_HTTPS` redirects will loop because Express never sees the request as secure. In production (`NODE_ENV=production`) this defaults to `1` automatically; in development it is off unless explicitly set.
+- **`COOKIE_SECURE`** is normally auto-derived — the session cookie is marked `secure` whenever `NODE_ENV=production` or `FORCE_HTTPS=true`. Setting `COOKIE_SECURE=false` is an escape hatch that disables the `secure` flag even in production (e.g. testing over plain HTTP on a LAN). Do not disable it in real deployments.
+
+If you access TREK directly on `http://:3000` with no reverse proxy, leave `FORCE_HTTPS` unset (or remove it) and remove `TRUST_PROXY` to avoid redirect loops to a non-existent HTTPS endpoint.
```bash
docker compose up -d
@@ -291,9 +310,9 @@ trek.yourdomain.com {
| `TZ` | Timezone for logs, reminders and cron jobs (e.g. `Europe/Berlin`) | `UTC` |
| `LOG_LEVEL` | `info` = concise user actions, `debug` = verbose details | `info` |
| `ALLOWED_ORIGINS` | Comma-separated origins for CORS and email links | same-origin |
-| `FORCE_HTTPS` | Redirect HTTP to HTTPS behind a TLS-terminating proxy. If you access TREK directly on `http://host:3000`, keep this `false`. | `false` |
-| `COOKIE_SECURE` | Set to `false` to allow session cookies over plain HTTP (e.g. accessing via IP without HTTPS). Defaults to `true` in production. **Not recommended to disable in production.** | `true` |
-| `TRUST_PROXY` | Number of trusted reverse proxies for `X-Forwarded-For`. Use this only when TREK is actually behind a reverse proxy. | `1` |
+| `FORCE_HTTPS` | Optional. When `true`: 301-redirects HTTP to HTTPS, sends HSTS (`max-age=31536000`), adds CSP `upgrade-insecure-requests`, and forces the session cookie `secure` flag. Only useful behind a TLS-terminating reverse proxy. Requires `TRUST_PROXY` to be set so Express can detect the forwarded protocol. | `false` |
+| `COOKIE_SECURE` | Controls the `secure` flag on the `trek_session` cookie. Auto-derived: secure is on when `NODE_ENV=production` **or** `FORCE_HTTPS=true`. Set to `false` as an escape hatch to allow session cookies over plain HTTP (e.g. LAN testing without TLS). **Not recommended to disable in production.** | auto (`true` in production) |
+| `TRUST_PROXY` | Number of trusted reverse proxies. Tells Express to read client IP from `X-Forwarded-For` and protocol from `X-Forwarded-Proto`. Activates automatically in production (defaults to `1`); off in development unless explicitly set. Must be set for `FORCE_HTTPS` redirects to work correctly. | `1` (when active) |
| `ALLOW_INTERNAL_NETWORK` | Allow outbound requests to private/RFC-1918 IP addresses. Set to `true` if Immich or other integrated services are hosted on your local network. Loopback (`127.x`) and link-local/metadata addresses (`169.254.x`) are always blocked regardless of this setting. | `false` |
| `APP_URL` | Public base URL of this instance (e.g. `https://trek.example.com`). Required when OIDC is enabled — must match the redirect URI registered with your IdP. Also used as the base URL for external links in email notifications. | — |
| **OIDC / SSO** | | |
@@ -311,8 +330,8 @@ trek.yourdomain.com {
| `ADMIN_PASSWORD` | Password for the first admin account created on initial boot. Must be set together with `ADMIN_EMAIL`. | random |
| **Other** | | |
| `DEMO_MODE` | Enable demo mode (hourly data resets) | `false` |
-| `MCP_RATE_LIMIT` | Max MCP API requests per user per minute | `60` |
-| `MCP_MAX_SESSION_PER_USER` | Max concurrent MCP sessions per user | `5` |
+| `MCP_RATE_LIMIT` | Max MCP API requests per user per minute | `300` |
+| `MCP_MAX_SESSION_PER_USER` | Max concurrent MCP sessions per user | `20` |
## Optional API Keys
diff --git a/chart/README.md b/charts/README.md
similarity index 68%
rename from chart/README.md
rename to charts/README.md
index 87fc2fc2..b5380f85 100644
--- a/chart/README.md
+++ b/charts/README.md
@@ -10,8 +10,20 @@ This is a minimal Helm chart for deploying the TREK app.
- Optional generic Ingress support
- Health checks on `/api/health`
+## Helm Repository
+
+A hosted Helm repository is available:
+
+```sh
+helm repo add trek https://mauriceboe.github.io/TREK
+helm repo update
+helm install trek trek/trek
+```
+
## Usage
+Or install directly from the local chart:
+
```sh
helm install trek ./chart \
--set ingress.enabled=true \
@@ -32,5 +44,7 @@ See `values.yaml` for more options.
- `ENCRYPTION_KEY` encrypts stored secrets (API keys, MFA, SMTP, OIDC) at rest. Recommended: set via `secretEnv.ENCRYPTION_KEY` or `existingSecret`. If left empty, the server falls back automatically: existing installs use `data/.jwt_secret` (no action needed on upgrade); fresh installs auto-generate a key persisted to the data PVC.
- If using ingress, you must manually keep `env.ALLOWED_ORIGINS` and `ingress.hosts` in sync to ensure CORS works correctly. The chart does not sync these automatically.
- Set `env.ALLOW_INTERNAL_NETWORK: "true"` if Immich or other integrated services are hosted on a private/RFC-1918 address (e.g. a pod on the same cluster or a NAS on your LAN). Loopback (`127.x`) and link-local/metadata addresses (`169.254.x`) remain blocked regardless.
-- Set `env.COOKIE_SECURE: "false"` only if your deployment has no TLS (e.g. during local testing without ingress). Session cookies require HTTPS in all other cases.
+- `FORCE_HTTPS` is optional. Set `env.FORCE_HTTPS: "true"` only when ingress (or another proxy) terminates TLS. It enables HTTPS redirects, HSTS, CSP `upgrade-insecure-requests`, and forces the session cookie `secure` flag. Requires `TRUST_PROXY` to be set.
+- Set `env.TRUST_PROXY: "1"` (or the number of proxy hops) when running behind ingress or a load balancer. Required for `FORCE_HTTPS` to detect the forwarded protocol correctly. In production it defaults to `1` automatically.
+- `COOKIE_SECURE` is auto-derived (on when `NODE_ENV=production` or `FORCE_HTTPS=true`). Set `env.COOKIE_SECURE: "false"` only during local testing without TLS. **Not recommended for production.**
- Set `env.OIDC_DISCOVERY_URL` to override the auto-constructed OIDC discovery endpoint. Required for providers (e.g. Authentik) that expose it at a non-standard path.
diff --git a/chart/Chart.yaml b/charts/trek/Chart.yaml
similarity index 65%
rename from chart/Chart.yaml
rename to charts/trek/Chart.yaml
index 886ba48f..914f839d 100644
--- a/chart/Chart.yaml
+++ b/charts/trek/Chart.yaml
@@ -1,5 +1,5 @@
apiVersion: v2
name: trek
-version: 0.1.0
+version: 2.9.13
description: Minimal Helm chart for TREK app
-appVersion: "latest"
+appVersion: "2.9.13"
diff --git a/chart/templates/NOTES.txt b/charts/trek/templates/NOTES.txt
similarity index 100%
rename from chart/templates/NOTES.txt
rename to charts/trek/templates/NOTES.txt
diff --git a/chart/templates/_helpers.tpl b/charts/trek/templates/_helpers.tpl
similarity index 100%
rename from chart/templates/_helpers.tpl
rename to charts/trek/templates/_helpers.tpl
diff --git a/chart/templates/configmap.yaml b/charts/trek/templates/configmap.yaml
similarity index 100%
rename from chart/templates/configmap.yaml
rename to charts/trek/templates/configmap.yaml
diff --git a/chart/templates/deployment.yaml b/charts/trek/templates/deployment.yaml
similarity index 98%
rename from chart/templates/deployment.yaml
rename to charts/trek/templates/deployment.yaml
index 0ab074ba..d79ae344 100644
--- a/chart/templates/deployment.yaml
+++ b/charts/trek/templates/deployment.yaml
@@ -27,7 +27,7 @@ spec:
fsGroup: 1000
containers:
- name: trek
- image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
+ image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
{{- with .Values.resources }}
resources:
diff --git a/chart/templates/ingress.yaml b/charts/trek/templates/ingress.yaml
similarity index 100%
rename from chart/templates/ingress.yaml
rename to charts/trek/templates/ingress.yaml
diff --git a/chart/templates/pvc.yaml b/charts/trek/templates/pvc.yaml
similarity index 100%
rename from chart/templates/pvc.yaml
rename to charts/trek/templates/pvc.yaml
diff --git a/chart/templates/secret.yaml b/charts/trek/templates/secret.yaml
similarity index 100%
rename from chart/templates/secret.yaml
rename to charts/trek/templates/secret.yaml
diff --git a/chart/templates/service.yaml b/charts/trek/templates/service.yaml
similarity index 100%
rename from chart/templates/service.yaml
rename to charts/trek/templates/service.yaml
diff --git a/chart/values.yaml b/charts/trek/values.yaml
similarity index 85%
rename from chart/values.yaml
rename to charts/trek/values.yaml
index 47a941c7..9063f06b 100644
--- a/chart/values.yaml
+++ b/charts/trek/values.yaml
@@ -1,7 +1,7 @@
image:
repository: mauriceboe/trek
- tag: latest
+ # tag: latest
pullPolicy: IfNotPresent
# Optional image pull secrets for private registries
@@ -25,11 +25,11 @@ env:
# Public base URL of this instance. Required when OIDC is enabled — must match the redirect URI registered with your IdP.
# Also used as the base URL for links in email notifications and other external links.
# FORCE_HTTPS: "false"
- # Set to "true" to redirect HTTP to HTTPS behind a TLS-terminating proxy.
+ # Optional. When "true": HTTPS redirect, HSTS, CSP upgrade-insecure-requests, secure cookies. Only behind a TLS proxy. Requires TRUST_PROXY.
# COOKIE_SECURE: "true"
- # Set to "false" to allow session cookies over plain HTTP (e.g. no ingress TLS). Not recommended for production.
+ # Auto-derived (true in production or when FORCE_HTTPS=true). Set "false" to force cookies over plain HTTP. Not recommended for production.
# TRUST_PROXY: "1"
- # Number of trusted reverse proxies for X-Forwarded-For header parsing.
+ # Trusted proxy hops for X-Forwarded-For/X-Forwarded-Proto. Defaults to 1 in production. Must be set for FORCE_HTTPS to work.
# ALLOW_INTERNAL_NETWORK: "false"
# Set to "true" if Immich or other integrated services are hosted on a private/RFC-1918 network address.
# Loopback (127.x) and link-local/metadata addresses (169.254.x) are always blocked.
@@ -51,10 +51,10 @@ env:
# Override the OIDC discovery endpoint for providers with non-standard paths (e.g. Authentik).
# DEMO_MODE: "false"
# Enable demo mode (hourly data resets).
- # MCP_RATE_LIMIT: "60"
- # Max MCP API requests per user per minute. Defaults to 60.
- # MCP_MAX_SESSION_PER_USER: "5"
- # Max concurrent MCP sessions per user. Defaults to 5.
+ # MCP_RATE_LIMIT: "300"
+ # Max MCP API requests per user per minute. Defaults to 300.
+ # MCP_MAX_SESSION_PER_USER: "20"
+ # Max concurrent MCP sessions per user. Defaults to 20.
# Secret environment variables stored in a Kubernetes Secret.
diff --git a/client/package-lock.json b/client/package-lock.json
index e099db1d..75d58947 100644
--- a/client/package-lock.json
+++ b/client/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "trek-client",
- "version": "2.9.12",
+ "version": "2.9.13",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "trek-client",
- "version": "2.9.12",
+ "version": "2.9.13",
"dependencies": {
"@react-pdf/renderer": "^4.3.2",
"axios": "^1.6.7",
@@ -34,7 +34,7 @@
"@types/react-dom": "^18.2.19",
"@types/react-window": "^1.8.8",
"@vitejs/plugin-react": "^4.2.1",
- "@vitest/coverage-v8": "^4.1.2",
+ "@vitest/coverage-v8": "^3.2.4",
"autoprefixer": "^10.4.18",
"jsdom": "^29.0.1",
"msw": "^2.13.0",
@@ -44,7 +44,7 @@
"typescript": "^6.0.2",
"vite": "^5.1.4",
"vite-plugin-pwa": "^0.21.0",
- "vitest": "^4.1.2"
+ "vitest": "^3.2.4"
}
},
"node_modules/@adobe/css-tools": {
@@ -67,15 +67,28 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/@ampproject/remapping": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz",
+ "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.5",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
"node_modules/@apideck/better-ajv-errors": {
- "version": "0.3.6",
- "resolved": "https://registry.npmjs.org/@apideck/better-ajv-errors/-/better-ajv-errors-0.3.6.tgz",
- "integrity": "sha512-P+ZygBLZtkp0qqOAJJVX4oX/sFo5JR3eBWwwuqHHhK0GIgQOKWrAfiAaWX0aArHkRWHMuggFEgAZNxVPwPZYaA==",
+ "version": "0.3.7",
+ "resolved": "https://registry.npmjs.org/@apideck/better-ajv-errors/-/better-ajv-errors-0.3.7.tgz",
+ "integrity": "sha512-TajUJwGWbDwkCx/CZi7tRE8PVB7simCvKJfHUsSdvps+aTM/PDPP4gkLmKnc+x3CE//y9i/nj74GqdL/hwk7Iw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "json-schema": "^0.4.0",
- "jsonpointer": "^5.0.0",
+ "jsonpointer": "^5.0.1",
"leven": "^3.1.0"
},
"engines": {
@@ -86,9 +99,9 @@
}
},
"node_modules/@asamuzakjp/css-color": {
- "version": "5.1.6",
- "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-5.1.6.tgz",
- "integrity": "sha512-BXWCh8dHs9GOfpo/fWGDJtDmleta2VePN9rn6WQt3GjEbxzutVF4t0x2pmH+7dbMCLtuv3MlwqRsAuxlzFXqFg==",
+ "version": "5.1.9",
+ "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-5.1.9.tgz",
+ "integrity": "sha512-zd9c/Wdso6v1U7v6w3i/hbAr4K7NaSHImdpvmLt+Y9ea5BhilnIGNkfhOJ7FEIuPipAnE9tZeDOll05WDT0kgg==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -102,9 +115,9 @@
}
},
"node_modules/@asamuzakjp/dom-selector": {
- "version": "7.0.7",
- "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-7.0.7.tgz",
- "integrity": "sha512-d2BgqDUOS1Hfp4IzKUZqCNz+Kg3Y88AkaBvJK/ZVSQPU1f7OpPNi7nQTH6/oI47Dkdg+Z3e8Yp6ynOu4UMINAQ==",
+ "version": "7.0.9",
+ "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-7.0.9.tgz",
+ "integrity": "sha512-r3ElRr7y8ucyN2KdICwGsmj19RoN13CLCa/pvGydghWK6ZzeKQ+TcDjVdtEZz2ElpndM5jXw//B9CEee0mWnVg==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -1842,23 +1855,10 @@
"node": ">=20.19.0"
}
},
- "node_modules/@emnapi/core": {
- "version": "1.9.2",
- "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.2.tgz",
- "integrity": "sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA==",
- "dev": true,
- "license": "MIT",
- "optional": true,
- "peer": true,
- "dependencies": {
- "@emnapi/wasi-threads": "1.2.1",
- "tslib": "^2.4.0"
- }
- },
"node_modules/@emnapi/runtime": {
- "version": "1.9.1",
- "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.1.tgz",
- "integrity": "sha512-VYi5+ZVLhpgK4hQ0TAjiQiZ6ol0oe4mBx7mVv7IflsiEp0OWoVsp/+f9Vc1hOhE0TtkORVrI1GvzyreqpgWtkA==",
+ "version": "1.9.2",
+ "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.2.tgz",
+ "integrity": "sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw==",
"dev": true,
"license": "MIT",
"optional": true,
@@ -1866,16 +1866,395 @@
"tslib": "^2.4.0"
}
},
- "node_modules/@emnapi/wasi-threads": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz",
- "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==",
+ "node_modules/@esbuild/aix-ppc64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
+ "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==",
+ "cpu": [
+ "ppc64"
+ ],
"dev": true,
"license": "MIT",
"optional": true,
- "peer": true,
- "dependencies": {
- "tslib": "^2.4.0"
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz",
+ "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz",
+ "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz",
+ "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz",
+ "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz",
+ "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz",
+ "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz",
+ "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz",
+ "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz",
+ "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz",
+ "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz",
+ "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz",
+ "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz",
+ "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz",
+ "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz",
+ "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz",
+ "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz",
+ "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz",
+ "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz",
+ "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz",
+ "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz",
+ "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz",
+ "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
}
},
"node_modules/@exodus/bytes": {
@@ -2365,13 +2744,62 @@
}
},
"node_modules/@isaacs/cliui": {
- "version": "9.0.0",
- "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-9.0.0.tgz",
- "integrity": "sha512-AokJm4tuBHillT+FpMtxQ60n8ObyXBatq7jD2/JA9dxbDDokKQm8KMht5ibGzLVU9IJDIKK4TPKgMHEYMn3lMg==",
+ "version": "8.0.2",
+ "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
+ "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==",
"dev": true,
- "license": "BlueOak-1.0.0",
+ "license": "ISC",
+ "dependencies": {
+ "string-width": "^5.1.2",
+ "string-width-cjs": "npm:string-width@^4.2.0",
+ "strip-ansi": "^7.0.1",
+ "strip-ansi-cjs": "npm:strip-ansi@^6.0.1",
+ "wrap-ansi": "^8.1.0",
+ "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0"
+ },
"engines": {
- "node": ">=18"
+ "node": ">=12"
+ }
+ },
+ "node_modules/@isaacs/cliui/node_modules/ansi-styles": {
+ "version": "6.2.3",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz",
+ "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/@isaacs/cliui/node_modules/wrap-ansi": {
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
+ "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^6.1.0",
+ "string-width": "^5.0.1",
+ "strip-ansi": "^7.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+ }
+ },
+ "node_modules/@istanbuljs/schema": {
+ "version": "0.1.3",
+ "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz",
+ "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
}
},
"node_modules/@jridgewell/gen-mapping": {
@@ -2453,23 +2881,28 @@
"node": ">=18"
}
},
- "node_modules/@napi-rs/wasm-runtime": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.2.tgz",
- "integrity": "sha512-sNXv5oLJ7ob93xkZ1XnxisYhGYXfaG9f65/ZgYuAu3qt7b3NadcOEhLvx28hv31PgX8SZJRYrAIPQilQmFpLVw==",
- "dev": true,
+ "node_modules/@noble/ciphers": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-1.3.0.tgz",
+ "integrity": "sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw==",
"license": "MIT",
- "optional": true,
- "dependencies": {
- "@tybys/wasm-util": "^0.10.1"
+ "engines": {
+ "node": "^14.21.3 || >=16"
},
"funding": {
- "type": "github",
- "url": "https://github.com/sponsors/Brooooooklyn"
+ "url": "https://paulmillr.com/funding/"
+ }
+ },
+ "node_modules/@noble/hashes": {
+ "version": "1.8.0",
+ "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz",
+ "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==",
+ "license": "MIT",
+ "engines": {
+ "node": "^14.21.3 || >=16"
},
- "peerDependencies": {
- "@emnapi/core": "^1.7.1",
- "@emnapi/runtime": "^1.7.1"
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
}
},
"node_modules/@nodelib/fs.scandir": {
@@ -2535,14 +2968,15 @@
"dev": true,
"license": "MIT"
},
- "node_modules/@oxc-project/types": {
- "version": "0.122.0",
- "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.122.0.tgz",
- "integrity": "sha512-oLAl5kBpV4w69UtFZ9xqcmTi+GENWOcPF7FCrczTiBbmC0ibXxCwyvZGbO39rCVEuLGAZM84DH0pUIyyv/YJzA==",
+ "node_modules/@pkgjs/parseargs": {
+ "version": "0.11.0",
+ "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
+ "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==",
"dev": true,
"license": "MIT",
- "funding": {
- "url": "https://github.com/sponsors/Boshen"
+ "optional": true,
+ "engines": {
+ "node": ">=14"
}
},
"node_modules/@react-leaflet/core": {
@@ -2557,19 +2991,19 @@
}
},
"node_modules/@react-pdf/fns": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/@react-pdf/fns/-/fns-3.1.2.tgz",
- "integrity": "sha512-qTKGUf0iAMGg2+OsUcp9ffKnKi41RukM/zYIWMDJ4hRVYSr89Q7e3wSDW/Koqx3ea3Uy/z3h2y3wPX6Bdfxk6g==",
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/@react-pdf/fns/-/fns-3.1.3.tgz",
+ "integrity": "sha512-0I7pApDr1/RLAKbizuLy/IHTEa93LSPy/bEwYniboC3Xqnp6Od8xFJKbKEzGw2wh/5zKFFwl00g4t9RwgIMc3w==",
"license": "MIT"
},
"node_modules/@react-pdf/font": {
- "version": "4.0.4",
- "resolved": "https://registry.npmjs.org/@react-pdf/font/-/font-4.0.4.tgz",
- "integrity": "sha512-8YtgGtL511txIEc9AjiilpZ7yjid8uCd8OGUl6jaL3LIHnrToUupSN4IzsMQpVTCMYiDLFnDNQzpZsOYtRS/Pg==",
+ "version": "4.0.6",
+ "resolved": "https://registry.npmjs.org/@react-pdf/font/-/font-4.0.6.tgz",
+ "integrity": "sha512-1RxR/hTyZcbgjESUjrMms574xuS9PLB4ovqQx6jvgdrIHXUyeUtSH6i3Szw1qVfUnA9MfaEm1FBuydQeJD39BQ==",
"license": "MIT",
"dependencies": {
- "@react-pdf/pdfkit": "^4.1.0",
- "@react-pdf/types": "^2.9.2",
+ "@react-pdf/pdfkit": "^5.0.0",
+ "@react-pdf/types": "^2.10.0",
"fontkit": "^2.0.2",
"is-url": "^1.2.4"
}
@@ -2585,34 +3019,36 @@
}
},
"node_modules/@react-pdf/layout": {
- "version": "4.4.2",
- "resolved": "https://registry.npmjs.org/@react-pdf/layout/-/layout-4.4.2.tgz",
- "integrity": "sha512-gNu2oh8MiGR+NJZYTJ4c4q0nWCESBI6rKFiodVhE7OeVAjtzZzd6l65wsN7HXdWJqOZD3ttD97iE+tf5SOd/Yg==",
+ "version": "4.5.0",
+ "resolved": "https://registry.npmjs.org/@react-pdf/layout/-/layout-4.5.0.tgz",
+ "integrity": "sha512-XlGLzcZFdEYQHK6b9z9uPOVywbjv6pQ92D4RvqRmYNjpf7lQLnhdHr63tbiF7fB5k8Lg9/lGBXkHbzkeQW5geQ==",
"license": "MIT",
"dependencies": {
- "@react-pdf/fns": "3.1.2",
+ "@react-pdf/fns": "3.1.3",
"@react-pdf/image": "^3.0.4",
- "@react-pdf/primitives": "^4.1.1",
- "@react-pdf/stylesheet": "^6.1.2",
- "@react-pdf/textkit": "^6.1.0",
- "@react-pdf/types": "^2.9.2",
+ "@react-pdf/primitives": "^4.2.0",
+ "@react-pdf/stylesheet": "^6.1.4",
+ "@react-pdf/textkit": "^6.1.1",
+ "@react-pdf/types": "^2.10.0",
"emoji-regex-xs": "^1.0.0",
"queue": "^6.0.1",
"yoga-layout": "^3.2.1"
}
},
"node_modules/@react-pdf/pdfkit": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/@react-pdf/pdfkit/-/pdfkit-4.1.0.tgz",
- "integrity": "sha512-Wm/IOAv0h/U5Ra94c/PltFJGcpTUd/fwVMVeFD6X9tTTPCttIwg0teRG1Lqq617J8K4W7jpL/B0HTH0mjp3QpQ==",
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/@react-pdf/pdfkit/-/pdfkit-5.0.0.tgz",
+ "integrity": "sha512-FcQBWGtfhMGuOB0G3NcnF/cBq/JnFVs22i1tuafiT1XlmG6KjCxgTGng5bVh+b9RtTuwNpUGmCtB6CmG6B4ZVA==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.20.13",
+ "@noble/ciphers": "^1.0.0",
+ "@noble/hashes": "^1.6.0",
"@react-pdf/png-js": "^3.0.0",
"browserify-zlib": "^0.2.0",
- "crypto-js": "^4.2.0",
"fontkit": "^2.0.2",
"jay-peg": "^1.1.1",
+ "js-md5": "^0.8.3",
"linebreak": "^1.1.0",
"vite-compatible-readable-stream": "^3.6.1"
}
@@ -2627,9 +3063,9 @@
}
},
"node_modules/@react-pdf/primitives": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/@react-pdf/primitives/-/primitives-4.1.1.tgz",
- "integrity": "sha512-IuhxYls1luJb7NUWy6q5avb1XrNaVj9bTNI40U9qGRuS6n7Hje/8H8Qi99Z9UKFV74bBP3DOf3L1wV2qZVgVrQ==",
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/@react-pdf/primitives/-/primitives-4.2.0.tgz",
+ "integrity": "sha512-onlXLcA6SpsD7SX9HOyt55qdRRJCfauegPlo4ZNw0hA/IipaZTbT9MJliWKtEXm03ibGxAQyp/BgTuXm91fo0A==",
"license": "MIT"
},
"node_modules/@react-pdf/reconciler": {
@@ -2645,23 +3081,17 @@
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
- "node_modules/@react-pdf/reconciler/node_modules/scheduler": {
- "version": "0.25.0-rc-603e6108-20241029",
- "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.25.0-rc-603e6108-20241029.tgz",
- "integrity": "sha512-pFwF6H1XrSdYYNLfOcGlM28/j8CGLu8IvdrxqhjWULe2bPcKiKW4CV+OWqR/9fT52mywx65l7ysNkjLKBda7eA==",
- "license": "MIT"
- },
"node_modules/@react-pdf/render": {
- "version": "4.3.2",
- "resolved": "https://registry.npmjs.org/@react-pdf/render/-/render-4.3.2.tgz",
- "integrity": "sha512-el5KYM1sH/PKcO4tRCIm8/AIEmhtraaONbwCrBhFdehoGv6JtgnXiMxHGAvZbI5kEg051GbyP+XIU6f6YbOu6Q==",
+ "version": "4.4.0",
+ "resolved": "https://registry.npmjs.org/@react-pdf/render/-/render-4.4.0.tgz",
+ "integrity": "sha512-+gGa9ymGosN6Ld3hFFSIVCV03Vva5S+asW7vmKetZY4LnhX5ea2gYNy6wL3e9VsLHqFDAefIXGtGDLH6XxQHag==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.20.13",
- "@react-pdf/fns": "3.1.2",
- "@react-pdf/primitives": "^4.1.1",
- "@react-pdf/textkit": "^6.1.0",
- "@react-pdf/types": "^2.9.2",
+ "@react-pdf/fns": "3.1.3",
+ "@react-pdf/primitives": "^4.2.0",
+ "@react-pdf/textkit": "^6.1.1",
+ "@react-pdf/types": "^2.10.0",
"abs-svg-path": "^0.1.1",
"color-string": "^1.9.1",
"normalize-svg-path": "^1.1.0",
@@ -2670,20 +3100,20 @@
}
},
"node_modules/@react-pdf/renderer": {
- "version": "4.3.2",
- "resolved": "https://registry.npmjs.org/@react-pdf/renderer/-/renderer-4.3.2.tgz",
- "integrity": "sha512-EhPkj35gO9rXIyyx29W3j3axemvVY5RigMmlK4/6Ku0pXB8z9PEE/sz4ZBOShu2uot6V4xiCR3aG+t9IjJJlBQ==",
+ "version": "4.4.0",
+ "resolved": "https://registry.npmjs.org/@react-pdf/renderer/-/renderer-4.4.0.tgz",
+ "integrity": "sha512-TtCcz1vyYD6fddhvBagwr9Aj3gRFLCqvduERQyNhTzHJi3zAKayHvJFr2PxcP4sRyIDRhibDW6ApqNhTqIVPoQ==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.20.13",
- "@react-pdf/fns": "3.1.2",
- "@react-pdf/font": "^4.0.4",
- "@react-pdf/layout": "^4.4.2",
- "@react-pdf/pdfkit": "^4.1.0",
- "@react-pdf/primitives": "^4.1.1",
+ "@react-pdf/fns": "3.1.3",
+ "@react-pdf/font": "^4.0.6",
+ "@react-pdf/layout": "^4.5.0",
+ "@react-pdf/pdfkit": "^5.0.0",
+ "@react-pdf/primitives": "^4.2.0",
"@react-pdf/reconciler": "^2.0.0",
- "@react-pdf/render": "^4.3.2",
- "@react-pdf/types": "^2.9.2",
+ "@react-pdf/render": "^4.4.0",
+ "@react-pdf/types": "^2.10.0",
"events": "^3.3.0",
"object-assign": "^4.1.1",
"prop-types": "^15.6.2",
@@ -2694,13 +3124,13 @@
}
},
"node_modules/@react-pdf/stylesheet": {
- "version": "6.1.2",
- "resolved": "https://registry.npmjs.org/@react-pdf/stylesheet/-/stylesheet-6.1.2.tgz",
- "integrity": "sha512-E3ftGRYUQGKiN3JOgtGsLDo0hGekA6dmkmi/MYACytmPTKxQRBSO3126MebmCq+t1rgU9uRlREIEawJ+8nzSbw==",
+ "version": "6.1.4",
+ "resolved": "https://registry.npmjs.org/@react-pdf/stylesheet/-/stylesheet-6.1.4.tgz",
+ "integrity": "sha512-jiwovO7lUwgccAh3JbVcXnh90AiSKZetdz2ETcWsKApPPLzLUzPkEs6wCVvZqh3lcGOAPFV3AfdMkFnLwv1ryg==",
"license": "MIT",
"dependencies": {
- "@react-pdf/fns": "3.1.2",
- "@react-pdf/types": "^2.9.2",
+ "@react-pdf/fns": "3.1.3",
+ "@react-pdf/types": "^2.10.0",
"color-string": "^1.9.1",
"hsl-to-hex": "^1.0.0",
"media-engine": "^1.0.3",
@@ -2708,26 +3138,26 @@
}
},
"node_modules/@react-pdf/textkit": {
- "version": "6.1.0",
- "resolved": "https://registry.npmjs.org/@react-pdf/textkit/-/textkit-6.1.0.tgz",
- "integrity": "sha512-sFlzDC9CDFrJsnL3B/+NHrk9+Advqk7iJZIStiYQDdskbow8GF/AGYrpIk+vWSnh35YxaGbHkqXq53XOxnyrjQ==",
+ "version": "6.1.1",
+ "resolved": "https://registry.npmjs.org/@react-pdf/textkit/-/textkit-6.1.1.tgz",
+ "integrity": "sha512-HAHoa407q0UHLzwe/oL6VwgJj2cGKs5vORSVY+cRG/GC0kt7nxUV9N+2hA6VcqJA37gSRg7BTLsVr8Tt+4l5ow==",
"license": "MIT",
"dependencies": {
- "@react-pdf/fns": "3.1.2",
+ "@react-pdf/fns": "3.1.3",
"bidi-js": "^1.0.2",
"hyphen": "^1.6.4",
"unicode-properties": "^1.4.1"
}
},
"node_modules/@react-pdf/types": {
- "version": "2.9.2",
- "resolved": "https://registry.npmjs.org/@react-pdf/types/-/types-2.9.2.tgz",
- "integrity": "sha512-dufvpKId9OajLLbgn9q7VLUmyo1Jf+iyGk2ZHmCL8nIDtL8N1Ejh9TH7+pXXrR0tdie1nmnEb5Bz9U7g4hI4/g==",
+ "version": "2.10.0",
+ "resolved": "https://registry.npmjs.org/@react-pdf/types/-/types-2.10.0.tgz",
+ "integrity": "sha512-iz0NusqQ/9ZHQirWhJqOaxY1UkpvuNkEDtH4/SPCnhZJKBO/IhlFLFHuzbHkmWByBoX6X3m8GCc2b/1QH6QNlA==",
"license": "MIT",
"dependencies": {
- "@react-pdf/font": "^4.0.4",
- "@react-pdf/primitives": "^4.1.1",
- "@react-pdf/stylesheet": "^6.1.2"
+ "@react-pdf/font": "^4.0.6",
+ "@react-pdf/primitives": "^4.2.0",
+ "@react-pdf/stylesheet": "^6.1.4"
}
},
"node_modules/@remix-run/router": {
@@ -2739,261 +3169,6 @@
"node": ">=14.0.0"
}
},
- "node_modules/@rolldown/binding-android-arm64": {
- "version": "1.0.0-rc.12",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.12.tgz",
- "integrity": "sha512-pv1y2Fv0JybcykuiiD3qBOBdz6RteYojRFY1d+b95WVuzx211CRh+ytI/+9iVyWQ6koTh5dawe4S/yRfOFjgaA==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
- ],
- "engines": {
- "node": "^20.19.0 || >=22.12.0"
- }
- },
- "node_modules/@rolldown/binding-darwin-arm64": {
- "version": "1.0.0-rc.12",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.12.tgz",
- "integrity": "sha512-cFYr6zTG/3PXXF3pUO+umXxt1wkRK/0AYT8lDwuqvRC+LuKYWSAQAQZjCWDQpAH172ZV6ieYrNnFzVVcnSflAg==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": "^20.19.0 || >=22.12.0"
- }
- },
- "node_modules/@rolldown/binding-darwin-x64": {
- "version": "1.0.0-rc.12",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.12.tgz",
- "integrity": "sha512-ZCsYknnHzeXYps0lGBz8JrF37GpE9bFVefrlmDrAQhOEi4IOIlcoU1+FwHEtyXGx2VkYAvhu7dyBf75EJQffBw==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": "^20.19.0 || >=22.12.0"
- }
- },
- "node_modules/@rolldown/binding-freebsd-x64": {
- "version": "1.0.0-rc.12",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.12.tgz",
- "integrity": "sha512-dMLeprcVsyJsKolRXyoTH3NL6qtsT0Y2xeuEA8WQJquWFXkEC4bcu1rLZZSnZRMtAqwtrF/Ib9Ddtpa/Gkge9Q==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "freebsd"
- ],
- "engines": {
- "node": "^20.19.0 || >=22.12.0"
- }
- },
- "node_modules/@rolldown/binding-linux-arm-gnueabihf": {
- "version": "1.0.0-rc.12",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.12.tgz",
- "integrity": "sha512-YqWjAgGC/9M1lz3GR1r1rP79nMgo3mQiiA+Hfo+pvKFK1fAJ1bCi0ZQVh8noOqNacuY1qIcfyVfP6HoyBRZ85Q==",
- "cpu": [
- "arm"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": "^20.19.0 || >=22.12.0"
- }
- },
- "node_modules/@rolldown/binding-linux-arm64-gnu": {
- "version": "1.0.0-rc.12",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.12.tgz",
- "integrity": "sha512-/I5AS4cIroLpslsmzXfwbe5OmWvSsrFuEw3mwvbQ1kDxJ822hFHIx+vsN/TAzNVyepI/j/GSzrtCIwQPeKCLIg==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": "^20.19.0 || >=22.12.0"
- }
- },
- "node_modules/@rolldown/binding-linux-arm64-musl": {
- "version": "1.0.0-rc.12",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.12.tgz",
- "integrity": "sha512-V6/wZztnBqlx5hJQqNWwFdxIKN0m38p8Jas+VoSfgH54HSj9tKTt1dZvG6JRHcjh6D7TvrJPWFGaY9UBVOaWPw==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": "^20.19.0 || >=22.12.0"
- }
- },
- "node_modules/@rolldown/binding-linux-ppc64-gnu": {
- "version": "1.0.0-rc.12",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.12.tgz",
- "integrity": "sha512-AP3E9BpcUYliZCxa3w5Kwj9OtEVDYK6sVoUzy4vTOJsjPOgdaJZKFmN4oOlX0Wp0RPV2ETfmIra9x1xuayFB7g==",
- "cpu": [
- "ppc64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": "^20.19.0 || >=22.12.0"
- }
- },
- "node_modules/@rolldown/binding-linux-s390x-gnu": {
- "version": "1.0.0-rc.12",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.12.tgz",
- "integrity": "sha512-nWwpvUSPkoFmZo0kQazZYOrT7J5DGOJ/+QHHzjvNlooDZED8oH82Yg67HvehPPLAg5fUff7TfWFHQS8IV1n3og==",
- "cpu": [
- "s390x"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": "^20.19.0 || >=22.12.0"
- }
- },
- "node_modules/@rolldown/binding-linux-x64-gnu": {
- "version": "1.0.0-rc.12",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.12.tgz",
- "integrity": "sha512-RNrafz5bcwRy+O9e6P8Z/OCAJW/A+qtBczIqVYwTs14pf4iV1/+eKEjdOUta93q2TsT/FI0XYDP3TCky38LMAg==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": "^20.19.0 || >=22.12.0"
- }
- },
- "node_modules/@rolldown/binding-linux-x64-musl": {
- "version": "1.0.0-rc.12",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.12.tgz",
- "integrity": "sha512-Jpw/0iwoKWx3LJ2rc1yjFrj+T7iHZn2JDg1Yny1ma0luviFS4mhAIcd1LFNxK3EYu3DHWCps0ydXQ5i/rrJ2ig==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": "^20.19.0 || >=22.12.0"
- }
- },
- "node_modules/@rolldown/binding-openharmony-arm64": {
- "version": "1.0.0-rc.12",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.12.tgz",
- "integrity": "sha512-vRugONE4yMfVn0+7lUKdKvN4D5YusEiPilaoO2sgUWpCvrncvWgPMzK00ZFFJuiPgLwgFNP5eSiUlv2tfc+lpA==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "openharmony"
- ],
- "engines": {
- "node": "^20.19.0 || >=22.12.0"
- }
- },
- "node_modules/@rolldown/binding-wasm32-wasi": {
- "version": "1.0.0-rc.12",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.12.tgz",
- "integrity": "sha512-ykGiLr/6kkiHc0XnBfmFJuCjr5ZYKKofkx+chJWDjitX+KsJuAmrzWhwyOMSHzPhzOHOy7u9HlFoa5MoAOJ/Zg==",
- "cpu": [
- "wasm32"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "dependencies": {
- "@napi-rs/wasm-runtime": "^1.1.1"
- },
- "engines": {
- "node": ">=14.0.0"
- }
- },
- "node_modules/@rolldown/binding-win32-arm64-msvc": {
- "version": "1.0.0-rc.12",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.12.tgz",
- "integrity": "sha512-5eOND4duWkwx1AzCxadcOrNeighiLwMInEADT0YM7xeEOOFcovWZCq8dadXgcRHSf3Ulh1kFo/qvzoFiCLOL1Q==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": "^20.19.0 || >=22.12.0"
- }
- },
- "node_modules/@rolldown/binding-win32-x64-msvc": {
- "version": "1.0.0-rc.12",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.12.tgz",
- "integrity": "sha512-PyqoipaswDLAZtot351MLhrlrh6lcZPo2LSYE+VDxbVk24LVKAGOuE4hb8xZQmrPAuEtTZW8E6D2zc5EUZX4Lw==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": "^20.19.0 || >=22.12.0"
- }
- },
"node_modules/@rolldown/pluginutils": {
"version": "1.0.0-beta.27",
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz",
@@ -3072,6 +3247,13 @@
}
}
},
+ "node_modules/@rollup/pluginutils/node_modules/estree-walker": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
+ "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@rollup/pluginutils/node_modules/picomatch": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
@@ -3086,9 +3268,9 @@
}
},
"node_modules/@rollup/rollup-android-arm-eabi": {
- "version": "4.59.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz",
- "integrity": "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==",
+ "version": "4.60.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.1.tgz",
+ "integrity": "sha512-d6FinEBLdIiK+1uACUttJKfgZREXrF0Qc2SmLII7W2AD8FfiZ9Wjd+rD/iRuf5s5dWrr1GgwXCvPqOuDquOowA==",
"cpu": [
"arm"
],
@@ -3100,9 +3282,9 @@
]
},
"node_modules/@rollup/rollup-android-arm64": {
- "version": "4.59.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.0.tgz",
- "integrity": "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==",
+ "version": "4.60.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.1.tgz",
+ "integrity": "sha512-YjG/EwIDvvYI1YvYbHvDz/BYHtkY4ygUIXHnTdLhG+hKIQFBiosfWiACWortsKPKU/+dUwQQCKQM3qrDe8c9BA==",
"cpu": [
"arm64"
],
@@ -3114,9 +3296,9 @@
]
},
"node_modules/@rollup/rollup-darwin-arm64": {
- "version": "4.59.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.59.0.tgz",
- "integrity": "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==",
+ "version": "4.60.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.1.tgz",
+ "integrity": "sha512-mjCpF7GmkRtSJwon+Rq1N8+pI+8l7w5g9Z3vWj4T7abguC4Czwi3Yu/pFaLvA3TTeMVjnu3ctigusqWUfjZzvw==",
"cpu": [
"arm64"
],
@@ -3128,9 +3310,9 @@
]
},
"node_modules/@rollup/rollup-darwin-x64": {
- "version": "4.59.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.0.tgz",
- "integrity": "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==",
+ "version": "4.60.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.1.tgz",
+ "integrity": "sha512-haZ7hJ1JT4e9hqkoT9R/19XW2QKqjfJVv+i5AGg57S+nLk9lQnJ1F/eZloRO3o9Scy9CM3wQ9l+dkXtcBgN5Ew==",
"cpu": [
"x64"
],
@@ -3142,9 +3324,9 @@
]
},
"node_modules/@rollup/rollup-freebsd-arm64": {
- "version": "4.59.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.0.tgz",
- "integrity": "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==",
+ "version": "4.60.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.1.tgz",
+ "integrity": "sha512-czw90wpQq3ZsAVBlinZjAYTKduOjTywlG7fEeWKUA7oCmpA8xdTkxZZlwNJKWqILlq0wehoZcJYfBvOyhPTQ6w==",
"cpu": [
"arm64"
],
@@ -3156,9 +3338,9 @@
]
},
"node_modules/@rollup/rollup-freebsd-x64": {
- "version": "4.59.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.0.tgz",
- "integrity": "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==",
+ "version": "4.60.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.1.tgz",
+ "integrity": "sha512-KVB2rqsxTHuBtfOeySEyzEOB7ltlB/ux38iu2rBQzkjbwRVlkhAGIEDiiYnO2kFOkJp+Z7pUXKyrRRFuFUKt+g==",
"cpu": [
"x64"
],
@@ -3170,9 +3352,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
- "version": "4.59.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.0.tgz",
- "integrity": "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==",
+ "version": "4.60.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.1.tgz",
+ "integrity": "sha512-L+34Qqil+v5uC0zEubW7uByo78WOCIrBvci69E7sFASRl0X7b/MB6Cqd1lky/CtcSVTydWa2WZwFuWexjS5o6g==",
"cpu": [
"arm"
],
@@ -3184,9 +3366,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
- "version": "4.59.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.0.tgz",
- "integrity": "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==",
+ "version": "4.60.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.1.tgz",
+ "integrity": "sha512-n83O8rt4v34hgFzlkb1ycniJh7IR5RCIqt6mz1VRJD6pmhRi0CXdmfnLu9dIUS6buzh60IvACM842Ffb3xd6Gg==",
"cpu": [
"arm"
],
@@ -3198,9 +3380,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-gnu": {
- "version": "4.59.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz",
- "integrity": "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==",
+ "version": "4.60.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.1.tgz",
+ "integrity": "sha512-Nql7sTeAzhTAja3QXeAI48+/+GjBJ+QmAH13snn0AJSNL50JsDqotyudHyMbO2RbJkskbMbFJfIJKWA6R1LCJQ==",
"cpu": [
"arm64"
],
@@ -3212,9 +3394,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-musl": {
- "version": "4.59.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.0.tgz",
- "integrity": "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==",
+ "version": "4.60.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.1.tgz",
+ "integrity": "sha512-+pUymDhd0ys9GcKZPPWlFiZ67sTWV5UU6zOJat02M1+PiuSGDziyRuI/pPue3hoUwm2uGfxdL+trT6Z9rxnlMA==",
"cpu": [
"arm64"
],
@@ -3226,9 +3408,9 @@
]
},
"node_modules/@rollup/rollup-linux-loong64-gnu": {
- "version": "4.59.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.0.tgz",
- "integrity": "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==",
+ "version": "4.60.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.1.tgz",
+ "integrity": "sha512-VSvgvQeIcsEvY4bKDHEDWcpW4Yw7BtlKG1GUT4FzBUlEKQK0rWHYBqQt6Fm2taXS+1bXvJT6kICu5ZwqKCnvlQ==",
"cpu": [
"loong64"
],
@@ -3240,9 +3422,9 @@
]
},
"node_modules/@rollup/rollup-linux-loong64-musl": {
- "version": "4.59.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.0.tgz",
- "integrity": "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==",
+ "version": "4.60.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.1.tgz",
+ "integrity": "sha512-4LqhUomJqwe641gsPp6xLfhqWMbQV04KtPp7/dIp0nzPxAkNY1AbwL5W0MQpcalLYk07vaW9Kp1PBhdpZYYcEw==",
"cpu": [
"loong64"
],
@@ -3254,9 +3436,9 @@
]
},
"node_modules/@rollup/rollup-linux-ppc64-gnu": {
- "version": "4.59.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.0.tgz",
- "integrity": "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==",
+ "version": "4.60.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.1.tgz",
+ "integrity": "sha512-tLQQ9aPvkBxOc/EUT6j3pyeMD6Hb8QF2BTBnCQWP/uu1lhc9AIrIjKnLYMEroIz/JvtGYgI9dF3AxHZNaEH0rw==",
"cpu": [
"ppc64"
],
@@ -3268,9 +3450,9 @@
]
},
"node_modules/@rollup/rollup-linux-ppc64-musl": {
- "version": "4.59.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.0.tgz",
- "integrity": "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==",
+ "version": "4.60.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.1.tgz",
+ "integrity": "sha512-RMxFhJwc9fSXP6PqmAz4cbv3kAyvD1etJFjTx4ONqFP9DkTkXsAMU4v3Vyc5BgzC+anz7nS/9tp4obsKfqkDHg==",
"cpu": [
"ppc64"
],
@@ -3282,9 +3464,9 @@
]
},
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
- "version": "4.59.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.0.tgz",
- "integrity": "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==",
+ "version": "4.60.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.1.tgz",
+ "integrity": "sha512-QKgFl+Yc1eEk6MmOBfRHYF6lTxiiiV3/z/BRrbSiW2I7AFTXoBFvdMEyglohPj//2mZS4hDOqeB0H1ACh3sBbg==",
"cpu": [
"riscv64"
],
@@ -3296,9 +3478,9 @@
]
},
"node_modules/@rollup/rollup-linux-riscv64-musl": {
- "version": "4.59.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.0.tgz",
- "integrity": "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==",
+ "version": "4.60.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.1.tgz",
+ "integrity": "sha512-RAjXjP/8c6ZtzatZcA1RaQr6O1TRhzC+adn8YZDnChliZHviqIjmvFwHcxi4JKPSDAt6Uhf/7vqcBzQJy0PDJg==",
"cpu": [
"riscv64"
],
@@ -3310,9 +3492,9 @@
]
},
"node_modules/@rollup/rollup-linux-s390x-gnu": {
- "version": "4.59.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.0.tgz",
- "integrity": "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==",
+ "version": "4.60.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.1.tgz",
+ "integrity": "sha512-wcuocpaOlaL1COBYiA89O6yfjlp3RwKDeTIA0hM7OpmhR1Bjo9j31G1uQVpDlTvwxGn2nQs65fBFL5UFd76FcQ==",
"cpu": [
"s390x"
],
@@ -3324,9 +3506,9 @@
]
},
"node_modules/@rollup/rollup-linux-x64-gnu": {
- "version": "4.59.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz",
- "integrity": "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==",
+ "version": "4.60.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.1.tgz",
+ "integrity": "sha512-77PpsFQUCOiZR9+LQEFg9GClyfkNXj1MP6wRnzYs0EeWbPcHs02AXu4xuUbM1zhwn3wqaizle3AEYg5aeoohhg==",
"cpu": [
"x64"
],
@@ -3338,9 +3520,9 @@
]
},
"node_modules/@rollup/rollup-linux-x64-musl": {
- "version": "4.59.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.0.tgz",
- "integrity": "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==",
+ "version": "4.60.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.1.tgz",
+ "integrity": "sha512-5cIATbk5vynAjqqmyBjlciMJl1+R/CwX9oLk/EyiFXDWd95KpHdrOJT//rnUl4cUcskrd0jCCw3wpZnhIHdD9w==",
"cpu": [
"x64"
],
@@ -3352,9 +3534,9 @@
]
},
"node_modules/@rollup/rollup-openbsd-x64": {
- "version": "4.59.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.0.tgz",
- "integrity": "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==",
+ "version": "4.60.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.1.tgz",
+ "integrity": "sha512-cl0w09WsCi17mcmWqqglez9Gk8isgeWvoUZ3WiJFYSR3zjBQc2J5/ihSjpl+VLjPqjQ/1hJRcqBfLjssREQILw==",
"cpu": [
"x64"
],
@@ -3366,9 +3548,9 @@
]
},
"node_modules/@rollup/rollup-openharmony-arm64": {
- "version": "4.59.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.0.tgz",
- "integrity": "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==",
+ "version": "4.60.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.1.tgz",
+ "integrity": "sha512-4Cv23ZrONRbNtbZa37mLSueXUCtN7MXccChtKpUnQNgF010rjrjfHx3QxkS2PI7LqGT5xXyYs1a7LbzAwT0iCA==",
"cpu": [
"arm64"
],
@@ -3380,9 +3562,9 @@
]
},
"node_modules/@rollup/rollup-win32-arm64-msvc": {
- "version": "4.59.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.0.tgz",
- "integrity": "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==",
+ "version": "4.60.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.1.tgz",
+ "integrity": "sha512-i1okWYkA4FJICtr7KpYzFpRTHgy5jdDbZiWfvny21iIKky5YExiDXP+zbXzm3dUcFpkEeYNHgQ5fuG236JPq0g==",
"cpu": [
"arm64"
],
@@ -3394,9 +3576,9 @@
]
},
"node_modules/@rollup/rollup-win32-ia32-msvc": {
- "version": "4.59.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.0.tgz",
- "integrity": "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==",
+ "version": "4.60.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.1.tgz",
+ "integrity": "sha512-u09m3CuwLzShA0EYKMNiFgcjjzwqtUMLmuCJLeZWjjOYA3IT2Di09KaxGBTP9xVztWyIWjVdsB2E9goMjZvTQg==",
"cpu": [
"ia32"
],
@@ -3408,9 +3590,9 @@
]
},
"node_modules/@rollup/rollup-win32-x64-gnu": {
- "version": "4.59.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.0.tgz",
- "integrity": "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==",
+ "version": "4.60.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.1.tgz",
+ "integrity": "sha512-k+600V9Zl1CM7eZxJgMyTUzmrmhB/0XZnF4pRypKAlAgxmedUA+1v9R+XOFv56W4SlHEzfeMtzujLJD22Uz5zg==",
"cpu": [
"x64"
],
@@ -3422,9 +3604,9 @@
]
},
"node_modules/@rollup/rollup-win32-x64-msvc": {
- "version": "4.59.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz",
- "integrity": "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==",
+ "version": "4.60.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.1.tgz",
+ "integrity": "sha512-lWMnixq/QzxyhTV6NjQJ4SFo1J6PvOX8vUx5Wb4bBPsEb+8xZ89Bz6kOXpfXj9ak9AHTQVQzlgzBEc1SyM27xQ==",
"cpu": [
"x64"
],
@@ -3435,13 +3617,6 @@
"win32"
]
},
- "node_modules/@standard-schema/spec": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz",
- "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/@surma/rollup-plugin-off-main-thread": {
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/@surma/rollup-plugin-off-main-thread/-/rollup-plugin-off-main-thread-2.2.3.tgz",
@@ -3455,10 +3630,20 @@
"string.prototype.matchall": "^4.0.6"
}
},
+ "node_modules/@surma/rollup-plugin-off-main-thread/node_modules/magic-string": {
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz",
+ "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "sourcemap-codec": "^1.4.8"
+ }
+ },
"node_modules/@swc/helpers": {
- "version": "0.5.19",
- "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.19.tgz",
- "integrity": "sha512-QamiFeIK3txNjgUTNppE6MiG3p7TdninpZu0E0PbqVh1a9FNLT2FRhisaa4NcaX52XVhA5l7Pk58Ft7Sqi/2sA==",
+ "version": "0.5.21",
+ "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.21.tgz",
+ "integrity": "sha512-jI/VAmtdjB/RnI8GTnokyX7Ug8c+g+ffD6QRLa6XQewtnGyukKkKSk3wLTM3b5cjt1jNh9x0jfVlagdN2gDKQg==",
"license": "Apache-2.0",
"dependencies": {
"tslib": "^2.8.0"
@@ -3554,17 +3739,6 @@
"@testing-library/dom": ">=7.21.4"
}
},
- "node_modules/@tybys/wasm-util": {
- "version": "0.10.1",
- "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz",
- "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==",
- "dev": true,
- "license": "MIT",
- "optional": true,
- "dependencies": {
- "tslib": "^2.4.0"
- }
- },
"node_modules/@types/aria-query": {
"version": "5.0.4",
"resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz",
@@ -3792,29 +3966,32 @@
}
},
"node_modules/@vitest/coverage-v8": {
- "version": "4.1.2",
- "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.1.2.tgz",
- "integrity": "sha512-sPK//PHO+kAkScb8XITeB1bf7fsk85Km7+rt4eeuRR3VS1/crD47cmV5wicisJmjNdfeokTZwjMk4Mj2d58Mgg==",
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-3.2.4.tgz",
+ "integrity": "sha512-EyF9SXU6kS5Ku/U82E259WSnvg6c8KTjppUncuNdm5QHpe17mwREHnjDzozC8x9MZ0xfBUFSaLkRv4TMA75ALQ==",
"dev": true,
"license": "MIT",
"dependencies": {
+ "@ampproject/remapping": "^2.3.0",
"@bcoe/v8-coverage": "^1.0.2",
- "@vitest/utils": "4.1.2",
- "ast-v8-to-istanbul": "^1.0.0",
+ "ast-v8-to-istanbul": "^0.3.3",
+ "debug": "^4.4.1",
"istanbul-lib-coverage": "^3.2.2",
"istanbul-lib-report": "^3.0.1",
- "istanbul-reports": "^3.2.0",
- "magicast": "^0.5.2",
- "obug": "^2.1.1",
- "std-env": "^4.0.0-rc.1",
- "tinyrainbow": "^3.1.0"
+ "istanbul-lib-source-maps": "^5.0.6",
+ "istanbul-reports": "^3.1.7",
+ "magic-string": "^0.30.17",
+ "magicast": "^0.3.5",
+ "std-env": "^3.9.0",
+ "test-exclude": "^7.0.1",
+ "tinyrainbow": "^2.0.0"
},
"funding": {
"url": "https://opencollective.com/vitest"
},
"peerDependencies": {
- "@vitest/browser": "4.1.2",
- "vitest": "4.1.2"
+ "@vitest/browser": "3.2.4",
+ "vitest": "3.2.4"
},
"peerDependenciesMeta": {
"@vitest/browser": {
@@ -3823,96 +4000,115 @@
}
},
"node_modules/@vitest/expect": {
- "version": "4.1.2",
- "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.2.tgz",
- "integrity": "sha512-gbu+7B0YgUJ2nkdsRJrFFW6X7NTP44WlhiclHniUhxADQJH5Szt9mZ9hWnJPJ8YwOK5zUOSSlSvyzRf0u1DSBQ==",
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.4.tgz",
+ "integrity": "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@standard-schema/spec": "^1.1.0",
"@types/chai": "^5.2.2",
- "@vitest/spy": "4.1.2",
- "@vitest/utils": "4.1.2",
- "chai": "^6.2.2",
- "tinyrainbow": "^3.1.0"
+ "@vitest/spy": "3.2.4",
+ "@vitest/utils": "3.2.4",
+ "chai": "^5.2.0",
+ "tinyrainbow": "^2.0.0"
},
"funding": {
"url": "https://opencollective.com/vitest"
}
},
- "node_modules/@vitest/pretty-format": {
- "version": "4.1.2",
- "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.2.tgz",
- "integrity": "sha512-dwQga8aejqeuB+TvXCMzSQemvV9hNEtDDpgUKDzOmNQayl2OG241PSWeJwKRH3CiC+sESrmoFd49rfnq7T4RnA==",
+ "node_modules/@vitest/mocker": {
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.4.tgz",
+ "integrity": "sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "tinyrainbow": "^3.1.0"
+ "@vitest/spy": "3.2.4",
+ "estree-walker": "^3.0.3",
+ "magic-string": "^0.30.17"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ },
+ "peerDependencies": {
+ "msw": "^2.4.9",
+ "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0"
+ },
+ "peerDependenciesMeta": {
+ "msw": {
+ "optional": true
+ },
+ "vite": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@vitest/pretty-format": {
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.4.tgz",
+ "integrity": "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "tinyrainbow": "^2.0.0"
},
"funding": {
"url": "https://opencollective.com/vitest"
}
},
"node_modules/@vitest/runner": {
- "version": "4.1.2",
- "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.2.tgz",
- "integrity": "sha512-Gr+FQan34CdiYAwpGJmQG8PgkyFVmARK8/xSijia3eTFgVfpcpztWLuP6FttGNfPLJhaZVP/euvujeNYar36OQ==",
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.4.tgz",
+ "integrity": "sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@vitest/utils": "4.1.2",
- "pathe": "^2.0.3"
+ "@vitest/utils": "3.2.4",
+ "pathe": "^2.0.3",
+ "strip-literal": "^3.0.0"
},
"funding": {
"url": "https://opencollective.com/vitest"
}
},
"node_modules/@vitest/snapshot": {
- "version": "4.1.2",
- "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.2.tgz",
- "integrity": "sha512-g7yfUmxYS4mNxk31qbOYsSt2F4m1E02LFqO53Xpzg3zKMhLAPZAjjfyl9e6z7HrW6LvUdTwAQR3HHfLjpko16A==",
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.4.tgz",
+ "integrity": "sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@vitest/pretty-format": "4.1.2",
- "@vitest/utils": "4.1.2",
- "magic-string": "^0.30.21",
+ "@vitest/pretty-format": "3.2.4",
+ "magic-string": "^0.30.17",
"pathe": "^2.0.3"
},
"funding": {
"url": "https://opencollective.com/vitest"
}
},
- "node_modules/@vitest/snapshot/node_modules/magic-string": {
- "version": "0.30.21",
- "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
- "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
+ "node_modules/@vitest/spy": {
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.4.tgz",
+ "integrity": "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@jridgewell/sourcemap-codec": "^1.5.5"
- }
- },
- "node_modules/@vitest/spy": {
- "version": "4.1.2",
- "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.2.tgz",
- "integrity": "sha512-DU4fBnbVCJGNBwVA6xSToNXrkZNSiw59H8tcuUspVMsBDBST4nfvsPsEHDHGtWRRnqBERBQu7TrTKskmjqTXKA==",
- "dev": true,
- "license": "MIT",
+ "tinyspy": "^4.0.3"
+ },
"funding": {
"url": "https://opencollective.com/vitest"
}
},
"node_modules/@vitest/utils": {
- "version": "4.1.2",
- "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.2.tgz",
- "integrity": "sha512-xw2/TiX82lQHA06cgbqRKFb5lCAy3axQ4H4SoUFhUsg+wztiet+co86IAMDtF6Vm1hc7J6j09oh/rgDn+JdKIQ==",
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.4.tgz",
+ "integrity": "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@vitest/pretty-format": "4.1.2",
- "convert-source-map": "^2.0.0",
- "tinyrainbow": "^3.1.0"
+ "@vitest/pretty-format": "3.2.4",
+ "loupe": "^3.1.4",
+ "tinyrainbow": "^2.0.0"
},
"funding": {
"url": "https://opencollective.com/vitest"
@@ -4066,9 +4262,9 @@
}
},
"node_modules/ast-v8-to-istanbul": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-1.0.0.tgz",
- "integrity": "sha512-1fSfIwuDICFA4LKkCzRPO7F0hzFf0B7+Xqrl27ynQaa+Rh0e1Es0v6kWHPott3lU10AyAr7oKHa65OppjLn3Rg==",
+ "version": "0.3.12",
+ "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-0.3.12.tgz",
+ "integrity": "sha512-BRRC8VRZY2R4Z4lFIL35MwNXmwVqBityvOIwETtsCSwvjl0IdgFsy9NhdaA6j74nUdtJJlIypeRhpDam19Wq3g==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -4077,16 +4273,6 @@
"js-tokens": "^10.0.0"
}
},
- "node_modules/ast-v8-to-istanbul/node_modules/estree-walker": {
- "version": "3.0.3",
- "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz",
- "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@types/estree": "^1.0.0"
- }
- },
"node_modules/ast-v8-to-istanbul/node_modules/js-tokens": {
"version": "10.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-10.0.0.tgz",
@@ -4190,14 +4376,14 @@
}
},
"node_modules/axios": {
- "version": "1.13.6",
- "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.6.tgz",
- "integrity": "sha512-ChTCHMouEe2kn713WHbQGcuYrr6fXTBiu460OTwWrWob16g1bXn4vtz07Ope7ewMozJAnEquLk5lWQWtBig9DQ==",
+ "version": "1.15.0",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-1.15.0.tgz",
+ "integrity": "sha512-wWyJDlAatxk30ZJer+GeCWS209sA42X+N5jU2jy6oHTp7ufw8uzUTVFBX9+wTfAlhiJXGS0Bq7X6efruWjuK9Q==",
"license": "MIT",
"dependencies": {
"follow-redirects": "^1.15.11",
"form-data": "^4.0.5",
- "proxy-from-env": "^1.1.0"
+ "proxy-from-env": "^2.1.0"
}
},
"node_modules/babel-plugin-polyfill-corejs2": {
@@ -4283,9 +4469,9 @@
"license": "MIT"
},
"node_modules/baseline-browser-mapping": {
- "version": "2.10.8",
- "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.8.tgz",
- "integrity": "sha512-PCLz/LXGBsNTErbtB6i5u4eLpHeMfi93aUv5duMmj6caNu6IphS4q6UevDnL36sZQv9lrP11dbPKGMaXPwMKfQ==",
+ "version": "2.10.17",
+ "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.17.tgz",
+ "integrity": "sha512-HdrkN8eVG2CXxeifv/VdJ4A4RSra1DTW8dc/hdxzhGHN8QePs6gKaWM9pHPcpCoxYZJuOZ8drHmbdpLHjCYjLA==",
"dev": true,
"license": "Apache-2.0",
"bin": {
@@ -4362,9 +4548,9 @@
}
},
"node_modules/browserslist": {
- "version": "4.28.1",
- "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz",
- "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==",
+ "version": "4.28.2",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz",
+ "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==",
"dev": true,
"funding": [
{
@@ -4382,11 +4568,11 @@
],
"license": "MIT",
"dependencies": {
- "baseline-browser-mapping": "^2.9.0",
- "caniuse-lite": "^1.0.30001759",
- "electron-to-chromium": "^1.5.263",
- "node-releases": "^2.0.27",
- "update-browserslist-db": "^1.2.0"
+ "baseline-browser-mapping": "^2.10.12",
+ "caniuse-lite": "^1.0.30001782",
+ "electron-to-chromium": "^1.5.328",
+ "node-releases": "^2.0.36",
+ "update-browserslist-db": "^1.2.3"
},
"bin": {
"browserslist": "cli.js"
@@ -4402,16 +4588,26 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/cac": {
+ "version": "6.7.14",
+ "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz",
+ "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
"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==",
+ "version": "1.0.9",
+ "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.9.tgz",
+ "integrity": "sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "call-bind-apply-helpers": "^1.0.0",
- "es-define-property": "^1.0.0",
- "get-intrinsic": "^1.2.4",
+ "call-bind-apply-helpers": "^1.0.2",
+ "es-define-property": "^1.0.1",
+ "get-intrinsic": "^1.3.0",
"set-function-length": "^1.2.2"
},
"engines": {
@@ -4462,9 +4658,9 @@
}
},
"node_modules/caniuse-lite": {
- "version": "1.0.30001780",
- "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001780.tgz",
- "integrity": "sha512-llngX0E7nQci5BPJDqoZSbuZ5Bcs9F5db7EtgfwBerX9XGtkkiO4NwfDDIRzHTTwcYC8vC7bmeUEPGrKlR/TkQ==",
+ "version": "1.0.30001787",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001787.tgz",
+ "integrity": "sha512-mNcrMN9KeI68u7muanUpEejSLghOKlVhRqS/Za2IeyGllJ9I9otGpR9g3nsw7n4W378TE/LyIteA0+/FOZm4Kg==",
"dev": true,
"funding": [
{
@@ -4493,11 +4689,18 @@
}
},
"node_modules/chai": {
- "version": "6.2.2",
- "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz",
- "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==",
+ "version": "5.3.3",
+ "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz",
+ "integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==",
"dev": true,
"license": "MIT",
+ "dependencies": {
+ "assertion-error": "^2.0.1",
+ "check-error": "^2.1.1",
+ "deep-eql": "^5.0.1",
+ "loupe": "^3.1.0",
+ "pathval": "^2.0.0"
+ },
"engines": {
"node": ">=18"
}
@@ -4542,6 +4745,16 @@
"url": "https://github.com/sponsors/wooorm"
}
},
+ "node_modules/check-error": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.3.tgz",
+ "integrity": "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 16"
+ }
+ },
"node_modules/chokidar": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
@@ -4621,6 +4834,41 @@
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
+ "node_modules/cliui/node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/cliui/node_modules/string-width": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/cliui/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,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/cliui/node_modules/wrap-ansi": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
@@ -4783,12 +5031,6 @@
"node": ">= 8"
}
},
- "node_modules/crypto-js": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz",
- "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==",
- "license": "MIT"
- },
"node_modules/crypto-random-string": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz",
@@ -4853,44 +5095,6 @@
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
}
},
- "node_modules/data-urls/node_modules/tr46": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/tr46/-/tr46-6.0.0.tgz",
- "integrity": "sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "punycode": "^2.3.1"
- },
- "engines": {
- "node": ">=20"
- }
- },
- "node_modules/data-urls/node_modules/webidl-conversions": {
- "version": "8.0.1",
- "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.1.tgz",
- "integrity": "sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==",
- "dev": true,
- "license": "BSD-2-Clause",
- "engines": {
- "node": ">=20"
- }
- },
- "node_modules/data-urls/node_modules/whatwg-url": {
- "version": "16.0.1",
- "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-16.0.1.tgz",
- "integrity": "sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@exodus/bytes": "^1.11.0",
- "tr46": "^6.0.0",
- "webidl-conversions": "^8.0.1"
- },
- "engines": {
- "node": "^20.19.0 || ^22.12.0 || >=24.0.0"
- }
- },
"node_modules/data-view-buffer": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz",
@@ -4982,6 +5186,16 @@
"url": "https://github.com/sponsors/wooorm"
}
},
+ "node_modules/deep-eql": {
+ "version": "5.0.2",
+ "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz",
+ "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/deepmerge": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
@@ -5111,6 +5325,13 @@
"node": ">= 0.4"
}
},
+ "node_modules/eastasianwidth": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
+ "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/ejs": {
"version": "3.1.10",
"resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz",
@@ -5128,16 +5349,16 @@
}
},
"node_modules/electron-to-chromium": {
- "version": "1.5.313",
- "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.313.tgz",
- "integrity": "sha512-QBMrTWEf00GXZmJyx2lbYD45jpI3TUFnNIzJ5BBc8piGUDwMPa1GV6HJWTZVvY/eiN3fSopl7NRbgGp9sZ9LTA==",
+ "version": "1.5.334",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.334.tgz",
+ "integrity": "sha512-mgjZAz7Jyx1SRCwEpy9wefDS7GvNPazLthHg8eQMJ76wBdGQQDW33TCrUTvQ4wzpmOrv2zrFoD3oNufMdyMpog==",
"dev": true,
"license": "ISC"
},
"node_modules/emoji-regex": {
- "version": "8.0.0",
- "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
- "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "version": "9.2.2",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
+ "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
"dev": true,
"license": "MIT"
},
@@ -5161,9 +5382,9 @@
}
},
"node_modules/es-abstract": {
- "version": "1.24.1",
- "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.1.tgz",
- "integrity": "sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw==",
+ "version": "1.24.2",
+ "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.2.tgz",
+ "integrity": "sha512-2FpH9Q5i2RRwyEP1AylXe6nYLR5OhaJTZwmlcP0dL/+JCbgg7yyEo/sEK6HeGZRf3dFpWwThaRHVApXSkW3xeg==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -5248,9 +5469,9 @@
}
},
"node_modules/es-module-lexer": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.0.0.tgz",
- "integrity": "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==",
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz",
+ "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==",
"dev": true,
"license": "MIT"
},
@@ -5299,6 +5520,45 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/esbuild": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz",
+ "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.21.5",
+ "@esbuild/android-arm": "0.21.5",
+ "@esbuild/android-arm64": "0.21.5",
+ "@esbuild/android-x64": "0.21.5",
+ "@esbuild/darwin-arm64": "0.21.5",
+ "@esbuild/darwin-x64": "0.21.5",
+ "@esbuild/freebsd-arm64": "0.21.5",
+ "@esbuild/freebsd-x64": "0.21.5",
+ "@esbuild/linux-arm": "0.21.5",
+ "@esbuild/linux-arm64": "0.21.5",
+ "@esbuild/linux-ia32": "0.21.5",
+ "@esbuild/linux-loong64": "0.21.5",
+ "@esbuild/linux-mips64el": "0.21.5",
+ "@esbuild/linux-ppc64": "0.21.5",
+ "@esbuild/linux-riscv64": "0.21.5",
+ "@esbuild/linux-s390x": "0.21.5",
+ "@esbuild/linux-x64": "0.21.5",
+ "@esbuild/netbsd-x64": "0.21.5",
+ "@esbuild/openbsd-x64": "0.21.5",
+ "@esbuild/sunos-x64": "0.21.5",
+ "@esbuild/win32-arm64": "0.21.5",
+ "@esbuild/win32-ia32": "0.21.5",
+ "@esbuild/win32-x64": "0.21.5"
+ }
+ },
"node_modules/escalade": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
@@ -5332,11 +5592,14 @@
}
},
"node_modules/estree-walker": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
- "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz",
+ "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==",
"dev": true,
- "license": "MIT"
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "^1.0.0"
+ }
},
"node_modules/esutils": {
"version": "2.0.3",
@@ -5772,26 +6035,23 @@
}
},
"node_modules/glob": {
- "version": "11.1.0",
- "resolved": "https://registry.npmjs.org/glob/-/glob-11.1.0.tgz",
- "integrity": "sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw==",
+ "version": "10.5.0",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz",
+ "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==",
"deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me",
"dev": true,
- "license": "BlueOak-1.0.0",
+ "license": "ISC",
"dependencies": {
- "foreground-child": "^3.3.1",
- "jackspeak": "^4.1.1",
- "minimatch": "^10.1.1",
+ "foreground-child": "^3.1.0",
+ "jackspeak": "^3.1.2",
+ "minimatch": "^9.0.4",
"minipass": "^7.1.2",
"package-json-from-dist": "^1.0.0",
- "path-scurry": "^2.0.0"
+ "path-scurry": "^1.11.1"
},
"bin": {
"glob": "dist/esm/bin.mjs"
},
- "engines": {
- "node": "20 || >=22"
- },
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
@@ -5809,6 +6069,39 @@
"node": ">=10.13.0"
}
},
+ "node_modules/glob/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==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/glob/node_modules/brace-expansion": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.3.tgz",
+ "integrity": "sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0"
+ }
+ },
+ "node_modules/glob/node_modules/minimatch": {
+ "version": "9.0.9",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz",
+ "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^2.0.2"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
"node_modules/globalthis": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz",
@@ -6664,6 +6957,21 @@
"node": ">=10"
}
},
+ "node_modules/istanbul-lib-source-maps": {
+ "version": "5.0.6",
+ "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz",
+ "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@jridgewell/trace-mapping": "^0.3.23",
+ "debug": "^4.1.1",
+ "istanbul-lib-coverage": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
"node_modules/istanbul-reports": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz",
@@ -6679,19 +6987,19 @@
}
},
"node_modules/jackspeak": {
- "version": "4.2.3",
- "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.2.3.tgz",
- "integrity": "sha512-ykkVRwrYvFm1nb2AJfKKYPr0emF6IiXDYUaFx4Zn9ZuIH7MrzEZ3sD5RlqGXNRpHtvUHJyOnCEFxOlNDtGo7wg==",
+ "version": "3.4.3",
+ "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz",
+ "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==",
"dev": true,
"license": "BlueOak-1.0.0",
"dependencies": {
- "@isaacs/cliui": "^9.0.0"
- },
- "engines": {
- "node": "20 || >=22"
+ "@isaacs/cliui": "^8.0.2"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
+ },
+ "optionalDependencies": {
+ "@pkgjs/parseargs": "^0.11.0"
}
},
"node_modules/jake": {
@@ -6731,6 +7039,12 @@
"jiti": "bin/jiti.js"
}
},
+ "node_modules/js-md5": {
+ "version": "0.8.3",
+ "resolved": "https://registry.npmjs.org/js-md5/-/js-md5-0.8.3.tgz",
+ "integrity": "sha512-qR0HB5uP6wCuRMrWPTrkMaev7MJZwJuuw4fnwAzRgP4J4/F8RwtodOKpGp4XpqsLBFzzgqIO42efFAyz2Et6KQ==",
+ "license": "MIT"
+ },
"node_modules/js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
@@ -6738,14 +7052,14 @@
"license": "MIT"
},
"node_modules/jsdom": {
- "version": "29.0.1",
- "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-29.0.1.tgz",
- "integrity": "sha512-z6JOK5gRO7aMybVq/y/MlIpKh8JIi68FBKMUtKkK2KH/wMSRlCxQ682d08LB9fYXplyY/UXG8P4XXTScmdjApg==",
+ "version": "29.0.2",
+ "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-29.0.2.tgz",
+ "integrity": "sha512-9VnGEBosc/ZpwyOsJBCQ/3I5p7Q5ngOY14a9bf5btenAORmZfDse1ZEheMiWcJ3h81+Fv7HmJFdS0szo/waF2w==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@asamuzakjp/css-color": "^5.0.1",
- "@asamuzakjp/dom-selector": "^7.0.3",
+ "@asamuzakjp/css-color": "^5.1.5",
+ "@asamuzakjp/dom-selector": "^7.0.6",
"@bramus/specificity": "^2.4.2",
"@csstools/css-syntax-patches-for-csstree": "^1.1.1",
"@exodus/bytes": "^1.15.0",
@@ -6779,53 +7093,15 @@
}
},
"node_modules/jsdom/node_modules/lru-cache": {
- "version": "11.3.1",
- "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.3.1.tgz",
- "integrity": "sha512-Y71HWT4hydF1IAG/2OPync4dgQ/J2iWye7eg6CuzJHI+E97tvqFPlADzxiNnjH6WSljg8ecfXMr9k6bfFuqA5w==",
+ "version": "11.3.3",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.3.3.tgz",
+ "integrity": "sha512-JvNw9Y81y33E+BEYPr0U7omo+U9AySnsMsEiXgwT6yqd31VQWTLNQqmT4ou5eqPFUrTfIDFta2wKhB1hyohtAQ==",
"dev": true,
"license": "BlueOak-1.0.0",
"engines": {
"node": "20 || >=22"
}
},
- "node_modules/jsdom/node_modules/tr46": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/tr46/-/tr46-6.0.0.tgz",
- "integrity": "sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "punycode": "^2.3.1"
- },
- "engines": {
- "node": ">=20"
- }
- },
- "node_modules/jsdom/node_modules/webidl-conversions": {
- "version": "8.0.1",
- "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.1.tgz",
- "integrity": "sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==",
- "dev": true,
- "license": "BSD-2-Clause",
- "engines": {
- "node": ">=20"
- }
- },
- "node_modules/jsdom/node_modules/whatwg-url": {
- "version": "16.0.1",
- "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-16.0.1.tgz",
- "integrity": "sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@exodus/bytes": "^1.11.0",
- "tr46": "^6.0.0",
- "webidl-conversions": "^8.0.1"
- },
- "engines": {
- "node": "^20.19.0 || ^22.12.0 || >=24.0.0"
- }
- },
"node_modules/jsesc": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
@@ -6839,13 +7115,6 @@
"node": ">=6"
}
},
- "node_modules/json-schema": {
- "version": "0.4.0",
- "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz",
- "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==",
- "dev": true,
- "license": "(AFL-2.1 OR BSD-3-Clause)"
- },
"node_modules/json-schema-traverse": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
@@ -6914,267 +7183,6 @@
"node": ">=6"
}
},
- "node_modules/lightningcss": {
- "version": "1.32.0",
- "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz",
- "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==",
- "dev": true,
- "license": "MPL-2.0",
- "dependencies": {
- "detect-libc": "^2.0.3"
- },
- "engines": {
- "node": ">= 12.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
- },
- "optionalDependencies": {
- "lightningcss-android-arm64": "1.32.0",
- "lightningcss-darwin-arm64": "1.32.0",
- "lightningcss-darwin-x64": "1.32.0",
- "lightningcss-freebsd-x64": "1.32.0",
- "lightningcss-linux-arm-gnueabihf": "1.32.0",
- "lightningcss-linux-arm64-gnu": "1.32.0",
- "lightningcss-linux-arm64-musl": "1.32.0",
- "lightningcss-linux-x64-gnu": "1.32.0",
- "lightningcss-linux-x64-musl": "1.32.0",
- "lightningcss-win32-arm64-msvc": "1.32.0",
- "lightningcss-win32-x64-msvc": "1.32.0"
- }
- },
- "node_modules/lightningcss-android-arm64": {
- "version": "1.32.0",
- "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz",
- "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MPL-2.0",
- "optional": true,
- "os": [
- "android"
- ],
- "engines": {
- "node": ">= 12.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
- }
- },
- "node_modules/lightningcss-darwin-arm64": {
- "version": "1.32.0",
- "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz",
- "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MPL-2.0",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">= 12.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
- }
- },
- "node_modules/lightningcss-darwin-x64": {
- "version": "1.32.0",
- "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz",
- "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MPL-2.0",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">= 12.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
- }
- },
- "node_modules/lightningcss-freebsd-x64": {
- "version": "1.32.0",
- "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz",
- "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MPL-2.0",
- "optional": true,
- "os": [
- "freebsd"
- ],
- "engines": {
- "node": ">= 12.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
- }
- },
- "node_modules/lightningcss-linux-arm-gnueabihf": {
- "version": "1.32.0",
- "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz",
- "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==",
- "cpu": [
- "arm"
- ],
- "dev": true,
- "license": "MPL-2.0",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 12.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
- }
- },
- "node_modules/lightningcss-linux-arm64-gnu": {
- "version": "1.32.0",
- "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz",
- "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MPL-2.0",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 12.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
- }
- },
- "node_modules/lightningcss-linux-arm64-musl": {
- "version": "1.32.0",
- "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz",
- "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MPL-2.0",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 12.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
- }
- },
- "node_modules/lightningcss-linux-x64-gnu": {
- "version": "1.32.0",
- "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz",
- "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MPL-2.0",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 12.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
- }
- },
- "node_modules/lightningcss-linux-x64-musl": {
- "version": "1.32.0",
- "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz",
- "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MPL-2.0",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 12.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
- }
- },
- "node_modules/lightningcss-win32-arm64-msvc": {
- "version": "1.32.0",
- "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz",
- "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MPL-2.0",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">= 12.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
- }
- },
- "node_modules/lightningcss-win32-x64-msvc": {
- "version": "1.32.0",
- "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz",
- "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MPL-2.0",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">= 12.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
- }
- },
"node_modules/lilconfig": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz",
@@ -7257,6 +7265,13 @@
"loose-envify": "cli.js"
}
},
+ "node_modules/loupe": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz",
+ "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/lru-cache": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
@@ -7288,25 +7303,25 @@
}
},
"node_modules/magic-string": {
- "version": "0.25.9",
- "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz",
- "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==",
+ "version": "0.30.21",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
+ "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "sourcemap-codec": "^1.4.8"
+ "@jridgewell/sourcemap-codec": "^1.5.5"
}
},
"node_modules/magicast": {
- "version": "0.5.2",
- "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.2.tgz",
- "integrity": "sha512-E3ZJh4J3S9KfwdjZhe2afj6R9lGIN5Pher1pF39UGrXRqq/VDaGVIGN13BjHd2u8B61hArAGOnso7nBOouW3TQ==",
+ "version": "0.3.5",
+ "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.3.5.tgz",
+ "integrity": "sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/parser": "^7.29.0",
- "@babel/types": "^7.29.0",
- "source-map-js": "^1.2.1"
+ "@babel/parser": "^7.25.4",
+ "@babel/types": "^7.25.4",
+ "source-map-js": "^1.2.0"
}
},
"node_modules/make-dir": {
@@ -8271,13 +8286,13 @@
}
},
"node_modules/minimatch": {
- "version": "10.2.4",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz",
- "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==",
+ "version": "10.2.5",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz",
+ "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==",
"dev": true,
"license": "BlueOak-1.0.0",
"dependencies": {
- "brace-expansion": "^5.0.2"
+ "brace-expansion": "^5.0.5"
},
"engines": {
"node": "18 || 20 || >=22"
@@ -8303,9 +8318,9 @@
"license": "MIT"
},
"node_modules/msw": {
- "version": "2.13.0",
- "resolved": "https://registry.npmjs.org/msw/-/msw-2.13.0.tgz",
- "integrity": "sha512-5PPWf7I7DBHb4ZUZ0NUI+/VBDk/eiNYDNJZGt/jZ7+rbCSIK5hRcNTGqWMnn0vT6NrHiQlb0nfpenVGz1vrqpg==",
+ "version": "2.13.2",
+ "resolved": "https://registry.npmjs.org/msw/-/msw-2.13.2.tgz",
+ "integrity": "sha512-go2H1TIERKkC48pXiwec5l6sbNqYuvqOk3/vHGo1Zd+pq/H63oFawDQerH+WQdUw/flJFHDG7F+QdWMwhntA/A==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
@@ -8347,22 +8362,6 @@
}
}
},
- "node_modules/msw/node_modules/type-fest": {
- "version": "5.5.0",
- "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-5.5.0.tgz",
- "integrity": "sha512-PlBfpQwiUvGViBNX84Yxwjsdhd1TUlXr6zjX7eoirtCPIr08NAmxwa+fcYBTeRQxHo9YC9wwF3m9i700sHma8g==",
- "dev": true,
- "license": "(MIT OR CC0-1.0)",
- "dependencies": {
- "tagged-tag": "^1.0.0"
- },
- "engines": {
- "node": ">=20"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
"node_modules/mute-stream": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-2.0.0.tgz",
@@ -8405,9 +8404,9 @@
}
},
"node_modules/node-releases": {
- "version": "2.0.36",
- "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.36.tgz",
- "integrity": "sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA==",
+ "version": "2.0.37",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.37.tgz",
+ "integrity": "sha512-1h5gKZCF+pO/o3Iqt5Jp7wc9rH3eJJ0+nh/CIoiRwjRxde/hAHyLPXYN4V3CqKAbiZPSeJFSWHmJsbkicta0Eg==",
"dev": true,
"license": "MIT"
},
@@ -8493,17 +8492,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/obug": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz",
- "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==",
- "dev": true,
- "funding": [
- "https://github.com/sponsors/sxzz",
- "https://opencollective.com/debug"
- ],
- "license": "MIT"
- },
"node_modules/outvariant": {
"version": "1.4.3",
"resolved": "https://registry.npmjs.org/outvariant/-/outvariant-1.4.3.tgz",
@@ -8604,31 +8592,28 @@
"license": "MIT"
},
"node_modules/path-scurry": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz",
- "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==",
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz",
+ "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==",
"dev": true,
"license": "BlueOak-1.0.0",
"dependencies": {
- "lru-cache": "^11.0.0",
- "minipass": "^7.1.2"
+ "lru-cache": "^10.2.0",
+ "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
},
"engines": {
- "node": "18 || 20 || >=22"
+ "node": ">=16 || 14 >=14.18"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/path-scurry/node_modules/lru-cache": {
- "version": "11.2.7",
- "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.7.tgz",
- "integrity": "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==",
+ "version": "10.4.3",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
+ "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
"dev": true,
- "license": "BlueOak-1.0.0",
- "engines": {
- "node": "20 || >=22"
- }
+ "license": "ISC"
},
"node_modules/path-to-regexp": {
"version": "6.3.0",
@@ -8644,6 +8629,16 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/pathval": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz",
+ "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 14.16"
+ }
+ },
"node_modules/picocolors": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
@@ -8695,9 +8690,9 @@
}
},
"node_modules/postcss": {
- "version": "8.5.8",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz",
- "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==",
+ "version": "8.5.9",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.9.tgz",
+ "integrity": "sha512-7a70Nsot+EMX9fFU3064K/kdHWZqGVY+BADLyXc8Dfv+mTLLVl6JzJpPaCZ2kQL9gIJvKXSLMHhqdRRjwQeFtw==",
"dev": true,
"funding": [
{
@@ -8885,14 +8880,6 @@
"node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0"
}
},
- "node_modules/pretty-format/node_modules/react-is": {
- "version": "17.0.2",
- "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
- "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==",
- "dev": true,
- "license": "MIT",
- "peer": true
- },
"node_modules/prop-types": {
"version": "15.8.1",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
@@ -8904,6 +8891,12 @@
"react-is": "^16.13.1"
}
},
+ "node_modules/prop-types/node_modules/react-is": {
+ "version": "16.13.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
+ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
+ "license": "MIT"
+ },
"node_modules/property-information": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz",
@@ -8915,10 +8908,13 @@
}
},
"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==",
- "license": "MIT"
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz",
+ "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ }
},
"node_modules/punycode": {
"version": "2.3.1",
@@ -8995,6 +8991,15 @@
"react": "^18.3.1"
}
},
+ "node_modules/react-dom/node_modules/scheduler": {
+ "version": "0.23.2",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
+ "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
+ "license": "MIT",
+ "dependencies": {
+ "loose-envify": "^1.1.0"
+ }
+ },
"node_modules/react-dropzone": {
"version": "14.4.1",
"resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-14.4.1.tgz",
@@ -9013,10 +9018,12 @@
}
},
"node_modules/react-is": {
- "version": "16.13.1",
- "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
- "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
- "license": "MIT"
+ "version": "17.0.2",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
+ "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
},
"node_modules/react-leaflet": {
"version": "4.2.1",
@@ -9253,9 +9260,9 @@
"license": "MIT"
},
"node_modules/regjsparser": {
- "version": "0.13.0",
- "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.13.0.tgz",
- "integrity": "sha512-NZQZdC5wOE/H3UT28fVGL+ikOZcEzfMGk/c3iN9UGxzWHMa1op7274oyiUVrAG4B2EuFhus8SvkaYnhvW92p9Q==",
+ "version": "0.13.1",
+ "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.13.1.tgz",
+ "integrity": "sha512-dLsljMd9sqwRkby8zhO1gSg3PnJIBFid8f4CQj/sXx+7cKx+E7u0PKhZ+U4wmhx7EfmtvnA318oVaIkAB1lRJw==",
"dev": true,
"license": "BSD-2-Clause",
"dependencies": {
@@ -9395,51 +9402,10 @@
"node": ">=0.10.0"
}
},
- "node_modules/rolldown": {
- "version": "1.0.0-rc.12",
- "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.12.tgz",
- "integrity": "sha512-yP4USLIMYrwpPHEFB5JGH1uxhcslv6/hL0OyvTuY+3qlOSJvZ7ntYnoWpehBxufkgN0cvXxppuTu5hHa/zPh+A==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@oxc-project/types": "=0.122.0",
- "@rolldown/pluginutils": "1.0.0-rc.12"
- },
- "bin": {
- "rolldown": "bin/cli.mjs"
- },
- "engines": {
- "node": "^20.19.0 || >=22.12.0"
- },
- "optionalDependencies": {
- "@rolldown/binding-android-arm64": "1.0.0-rc.12",
- "@rolldown/binding-darwin-arm64": "1.0.0-rc.12",
- "@rolldown/binding-darwin-x64": "1.0.0-rc.12",
- "@rolldown/binding-freebsd-x64": "1.0.0-rc.12",
- "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.12",
- "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.12",
- "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.12",
- "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.12",
- "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.12",
- "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.12",
- "@rolldown/binding-linux-x64-musl": "1.0.0-rc.12",
- "@rolldown/binding-openharmony-arm64": "1.0.0-rc.12",
- "@rolldown/binding-wasm32-wasi": "1.0.0-rc.12",
- "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.12",
- "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.12"
- }
- },
- "node_modules/rolldown/node_modules/@rolldown/pluginutils": {
- "version": "1.0.0-rc.12",
- "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.12.tgz",
- "integrity": "sha512-HHMwmarRKvoFsJorqYlFeFRzXZqCt2ETQlEDOb9aqssrnVBB1/+xgTGtuTrIk5vzLNX1MjMtTf7W9z3tsSbrxw==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/rollup": {
- "version": "4.59.0",
- "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.0.tgz",
- "integrity": "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==",
+ "version": "4.60.1",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.1.tgz",
+ "integrity": "sha512-VmtB2rFU/GroZ4oL8+ZqXgSA38O6GR8KSIvWmEFv63pQ0G6KaBH9s07PO8XTXP4vI+3UJUEypOfjkGfmSBBR0w==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -9453,31 +9419,31 @@
"npm": ">=8.0.0"
},
"optionalDependencies": {
- "@rollup/rollup-android-arm-eabi": "4.59.0",
- "@rollup/rollup-android-arm64": "4.59.0",
- "@rollup/rollup-darwin-arm64": "4.59.0",
- "@rollup/rollup-darwin-x64": "4.59.0",
- "@rollup/rollup-freebsd-arm64": "4.59.0",
- "@rollup/rollup-freebsd-x64": "4.59.0",
- "@rollup/rollup-linux-arm-gnueabihf": "4.59.0",
- "@rollup/rollup-linux-arm-musleabihf": "4.59.0",
- "@rollup/rollup-linux-arm64-gnu": "4.59.0",
- "@rollup/rollup-linux-arm64-musl": "4.59.0",
- "@rollup/rollup-linux-loong64-gnu": "4.59.0",
- "@rollup/rollup-linux-loong64-musl": "4.59.0",
- "@rollup/rollup-linux-ppc64-gnu": "4.59.0",
- "@rollup/rollup-linux-ppc64-musl": "4.59.0",
- "@rollup/rollup-linux-riscv64-gnu": "4.59.0",
- "@rollup/rollup-linux-riscv64-musl": "4.59.0",
- "@rollup/rollup-linux-s390x-gnu": "4.59.0",
- "@rollup/rollup-linux-x64-gnu": "4.59.0",
- "@rollup/rollup-linux-x64-musl": "4.59.0",
- "@rollup/rollup-openbsd-x64": "4.59.0",
- "@rollup/rollup-openharmony-arm64": "4.59.0",
- "@rollup/rollup-win32-arm64-msvc": "4.59.0",
- "@rollup/rollup-win32-ia32-msvc": "4.59.0",
- "@rollup/rollup-win32-x64-gnu": "4.59.0",
- "@rollup/rollup-win32-x64-msvc": "4.59.0",
+ "@rollup/rollup-android-arm-eabi": "4.60.1",
+ "@rollup/rollup-android-arm64": "4.60.1",
+ "@rollup/rollup-darwin-arm64": "4.60.1",
+ "@rollup/rollup-darwin-x64": "4.60.1",
+ "@rollup/rollup-freebsd-arm64": "4.60.1",
+ "@rollup/rollup-freebsd-x64": "4.60.1",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.60.1",
+ "@rollup/rollup-linux-arm-musleabihf": "4.60.1",
+ "@rollup/rollup-linux-arm64-gnu": "4.60.1",
+ "@rollup/rollup-linux-arm64-musl": "4.60.1",
+ "@rollup/rollup-linux-loong64-gnu": "4.60.1",
+ "@rollup/rollup-linux-loong64-musl": "4.60.1",
+ "@rollup/rollup-linux-ppc64-gnu": "4.60.1",
+ "@rollup/rollup-linux-ppc64-musl": "4.60.1",
+ "@rollup/rollup-linux-riscv64-gnu": "4.60.1",
+ "@rollup/rollup-linux-riscv64-musl": "4.60.1",
+ "@rollup/rollup-linux-s390x-gnu": "4.60.1",
+ "@rollup/rollup-linux-x64-gnu": "4.60.1",
+ "@rollup/rollup-linux-x64-musl": "4.60.1",
+ "@rollup/rollup-openbsd-x64": "4.60.1",
+ "@rollup/rollup-openharmony-arm64": "4.60.1",
+ "@rollup/rollup-win32-arm64-msvc": "4.60.1",
+ "@rollup/rollup-win32-ia32-msvc": "4.60.1",
+ "@rollup/rollup-win32-x64-gnu": "4.60.1",
+ "@rollup/rollup-win32-x64-msvc": "4.60.1",
"fsevents": "~2.3.2"
}
},
@@ -9594,13 +9560,10 @@
}
},
"node_modules/scheduler": {
- "version": "0.23.2",
- "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
- "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
- "license": "MIT",
- "dependencies": {
- "loose-envify": "^1.1.0"
- }
+ "version": "0.25.0-rc-603e6108-20241029",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.25.0-rc-603e6108-20241029.tgz",
+ "integrity": "sha512-pFwF6H1XrSdYYNLfOcGlM28/j8CGLu8IvdrxqhjWULe2bPcKiKW4CV+OWqR/9fT52mywx65l7ysNkjLKBda7eA==",
+ "license": "MIT"
},
"node_modules/semver": {
"version": "6.3.1",
@@ -9768,14 +9731,14 @@
}
},
"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==",
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz",
+ "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==",
"dev": true,
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
- "object-inspect": "^1.13.3"
+ "object-inspect": "^1.13.4"
},
"engines": {
"node": ">= 0.4"
@@ -9907,6 +9870,35 @@
"node": ">=0.10.0"
}
},
+ "node_modules/source-map/node_modules/tr46": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz",
+ "integrity": "sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "punycode": "^2.1.0"
+ }
+ },
+ "node_modules/source-map/node_modules/webidl-conversions": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz",
+ "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==",
+ "dev": true,
+ "license": "BSD-2-Clause"
+ },
+ "node_modules/source-map/node_modules/whatwg-url": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz",
+ "integrity": "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "lodash.sortby": "^4.7.0",
+ "tr46": "^1.0.1",
+ "webidl-conversions": "^4.0.2"
+ }
+ },
"node_modules/sourcemap-codec": {
"version": "1.4.8",
"resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz",
@@ -9943,9 +9935,9 @@
}
},
"node_modules/std-env": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.0.0.tgz",
- "integrity": "sha512-zUMPtQ/HBY3/50VbpkupYHbRroTRZJPRLvreamgErJVys0ceuzMkD44J/QjqhHjOzK42GQ3QZIeFG1OYfOtKqQ==",
+ "version": "3.10.0",
+ "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz",
+ "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==",
"dev": true,
"license": "MIT"
},
@@ -9980,6 +9972,25 @@
}
},
"node_modules/string-width": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
+ "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "eastasianwidth": "^0.2.0",
+ "emoji-regex": "^9.2.2",
+ "strip-ansi": "^7.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/string-width-cjs": {
+ "name": "string-width",
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
@@ -9994,6 +10005,26 @@
"node": ">=8"
}
},
+ "node_modules/string-width-cjs/node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/string-width-cjs/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,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/string.prototype.matchall": {
"version": "4.0.12",
"resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz",
@@ -10111,6 +10142,23 @@
}
},
"node_modules/strip-ansi": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz",
+ "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^6.2.2"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/strip-ansi?sponsor=1"
+ }
+ },
+ "node_modules/strip-ansi-cjs": {
+ "name": "strip-ansi",
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
@@ -10123,6 +10171,19 @@
"node": ">=8"
}
},
+ "node_modules/strip-ansi/node_modules/ansi-regex": {
+ "version": "6.2.2",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz",
+ "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-regex?sponsor=1"
+ }
+ },
"node_modules/strip-comments": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/strip-comments/-/strip-comments-2.0.1.tgz",
@@ -10146,6 +10207,26 @@
"node": ">=8"
}
},
+ "node_modules/strip-literal": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.1.0.tgz",
+ "integrity": "sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "js-tokens": "^9.0.1"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ }
+ },
+ "node_modules/strip-literal/node_modules/js-tokens": {
+ "version": "9.0.1",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz",
+ "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/style-to-js": {
"version": "1.1.21",
"resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.21.tgz",
@@ -10306,6 +10387,19 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/tempy/node_modules/type-fest": {
+ "version": "0.16.0",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.16.0.tgz",
+ "integrity": "sha512-eaBzG6MxNzEn9kiwvtre90cXaNLkmadMWa1zQMs3XORCXNbsH/OewwbxC5ia9dCxIxnTAsSxXJaa/p5y8DlvJg==",
+ "dev": true,
+ "license": "(MIT OR CC0-1.0)",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/terser": {
"version": "5.46.1",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.46.1.tgz",
@@ -10332,6 +10426,21 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/test-exclude": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-7.0.2.tgz",
+ "integrity": "sha512-u9E6A+ZDYdp7a4WnarkXPZOx8Ilz46+kby6p1yZ8zsGTz9gYa6FIS7lj2oezzNKmtdyyJNNmmXDppga5GB7kSw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "@istanbuljs/schema": "^0.1.2",
+ "glob": "^10.4.1",
+ "minimatch": "^10.2.2"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/thenify": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz",
@@ -10369,24 +10478,21 @@
"license": "MIT"
},
"node_modules/tinyexec": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.4.tgz",
- "integrity": "sha512-u9r3uZC0bdpGOXtlxUIdwf9pkmvhqJdrVCH9fapQtgy/OeTTMZ1nqH7agtvEfmGui6e1XxjcdrlxvxJvc3sMqw==",
+ "version": "0.3.2",
+ "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz",
+ "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==",
"dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=18"
- }
+ "license": "MIT"
},
"node_modules/tinyglobby": {
- "version": "0.2.15",
- "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
- "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==",
+ "version": "0.2.16",
+ "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz",
+ "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==",
"dev": true,
"license": "MIT",
"dependencies": {
"fdir": "^6.5.0",
- "picomatch": "^4.0.3"
+ "picomatch": "^4.0.4"
},
"engines": {
"node": ">=12.0.0"
@@ -10426,10 +10532,30 @@
"url": "https://github.com/sponsors/jonschlinkert"
}
},
+ "node_modules/tinypool": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz",
+ "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.0.0 || >=20.0.0"
+ }
+ },
"node_modules/tinyrainbow": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz",
- "integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==",
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz",
+ "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/tinyspy": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-4.0.4.tgz",
+ "integrity": "sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==",
"dev": true,
"license": "MIT",
"engines": {
@@ -10503,13 +10629,16 @@
}
},
"node_modules/tr46": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz",
- "integrity": "sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==",
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/tr46/-/tr46-6.0.0.tgz",
+ "integrity": "sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "punycode": "^2.1.0"
+ "punycode": "^2.3.1"
+ },
+ "engines": {
+ "node": ">=20"
}
},
"node_modules/trim-lines": {
@@ -10546,13 +10675,16 @@
"license": "0BSD"
},
"node_modules/type-fest": {
- "version": "0.16.0",
- "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.16.0.tgz",
- "integrity": "sha512-eaBzG6MxNzEn9kiwvtre90cXaNLkmadMWa1zQMs3XORCXNbsH/OewwbxC5ia9dCxIxnTAsSxXJaa/p5y8DlvJg==",
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-5.5.0.tgz",
+ "integrity": "sha512-PlBfpQwiUvGViBNX84Yxwjsdhd1TUlXr6zjX7eoirtCPIr08NAmxwa+fcYBTeRQxHo9YC9wwF3m9i700sHma8g==",
"dev": true,
"license": "(MIT OR CC0-1.0)",
+ "dependencies": {
+ "tagged-tag": "^1.0.0"
+ },
"engines": {
- "node": ">=10"
+ "node": ">=20"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
@@ -11028,6 +11160,29 @@
"node": ">= 6"
}
},
+ "node_modules/vite-node": {
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.2.4.tgz",
+ "integrity": "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "cac": "^6.7.14",
+ "debug": "^4.4.1",
+ "es-module-lexer": "^1.7.0",
+ "pathe": "^2.0.3",
+ "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0"
+ },
+ "bin": {
+ "vite-node": "vite-node.mjs"
+ },
+ "engines": {
+ "node": "^18.0.0 || ^20.0.0 || >=22.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
"node_modules/vite-plugin-pwa": {
"version": "0.21.2",
"resolved": "https://registry.npmjs.org/vite-plugin-pwa/-/vite-plugin-pwa-0.21.2.tgz",
@@ -11059,502 +11214,66 @@
}
}
},
- "node_modules/vite/node_modules/@esbuild/aix-ppc64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
- "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==",
- "cpu": [
- "ppc64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "aix"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/vite/node_modules/@esbuild/android-arm": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz",
- "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==",
- "cpu": [
- "arm"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/vite/node_modules/@esbuild/android-arm64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz",
- "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/vite/node_modules/@esbuild/android-x64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz",
- "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/vite/node_modules/@esbuild/darwin-arm64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz",
- "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/vite/node_modules/@esbuild/darwin-x64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz",
- "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/vite/node_modules/@esbuild/freebsd-arm64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz",
- "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "freebsd"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/vite/node_modules/@esbuild/freebsd-x64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz",
- "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "freebsd"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/vite/node_modules/@esbuild/linux-arm": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz",
- "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==",
- "cpu": [
- "arm"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/vite/node_modules/@esbuild/linux-arm64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz",
- "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/vite/node_modules/@esbuild/linux-ia32": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz",
- "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==",
- "cpu": [
- "ia32"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/vite/node_modules/@esbuild/linux-loong64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz",
- "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==",
- "cpu": [
- "loong64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/vite/node_modules/@esbuild/linux-mips64el": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz",
- "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==",
- "cpu": [
- "mips64el"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/vite/node_modules/@esbuild/linux-ppc64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz",
- "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==",
- "cpu": [
- "ppc64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/vite/node_modules/@esbuild/linux-riscv64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz",
- "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==",
- "cpu": [
- "riscv64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/vite/node_modules/@esbuild/linux-s390x": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz",
- "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==",
- "cpu": [
- "s390x"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/vite/node_modules/@esbuild/linux-x64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz",
- "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/vite/node_modules/@esbuild/netbsd-x64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz",
- "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "netbsd"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/vite/node_modules/@esbuild/openbsd-x64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz",
- "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "openbsd"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/vite/node_modules/@esbuild/sunos-x64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz",
- "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "sunos"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/vite/node_modules/@esbuild/win32-arm64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz",
- "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/vite/node_modules/@esbuild/win32-ia32": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz",
- "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==",
- "cpu": [
- "ia32"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/vite/node_modules/@esbuild/win32-x64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz",
- "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/vite/node_modules/esbuild": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz",
- "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==",
- "dev": true,
- "hasInstallScript": true,
- "license": "MIT",
- "bin": {
- "esbuild": "bin/esbuild"
- },
- "engines": {
- "node": ">=12"
- },
- "optionalDependencies": {
- "@esbuild/aix-ppc64": "0.21.5",
- "@esbuild/android-arm": "0.21.5",
- "@esbuild/android-arm64": "0.21.5",
- "@esbuild/android-x64": "0.21.5",
- "@esbuild/darwin-arm64": "0.21.5",
- "@esbuild/darwin-x64": "0.21.5",
- "@esbuild/freebsd-arm64": "0.21.5",
- "@esbuild/freebsd-x64": "0.21.5",
- "@esbuild/linux-arm": "0.21.5",
- "@esbuild/linux-arm64": "0.21.5",
- "@esbuild/linux-ia32": "0.21.5",
- "@esbuild/linux-loong64": "0.21.5",
- "@esbuild/linux-mips64el": "0.21.5",
- "@esbuild/linux-ppc64": "0.21.5",
- "@esbuild/linux-riscv64": "0.21.5",
- "@esbuild/linux-s390x": "0.21.5",
- "@esbuild/linux-x64": "0.21.5",
- "@esbuild/netbsd-x64": "0.21.5",
- "@esbuild/openbsd-x64": "0.21.5",
- "@esbuild/sunos-x64": "0.21.5",
- "@esbuild/win32-arm64": "0.21.5",
- "@esbuild/win32-ia32": "0.21.5",
- "@esbuild/win32-x64": "0.21.5"
- }
- },
"node_modules/vitest": {
- "version": "4.1.2",
- "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.2.tgz",
- "integrity": "sha512-xjR1dMTVHlFLh98JE3i/f/WePqJsah4A0FK9cc8Ehp9Udk0AZk6ccpIZhh1qJ/yxVWRZ+Q54ocnD8TXmkhspGg==",
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.4.tgz",
+ "integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@vitest/expect": "4.1.2",
- "@vitest/mocker": "4.1.2",
- "@vitest/pretty-format": "4.1.2",
- "@vitest/runner": "4.1.2",
- "@vitest/snapshot": "4.1.2",
- "@vitest/spy": "4.1.2",
- "@vitest/utils": "4.1.2",
- "es-module-lexer": "^2.0.0",
- "expect-type": "^1.3.0",
- "magic-string": "^0.30.21",
- "obug": "^2.1.1",
+ "@types/chai": "^5.2.2",
+ "@vitest/expect": "3.2.4",
+ "@vitest/mocker": "3.2.4",
+ "@vitest/pretty-format": "^3.2.4",
+ "@vitest/runner": "3.2.4",
+ "@vitest/snapshot": "3.2.4",
+ "@vitest/spy": "3.2.4",
+ "@vitest/utils": "3.2.4",
+ "chai": "^5.2.0",
+ "debug": "^4.4.1",
+ "expect-type": "^1.2.1",
+ "magic-string": "^0.30.17",
"pathe": "^2.0.3",
- "picomatch": "^4.0.3",
- "std-env": "^4.0.0-rc.1",
+ "picomatch": "^4.0.2",
+ "std-env": "^3.9.0",
"tinybench": "^2.9.0",
- "tinyexec": "^1.0.2",
- "tinyglobby": "^0.2.15",
- "tinyrainbow": "^3.1.0",
- "vite": "^6.0.0 || ^7.0.0 || ^8.0.0",
+ "tinyexec": "^0.3.2",
+ "tinyglobby": "^0.2.14",
+ "tinypool": "^1.1.1",
+ "tinyrainbow": "^2.0.0",
+ "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0",
+ "vite-node": "3.2.4",
"why-is-node-running": "^2.3.0"
},
"bin": {
"vitest": "vitest.mjs"
},
"engines": {
- "node": "^20.0.0 || ^22.0.0 || >=24.0.0"
+ "node": "^18.0.0 || ^20.0.0 || >=22.0.0"
},
"funding": {
"url": "https://opencollective.com/vitest"
},
"peerDependencies": {
"@edge-runtime/vm": "*",
- "@opentelemetry/api": "^1.9.0",
- "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0",
- "@vitest/browser-playwright": "4.1.2",
- "@vitest/browser-preview": "4.1.2",
- "@vitest/browser-webdriverio": "4.1.2",
- "@vitest/ui": "4.1.2",
+ "@types/debug": "^4.1.12",
+ "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0",
+ "@vitest/browser": "3.2.4",
+ "@vitest/ui": "3.2.4",
"happy-dom": "*",
- "jsdom": "*",
- "vite": "^6.0.0 || ^7.0.0 || ^8.0.0"
+ "jsdom": "*"
},
"peerDependenciesMeta": {
"@edge-runtime/vm": {
"optional": true
},
- "@opentelemetry/api": {
+ "@types/debug": {
"optional": true
},
"@types/node": {
"optional": true
},
- "@vitest/browser-playwright": {
- "optional": true
- },
- "@vitest/browser-preview": {
- "optional": true
- },
- "@vitest/browser-webdriverio": {
+ "@vitest/browser": {
"optional": true
},
"@vitest/ui": {
@@ -11565,59 +11284,9 @@
},
"jsdom": {
"optional": true
- },
- "vite": {
- "optional": false
}
}
},
- "node_modules/vitest/node_modules/@vitest/mocker": {
- "version": "4.1.2",
- "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.2.tgz",
- "integrity": "sha512-Ize4iQtEALHDttPRCmN+FKqOl2vxTiNUhzobQFFt/BM1lRUTG7zRCLOykG/6Vo4E4hnUdfVLo5/eqKPukcWW7Q==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@vitest/spy": "4.1.2",
- "estree-walker": "^3.0.3",
- "magic-string": "^0.30.21"
- },
- "funding": {
- "url": "https://opencollective.com/vitest"
- },
- "peerDependencies": {
- "msw": "^2.4.9",
- "vite": "^6.0.0 || ^7.0.0 || ^8.0.0"
- },
- "peerDependenciesMeta": {
- "msw": {
- "optional": true
- },
- "vite": {
- "optional": true
- }
- }
- },
- "node_modules/vitest/node_modules/estree-walker": {
- "version": "3.0.3",
- "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz",
- "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@types/estree": "^1.0.0"
- }
- },
- "node_modules/vitest/node_modules/magic-string": {
- "version": "0.30.21",
- "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
- "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@jridgewell/sourcemap-codec": "^1.5.5"
- }
- },
"node_modules/vitest/node_modules/picomatch": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
@@ -11631,84 +11300,6 @@
"url": "https://github.com/sponsors/jonschlinkert"
}
},
- "node_modules/vitest/node_modules/vite": {
- "version": "8.0.5",
- "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.5.tgz",
- "integrity": "sha512-nmu43Qvq9UopTRfMx2jOYW5l16pb3iDC1JH6yMuPkpVbzK0k+L7dfsEDH4jRgYFmsg0sTAqkojoZgzLMlwHsCQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "lightningcss": "^1.32.0",
- "picomatch": "^4.0.4",
- "postcss": "^8.5.8",
- "rolldown": "1.0.0-rc.12",
- "tinyglobby": "^0.2.15"
- },
- "bin": {
- "vite": "bin/vite.js"
- },
- "engines": {
- "node": "^20.19.0 || >=22.12.0"
- },
- "funding": {
- "url": "https://github.com/vitejs/vite?sponsor=1"
- },
- "optionalDependencies": {
- "fsevents": "~2.3.3"
- },
- "peerDependencies": {
- "@types/node": "^20.19.0 || >=22.12.0",
- "@vitejs/devtools": "^0.1.0",
- "esbuild": "^0.27.0 || ^0.28.0",
- "jiti": ">=1.21.0",
- "less": "^4.0.0",
- "sass": "^1.70.0",
- "sass-embedded": "^1.70.0",
- "stylus": ">=0.54.8",
- "sugarss": "^5.0.0",
- "terser": "^5.16.0",
- "tsx": "^4.8.1",
- "yaml": "^2.4.2"
- },
- "peerDependenciesMeta": {
- "@types/node": {
- "optional": true
- },
- "@vitejs/devtools": {
- "optional": true
- },
- "esbuild": {
- "optional": true
- },
- "jiti": {
- "optional": true
- },
- "less": {
- "optional": true
- },
- "sass": {
- "optional": true
- },
- "sass-embedded": {
- "optional": true
- },
- "stylus": {
- "optional": true
- },
- "sugarss": {
- "optional": true
- },
- "terser": {
- "optional": true
- },
- "tsx": {
- "optional": true
- },
- "yaml": {
- "optional": true
- }
- }
- },
"node_modules/w3c-xmlserializer": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz",
@@ -11723,11 +11314,14 @@
}
},
"node_modules/webidl-conversions": {
- "version": "4.0.2",
- "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz",
- "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==",
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.1.tgz",
+ "integrity": "sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==",
"dev": true,
- "license": "BSD-2-Clause"
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=20"
+ }
},
"node_modules/whatwg-mimetype": {
"version": "5.0.0",
@@ -11740,15 +11334,18 @@
}
},
"node_modules/whatwg-url": {
- "version": "7.1.0",
- "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz",
- "integrity": "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==",
+ "version": "16.0.1",
+ "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-16.0.1.tgz",
+ "integrity": "sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "lodash.sortby": "^4.7.0",
- "tr46": "^1.0.1",
- "webidl-conversions": "^4.0.2"
+ "@exodus/bytes": "^1.11.0",
+ "tr46": "^6.0.0",
+ "webidl-conversions": "^8.0.1"
+ },
+ "engines": {
+ "node": "^20.19.0 || ^22.12.0 || >=24.0.0"
}
},
"node_modules/which": {
@@ -11943,6 +11540,16 @@
"node": ">=20.0.0"
}
},
+ "node_modules/workbox-build/node_modules/@isaacs/cliui": {
+ "version": "9.0.0",
+ "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-9.0.0.tgz",
+ "integrity": "sha512-AokJm4tuBHillT+FpMtxQ60n8ObyXBatq7jD2/JA9dxbDDokKQm8KMht5ibGzLVU9IJDIKK4TPKgMHEYMn3lMg==",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/workbox-build/node_modules/@rollup/plugin-babel": {
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz",
@@ -12013,6 +11620,84 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/workbox-build/node_modules/glob": {
+ "version": "11.1.0",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-11.1.0.tgz",
+ "integrity": "sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw==",
+ "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
+ "dependencies": {
+ "foreground-child": "^3.3.1",
+ "jackspeak": "^4.1.1",
+ "minimatch": "^10.1.1",
+ "minipass": "^7.1.2",
+ "package-json-from-dist": "^1.0.0",
+ "path-scurry": "^2.0.0"
+ },
+ "bin": {
+ "glob": "dist/esm/bin.mjs"
+ },
+ "engines": {
+ "node": "20 || >=22"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/workbox-build/node_modules/jackspeak": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.2.3.tgz",
+ "integrity": "sha512-ykkVRwrYvFm1nb2AJfKKYPr0emF6IiXDYUaFx4Zn9ZuIH7MrzEZ3sD5RlqGXNRpHtvUHJyOnCEFxOlNDtGo7wg==",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
+ "dependencies": {
+ "@isaacs/cliui": "^9.0.0"
+ },
+ "engines": {
+ "node": "20 || >=22"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/workbox-build/node_modules/lru-cache": {
+ "version": "11.3.3",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.3.3.tgz",
+ "integrity": "sha512-JvNw9Y81y33E+BEYPr0U7omo+U9AySnsMsEiXgwT6yqd31VQWTLNQqmT4ou5eqPFUrTfIDFta2wKhB1hyohtAQ==",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
+ "engines": {
+ "node": "20 || >=22"
+ }
+ },
+ "node_modules/workbox-build/node_modules/magic-string": {
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz",
+ "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "sourcemap-codec": "^1.4.8"
+ }
+ },
+ "node_modules/workbox-build/node_modules/path-scurry": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz",
+ "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
+ "dependencies": {
+ "lru-cache": "^11.0.0",
+ "minipass": "^7.1.2"
+ },
+ "engines": {
+ "node": "18 || 20 || >=22"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
"node_modules/workbox-build/node_modules/pretty-bytes": {
"version": "5.6.0",
"resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz",
@@ -12194,6 +11879,76 @@
"node": ">=8"
}
},
+ "node_modules/wrap-ansi-cjs": {
+ "name": "wrap-ansi",
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
+ "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^4.0.0",
+ "string-width": "^4.1.0",
+ "strip-ansi": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+ }
+ },
+ "node_modules/wrap-ansi-cjs/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,
+ "license": "MIT",
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/wrap-ansi-cjs/node_modules/string-width": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/wrap-ansi-cjs/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,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/wrap-ansi/node_modules/ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
@@ -12210,6 +11965,41 @@
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
+ "node_modules/wrap-ansi/node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/wrap-ansi/node_modules/string-width": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/wrap-ansi/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,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/xml-name-validator": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz",
@@ -12273,6 +12063,41 @@
"node": ">=12"
}
},
+ "node_modules/yargs/node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/yargs/node_modules/string-width": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/yargs/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,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/yoctocolors-cjs": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/yoctocolors-cjs/-/yoctocolors-cjs-2.1.3.tgz",
diff --git a/client/package.json b/client/package.json
index 56508759..e5d2c276 100644
--- a/client/package.json
+++ b/client/package.json
@@ -1,6 +1,6 @@
{
"name": "trek-client",
- "version": "2.9.12",
+ "version": "2.9.13",
"private": true,
"type": "module",
"scripts": {
@@ -41,7 +41,7 @@
"@types/react-dom": "^18.2.19",
"@types/react-window": "^1.8.8",
"@vitejs/plugin-react": "^4.2.1",
- "@vitest/coverage-v8": "^4.1.2",
+ "@vitest/coverage-v8": "^3.2.4",
"autoprefixer": "^10.4.18",
"jsdom": "^29.0.1",
"msw": "^2.13.0",
@@ -51,6 +51,6 @@
"typescript": "^6.0.2",
"vite": "^5.1.4",
"vite-plugin-pwa": "^0.21.0",
- "vitest": "^4.1.2"
+ "vitest": "^3.2.4"
}
}
diff --git a/client/src/App.tsx b/client/src/App.tsx
index 6adabd6a..5f95965f 100644
--- a/client/src/App.tsx
+++ b/client/src/App.tsx
@@ -15,6 +15,7 @@ import JourneyDetailPage from './pages/JourneyDetailPage'
import JourneyPublicPage from './pages/JourneyPublicPage'
import SharedTripPage from './pages/SharedTripPage'
import InAppNotificationsPage from './pages/InAppNotificationsPage.tsx'
+import OAuthAuthorizePage from './pages/OAuthAuthorizePage'
import { ToastContainer } from './components/shared/Toast'
import BottomNav from './components/Layout/BottomNav'
import { TranslationProvider, useTranslation } from './i18n'
@@ -173,6 +174,8 @@ export default function App() {
} />
} />
} />
+ {/* OAuth 2.1 consent page — intentionally outside ProtectedRoute */}
+ } />
apiClient.get('/oauth/authorize/validate', { params }).then(r => r.data),
+
+ /** Submit user consent (approve or deny) */
+ authorize: (body: {
+ client_id: string
+ redirect_uri: string
+ scope: string
+ state?: string
+ code_challenge: string
+ code_challenge_method: string
+ approved: boolean
+ }) => apiClient.post('/oauth/authorize', body).then(r => r.data),
+
+ clients: {
+ list: () => apiClient.get('/oauth/clients').then(r => r.data),
+ create: (data: { name: string; redirect_uris: string[]; allowed_scopes: string[] }) =>
+ apiClient.post('/oauth/clients', data).then(r => r.data),
+ rotate: (id: string) => apiClient.post(`/oauth/clients/${id}/rotate`).then(r => r.data),
+ delete: (id: string) => apiClient.delete(`/oauth/clients/${id}`).then(r => r.data),
+ },
+
+ sessions: {
+ list: () => apiClient.get('/oauth/sessions').then(r => r.data),
+ revoke: (id: number) => apiClient.delete(`/oauth/sessions/${id}`).then(r => r.data),
+ },
+}
+
export const tripsApi = {
list: (params?: Record) => apiClient.get('/trips', { params }).then(r => r.data),
create: (data: Record) => apiClient.post('/trips', data).then(r => r.data),
@@ -195,6 +232,8 @@ export const adminApi = {
apiClient.get('/admin/audit-log', { params }).then(r => r.data),
mcpTokens: () => apiClient.get('/admin/mcp-tokens').then(r => r.data),
deleteMcpToken: (id: number) => apiClient.delete(`/admin/mcp-tokens/${id}`).then(r => r.data),
+ oauthSessions: () => apiClient.get('/admin/oauth-sessions').then(r => r.data),
+ revokeOAuthSession: (id: number) => apiClient.delete(`/admin/oauth-sessions/${id}`).then(r => r.data),
getPermissions: () => apiClient.get('/admin/permissions').then(r => r.data),
updatePermissions: (permissions: Record) => apiClient.put('/admin/permissions', { permissions }).then(r => r.data),
rotateJwtSecret: () => apiClient.post('/admin/rotate-jwt-secret').then(r => r.data),
diff --git a/client/src/api/oauthScopes.test.ts b/client/src/api/oauthScopes.test.ts
new file mode 100644
index 00000000..b16da606
--- /dev/null
+++ b/client/src/api/oauthScopes.test.ts
@@ -0,0 +1,102 @@
+// FE-OAUTH-SCOPES-001 to FE-OAUTH-SCOPES-010
+import { describe, it, expect } from 'vitest'
+import { SCOPE_GROUPS, ALL_SCOPES, SCOPE_GROUP_NAMES, getScopesByGroup } from './oauthScopes'
+
+describe('SCOPE_GROUPS', () => {
+ it('FE-OAUTH-SCOPES-001: contains all expected scope keys', () => {
+ const expected = [
+ 'trips:read', 'trips:write', 'trips:delete', 'trips:share',
+ 'places:read', 'places:write',
+ 'atlas:read', 'atlas:write',
+ 'packing:read', 'packing:write',
+ 'todos:read', 'todos:write',
+ 'budget:read', 'budget:write',
+ 'reservations:read', 'reservations:write',
+ 'collab:read', 'collab:write',
+ 'notifications:read', 'notifications:write',
+ 'vacay:read', 'vacay:write',
+ 'geo:read', 'weather:read',
+ ]
+ for (const scope of expected) {
+ expect(SCOPE_GROUPS).toHaveProperty(scope)
+ }
+ })
+
+ it('FE-OAUTH-SCOPES-002: each scope entry has labelKey, descriptionKey, groupKey', () => {
+ for (const [scope, keys] of Object.entries(SCOPE_GROUPS)) {
+ expect(keys.labelKey, `${scope} missing labelKey`).toBeTruthy()
+ expect(keys.descriptionKey, `${scope} missing descriptionKey`).toBeTruthy()
+ expect(keys.groupKey, `${scope} missing groupKey`).toBeTruthy()
+ }
+ })
+})
+
+describe('ALL_SCOPES', () => {
+ it('FE-OAUTH-SCOPES-003: contains exactly 24 scopes', () => {
+ expect(ALL_SCOPES).toHaveLength(24)
+ })
+
+ it('FE-OAUTH-SCOPES-004: matches Object.keys(SCOPE_GROUPS)', () => {
+ expect(ALL_SCOPES).toEqual(Object.keys(SCOPE_GROUPS))
+ })
+})
+
+describe('SCOPE_GROUP_NAMES', () => {
+ it('FE-OAUTH-SCOPES-005: contains no duplicate group names', () => {
+ expect(SCOPE_GROUP_NAMES).toHaveLength(new Set(SCOPE_GROUP_NAMES).size)
+ })
+
+ it('FE-OAUTH-SCOPES-006: contains expected groups', () => {
+ const expected = [
+ 'oauth.scope.group.trips',
+ 'oauth.scope.group.places',
+ 'oauth.scope.group.packing',
+ 'oauth.scope.group.budget',
+ ]
+ for (const g of expected) {
+ expect(SCOPE_GROUP_NAMES).toContain(g)
+ }
+ })
+})
+
+describe('getScopesByGroup', () => {
+ const identity = (key: string) => key
+
+ it('FE-OAUTH-SCOPES-007: groups all scopes under the correct group key', () => {
+ const groups = getScopesByGroup(identity)
+ // Every scope must appear exactly once across all groups
+ const allScopesInGroups = Object.values(groups).flat().map(s => s.scope)
+ expect(allScopesInGroups).toHaveLength(ALL_SCOPES.length)
+ for (const scope of ALL_SCOPES) {
+ expect(allScopesInGroups).toContain(scope)
+ }
+ })
+
+ it('FE-OAUTH-SCOPES-008: each item has scope, label, description, group', () => {
+ const groups = getScopesByGroup(identity)
+ for (const items of Object.values(groups)) {
+ for (const item of items) {
+ expect(item.scope).toBeTruthy()
+ expect(item.label).toBeTruthy()
+ expect(item.description).toBeTruthy()
+ expect(item.group).toBeTruthy()
+ }
+ }
+ })
+
+ it('FE-OAUTH-SCOPES-009: trips group contains trips:read and trips:write', () => {
+ const groups = getScopesByGroup(identity)
+ const tripsGroup = groups['oauth.scope.group.trips']
+ expect(tripsGroup).toBeDefined()
+ const scopeNames = tripsGroup.map(s => s.scope)
+ expect(scopeNames).toContain('trips:read')
+ expect(scopeNames).toContain('trips:write')
+ })
+
+ it('FE-OAUTH-SCOPES-010: uses translated group name as key', () => {
+ const t = (key: string) => key === 'oauth.scope.group.trips' ? 'Trips' : key
+ const groups = getScopesByGroup(t)
+ expect(groups['Trips']).toBeDefined()
+ expect(groups['oauth.scope.group.trips']).toBeUndefined()
+ })
+})
diff --git a/client/src/api/oauthScopes.ts b/client/src/api/oauthScopes.ts
new file mode 100644
index 00000000..55cc3c09
--- /dev/null
+++ b/client/src/api/oauthScopes.ts
@@ -0,0 +1,56 @@
+// Human-readable scope definitions for the OAuth consent page.
+// Must stay in sync with server/src/mcp/scopes.ts
+
+export interface ScopeInfo {
+ label: string
+ description: string
+ group: string
+}
+
+export interface ScopeKeys {
+ labelKey: string
+ descriptionKey: string
+ groupKey: string
+}
+
+export const SCOPE_GROUPS: Record = {
+ 'trips:read': { labelKey: 'oauth.scope.trips:read.label', descriptionKey: 'oauth.scope.trips:read.description', groupKey: 'oauth.scope.group.trips' },
+ 'trips:write': { labelKey: 'oauth.scope.trips:write.label', descriptionKey: 'oauth.scope.trips:write.description', groupKey: 'oauth.scope.group.trips' },
+ 'trips:delete': { labelKey: 'oauth.scope.trips:delete.label', descriptionKey: 'oauth.scope.trips:delete.description', groupKey: 'oauth.scope.group.trips' },
+ 'trips:share': { labelKey: 'oauth.scope.trips:share.label', descriptionKey: 'oauth.scope.trips:share.description', groupKey: 'oauth.scope.group.trips' },
+ 'places:read': { labelKey: 'oauth.scope.places:read.label', descriptionKey: 'oauth.scope.places:read.description', groupKey: 'oauth.scope.group.places' },
+ 'places:write': { labelKey: 'oauth.scope.places:write.label', descriptionKey: 'oauth.scope.places:write.description', groupKey: 'oauth.scope.group.places' },
+ 'atlas:read': { labelKey: 'oauth.scope.atlas:read.label', descriptionKey: 'oauth.scope.atlas:read.description', groupKey: 'oauth.scope.group.atlas' },
+ 'atlas:write': { labelKey: 'oauth.scope.atlas:write.label', descriptionKey: 'oauth.scope.atlas:write.description', groupKey: 'oauth.scope.group.atlas' },
+ 'packing:read': { labelKey: 'oauth.scope.packing:read.label', descriptionKey: 'oauth.scope.packing:read.description', groupKey: 'oauth.scope.group.packing' },
+ 'packing:write': { labelKey: 'oauth.scope.packing:write.label', descriptionKey: 'oauth.scope.packing:write.description', groupKey: 'oauth.scope.group.packing' },
+ 'todos:read': { labelKey: 'oauth.scope.todos:read.label', descriptionKey: 'oauth.scope.todos:read.description', groupKey: 'oauth.scope.group.todos' },
+ 'todos:write': { labelKey: 'oauth.scope.todos:write.label', descriptionKey: 'oauth.scope.todos:write.description', groupKey: 'oauth.scope.group.todos' },
+ 'budget:read': { labelKey: 'oauth.scope.budget:read.label', descriptionKey: 'oauth.scope.budget:read.description', groupKey: 'oauth.scope.group.budget' },
+ 'budget:write': { labelKey: 'oauth.scope.budget:write.label', descriptionKey: 'oauth.scope.budget:write.description', groupKey: 'oauth.scope.group.budget' },
+ 'reservations:read': { labelKey: 'oauth.scope.reservations:read.label', descriptionKey: 'oauth.scope.reservations:read.description', groupKey: 'oauth.scope.group.reservations' },
+ 'reservations:write': { labelKey: 'oauth.scope.reservations:write.label', descriptionKey: 'oauth.scope.reservations:write.description', groupKey: 'oauth.scope.group.reservations' },
+ 'collab:read': { labelKey: 'oauth.scope.collab:read.label', descriptionKey: 'oauth.scope.collab:read.description', groupKey: 'oauth.scope.group.collab' },
+ 'collab:write': { labelKey: 'oauth.scope.collab:write.label', descriptionKey: 'oauth.scope.collab:write.description', groupKey: 'oauth.scope.group.collab' },
+ 'notifications:read': { labelKey: 'oauth.scope.notifications:read.label', descriptionKey: 'oauth.scope.notifications:read.description', groupKey: 'oauth.scope.group.notifications' },
+ 'notifications:write': { labelKey: 'oauth.scope.notifications:write.label', descriptionKey: 'oauth.scope.notifications:write.description', groupKey: 'oauth.scope.group.notifications' },
+ 'vacay:read': { labelKey: 'oauth.scope.vacay:read.label', descriptionKey: 'oauth.scope.vacay:read.description', groupKey: 'oauth.scope.group.vacay' },
+ 'vacay:write': { labelKey: 'oauth.scope.vacay:write.label', descriptionKey: 'oauth.scope.vacay:write.description', groupKey: 'oauth.scope.group.vacay' },
+ 'geo:read': { labelKey: 'oauth.scope.geo:read.label', descriptionKey: 'oauth.scope.geo:read.description', groupKey: 'oauth.scope.group.geo' },
+ 'weather:read': { labelKey: 'oauth.scope.weather:read.label', descriptionKey: 'oauth.scope.weather:read.description', groupKey: 'oauth.scope.group.weather' },
+}
+
+export const ALL_SCOPES = Object.keys(SCOPE_GROUPS)
+
+// Group all scopes for the client registration form
+export const SCOPE_GROUP_NAMES = [...new Set(Object.values(SCOPE_GROUPS).map(s => s.groupKey))]
+
+export function getScopesByGroup(t: (key: string) => string): Record> {
+ const groups: Record> = {}
+ for (const [scope, keys] of Object.entries(SCOPE_GROUPS)) {
+ const group = t(keys.groupKey)
+ if (!groups[group]) groups[group] = []
+ groups[group].push({ scope, label: t(keys.labelKey), description: t(keys.descriptionKey), group })
+ }
+ return groups
+}
diff --git a/client/src/components/Admin/AdminMcpTokensPanel.test.tsx b/client/src/components/Admin/AdminMcpTokensPanel.test.tsx
index 3a5be8f7..8abcd44d 100644
--- a/client/src/components/Admin/AdminMcpTokensPanel.test.tsx
+++ b/client/src/components/Admin/AdminMcpTokensPanel.test.tsx
@@ -1,4 +1,4 @@
-// FE-ADMIN-MCP-001 to FE-ADMIN-MCP-010
+// FE-ADMIN-MCP-001 to FE-ADMIN-MCP-016
import { render, screen, waitFor } from '../../../tests/helpers/render';
import userEvent from '@testing-library/user-event';
import { http, HttpResponse } from 'msw';
@@ -197,4 +197,127 @@ describe('AdminMcpTokensPanel', () => {
render(<> >);
await screen.findByText('Failed to load tokens');
});
+
+ it('FE-ADMIN-MCP-011: OAuth sessions loading spinner shown on mount', async () => {
+ server.use(
+ http.get('/api/admin/oauth-sessions', async () => {
+ await new Promise(resolve => setTimeout(resolve, 200));
+ return HttpResponse.json({ sessions: [] });
+ })
+ );
+ render( );
+ expect(document.querySelector('.animate-spin')).toBeInTheDocument();
+ });
+
+ it('FE-ADMIN-MCP-012: OAuth sessions empty state rendered when no sessions', async () => {
+ server.use(
+ http.get('/api/admin/oauth-sessions', () =>
+ HttpResponse.json({ sessions: [] })
+ )
+ );
+ render( );
+ await screen.findByText('No active OAuth sessions');
+ });
+
+ it('FE-ADMIN-MCP-013: OAuth sessions list renders with scopes', async () => {
+ server.use(
+ http.get('/api/admin/oauth-sessions', () =>
+ HttpResponse.json({
+ sessions: [
+ {
+ id: 1,
+ client_name: 'Claude Desktop',
+ username: 'alice',
+ scopes: ['trips:read', 'budget:read'],
+ created_at: '2025-01-01T00:00:00Z',
+ },
+ ],
+ })
+ )
+ );
+ render( );
+ await screen.findByText('Claude Desktop');
+ expect(screen.getByText('alice')).toBeInTheDocument();
+ expect(screen.getByText('trips:read')).toBeInTheDocument();
+ });
+
+ it('FE-ADMIN-MCP-014: scope expand/collapse toggle shows hidden scopes', async () => {
+ const user = userEvent.setup();
+ // 7 scopes — more than SCOPES_PREVIEW=6, so "+1 more" button appears
+ const scopes = ['trips:read', 'trips:write', 'places:read', 'places:write', 'budget:read', 'budget:write', 'packing:read'];
+ server.use(
+ http.get('/api/admin/oauth-sessions', () =>
+ HttpResponse.json({
+ sessions: [
+ { id: 1, client_name: 'App', username: 'bob', scopes, created_at: '2025-01-01T00:00:00Z' },
+ ],
+ })
+ )
+ );
+ render( );
+ await screen.findByText('App');
+ // "+1 more" button should appear
+ const moreBtn = await screen.findByText(/\+1 more/);
+ expect(moreBtn).toBeInTheDocument();
+ await user.click(moreBtn);
+ // After expand, "show less" appears
+ expect(await screen.findByText('show less')).toBeInTheDocument();
+ });
+
+ it('FE-ADMIN-MCP-015: revoke session confirmation and successful revoke', async () => {
+ const user = userEvent.setup();
+ server.use(
+ http.get('/api/admin/oauth-sessions', () =>
+ HttpResponse.json({
+ sessions: [
+ { id: 5, client_name: 'Revoke Me', username: 'carol', scopes: ['trips:read'], created_at: '2025-01-01T00:00:00Z' },
+ ],
+ })
+ ),
+ http.delete('/api/admin/oauth-sessions/5', () =>
+ HttpResponse.json({ success: true })
+ )
+ );
+ render(<> >);
+ await screen.findByText('Revoke Me');
+
+ // Click the revoke (trash) button next to the session
+ const deleteBtn = screen.getAllByTitle('Delete')[0];
+ await user.click(deleteBtn);
+
+ // Confirmation modal opens
+ expect(screen.getByText('Revoke Session')).toBeInTheDocument();
+ // Confirm — find the modal's Delete button (has no title, unlike the trash icon)
+ const deleteBtns = screen.getAllByRole('button', { name: 'Delete' });
+ const confirmBtn = deleteBtns.find(b => !b.title);
+ await user.click(confirmBtn ?? deleteBtns[deleteBtns.length - 1]);
+ await waitFor(() => {
+ expect(screen.queryByText('Revoke Me')).not.toBeInTheDocument();
+ });
+ });
+
+ it('FE-ADMIN-MCP-016: revoke session error shows toast', async () => {
+ const user = userEvent.setup();
+ server.use(
+ http.get('/api/admin/oauth-sessions', () =>
+ HttpResponse.json({
+ sessions: [
+ { id: 6, client_name: 'Error Session', username: 'dave', scopes: ['trips:read'], created_at: '2025-01-01T00:00:00Z' },
+ ],
+ })
+ ),
+ http.delete('/api/admin/oauth-sessions/6', () =>
+ HttpResponse.json({ error: 'forbidden' }, { status: 403 })
+ )
+ );
+ render(<> >);
+ await screen.findByText('Error Session');
+
+ const deleteBtn = screen.getAllByTitle('Delete')[0];
+ await user.click(deleteBtn);
+ const deleteBtns = screen.getAllByRole('button', { name: 'Delete' });
+ const confirmBtn = deleteBtns.find(b => !b.title);
+ await user.click(confirmBtn ?? deleteBtns[deleteBtns.length - 1]);
+ await screen.findByText('Failed to revoke session');
+ });
});
diff --git a/client/src/components/Admin/AdminMcpTokensPanel.tsx b/client/src/components/Admin/AdminMcpTokensPanel.tsx
index 8a89f92d..7173ae9c 100644
--- a/client/src/components/Admin/AdminMcpTokensPanel.tsx
+++ b/client/src/components/Admin/AdminMcpTokensPanel.tsx
@@ -1,9 +1,21 @@
import { useState, useEffect } from 'react'
import { adminApi } from '../../api/client'
import { useToast } from '../shared/Toast'
-import { Key, Trash2, User, Loader2 } from 'lucide-react'
+import { Key, Trash2, User, Loader2, Shield } from 'lucide-react'
import { useTranslation } from '../../i18n'
+interface AdminOAuthSession {
+ id: number
+ client_id: string
+ client_name: string
+ user_id: number
+ username: string
+ scopes: string[]
+ access_token_expires_at: string
+ refresh_token_expires_at: string
+ created_at: string
+}
+
interface AdminMcpToken {
id: number
name: string
@@ -14,21 +26,49 @@ interface AdminMcpToken {
username: string
}
+const SCOPES_PREVIEW = 6
+
export default function AdminMcpTokensPanel() {
+ const [sessions, setSessions] = useState([])
+ const [sessionsLoading, setSessionsLoading] = useState(true)
const [tokens, setTokens] = useState([])
- const [isLoading, setIsLoading] = useState(true)
+ const [tokensLoading, setTokensLoading] = useState(true)
+ const [expandedScopes, setExpandedScopes] = useState>(new Set())
+ const [revokeConfirmId, setRevokeConfirmId] = useState(null)
const [deleteConfirmId, setDeleteConfirmId] = useState(null)
+
+ const toggleScopes = (id: number) =>
+ setExpandedScopes(prev => {
+ const next = new Set(prev)
+ next.has(id) ? next.delete(id) : next.add(id)
+ return next
+ })
const toast = useToast()
const { t, locale } = useTranslation()
useEffect(() => {
- setIsLoading(true)
+ adminApi.oauthSessions()
+ .then(d => setSessions(d.sessions || []))
+ .catch(() => toast.error(t('admin.oauthSessions.loadError')))
+ .finally(() => setSessionsLoading(false))
+
adminApi.mcpTokens()
.then(d => setTokens(d.tokens || []))
.catch(() => toast.error(t('admin.mcpTokens.loadError')))
- .finally(() => setIsLoading(false))
+ .finally(() => setTokensLoading(false))
}, [])
+ const handleRevoke = async (id: number) => {
+ try {
+ await adminApi.revokeOAuthSession(id)
+ setSessions(prev => prev.filter(s => s.id !== id))
+ setRevokeConfirmId(null)
+ toast.success(t('admin.oauthSessions.revokeSuccess'))
+ } catch {
+ toast.error(t('admin.oauthSessions.revokeError'))
+ }
+ }
+
const handleDelete = async (id: number) => {
try {
await adminApi.deleteMcpToken(id)
@@ -47,55 +87,156 @@ export default function AdminMcpTokensPanel() {
{t('admin.mcpTokens.subtitle')}
-
- {isLoading ? (
-
-
-
- ) : tokens.length === 0 ? (
-
-
-
{t('admin.mcpTokens.empty')}
-
- ) : (
- <>
-
-
{t('admin.mcpTokens.tokenName')}
-
{t('admin.mcpTokens.owner')}
-
{t('admin.mcpTokens.created')}
-
{t('admin.mcpTokens.lastUsed')}
-
+ {/* OAuth Sessions */}
+
+
{t('admin.oauthSessions.sectionTitle')}
+
+ {sessionsLoading ? (
+
+
- {tokens.map((token, i) => (
-
-
-
{token.name}
-
{token.token_prefix}...
-
-
-
- {token.username}
-
-
- {new Date(token.created_at).toLocaleDateString(locale)}
-
-
- {token.last_used_at ? new Date(token.last_used_at).toLocaleDateString(locale) : t('admin.mcpTokens.never')}
-
-
setDeleteConfirmId(token.id)}
- className="p-1.5 rounded-lg transition-colors hover:bg-red-50 hover:text-red-600 dark:hover:bg-red-900/20"
- style={{ color: 'var(--text-tertiary)' }} title={t('common.delete')}>
-
-
+ ) : sessions.length === 0 ? (
+
+
+
{t('admin.oauthSessions.empty')}
+
+ ) : (
+ <>
+
+ {t('admin.oauthSessions.clientName')}
+ {t('admin.oauthSessions.owner')}
+ {t('admin.oauthSessions.created')}
+
- ))}
- >
- )}
+ {sessions.map((session, i) => {
+ const expanded = expandedScopes.has(session.id)
+ const visible = expanded ? session.scopes : session.scopes.slice(0, SCOPES_PREVIEW)
+ const hidden = session.scopes.length - SCOPES_PREVIEW
+ return (
+
+
+
{session.client_name}
+
+ {visible.map(scope => (
+
+ {scope}
+
+ ))}
+ {!expanded && hidden > 0 && (
+ toggleScopes(session.id)}
+ className="inline-flex items-center px-1.5 py-0.5 rounded text-xs font-medium transition-colors hover:opacity-80"
+ style={{ background: 'var(--bg-secondary)', color: 'var(--text-secondary)', border: '1px solid var(--border-primary)' }}>
+ +{hidden} more
+
+ )}
+ {expanded && hidden > 0 && (
+ toggleScopes(session.id)}
+ className="inline-flex items-center px-1.5 py-0.5 rounded text-xs font-medium transition-colors hover:opacity-80"
+ style={{ background: 'var(--bg-secondary)', color: 'var(--text-secondary)', border: '1px solid var(--border-primary)' }}>
+ show less
+
+ )}
+
+
+
+
+ {session.username}
+
+
+ {new Date(session.created_at).toLocaleDateString(locale)}
+
+
setRevokeConfirmId(session.id)}
+ className="p-1.5 rounded-lg transition-colors hover:bg-red-50 hover:text-red-600 dark:hover:bg-red-900/20"
+ style={{ color: 'var(--text-tertiary)' }} title={t('common.delete')}>
+
+
+
+ )
+ })}
+ >
+ )}
+
+ {/* MCP Tokens */}
+
+
{t('admin.mcpTokens.sectionTitle')}
+
+ {tokensLoading ? (
+
+
+
+ ) : tokens.length === 0 ? (
+
+
+
{t('admin.mcpTokens.empty')}
+
+ ) : (
+ <>
+
+ {t('admin.mcpTokens.tokenName')}
+ {t('admin.mcpTokens.owner')}
+ {t('admin.mcpTokens.created')}
+ {t('admin.mcpTokens.lastUsed')}
+
+
+ {tokens.map((token, i) => (
+
+
+
{token.name}
+
{token.token_prefix}...
+
+
+
+ {token.username}
+
+
+ {new Date(token.created_at).toLocaleDateString(locale)}
+
+
+ {token.last_used_at ? new Date(token.last_used_at).toLocaleDateString(locale) : t('admin.mcpTokens.never')}
+
+
setDeleteConfirmId(token.id)}
+ className="p-1.5 rounded-lg transition-colors hover:bg-red-50 hover:text-red-600 dark:hover:bg-red-900/20"
+ style={{ color: 'var(--text-tertiary)' }} title={t('common.delete')}>
+
+
+
+ ))}
+ >
+ )}
+
+
+
+ {/* Revoke OAuth session modal */}
+ {revokeConfirmId !== null && (
+
{ if (e.target === e.currentTarget) setRevokeConfirmId(null) }}>
+
+
{t('admin.oauthSessions.revokeTitle')}
+
{t('admin.oauthSessions.revokeMessage')}
+
+ setRevokeConfirmId(null)}
+ className="px-4 py-2 rounded-lg text-sm border" style={{ borderColor: 'var(--border-primary)', color: 'var(--text-secondary)' }}>
+ {t('common.cancel')}
+
+ handleRevoke(revokeConfirmId)}
+ className="px-4 py-2 rounded-lg text-sm font-medium text-white bg-red-600 hover:bg-red-700">
+ {t('common.delete')}
+
+
+
+
+ )}
+
+ {/* Delete MCP token modal */}
{deleteConfirmId !== null && (
{ if (e.target === e.currentTarget) setDeleteConfirmId(null) }}>
diff --git a/client/src/components/Budget/BudgetPanel.test.tsx b/client/src/components/Budget/BudgetPanel.test.tsx
index 4a48d9ba..c912d651 100644
--- a/client/src/components/Budget/BudgetPanel.test.tsx
+++ b/client/src/components/Budget/BudgetPanel.test.tsx
@@ -1,4 +1,4 @@
-// FE-COMP-BUDGET-001 to FE-COMP-BUDGET-020
+// FE-COMP-BUDGET-001 to FE-COMP-BUDGET-040
import { render, screen, waitFor } from '../../../tests/helpers/render';
import userEvent from '@testing-library/user-event';
import { http, HttpResponse } from 'msw';
@@ -6,6 +6,7 @@ import { server } from '../../../tests/helpers/msw/server';
import { useAuthStore } from '../../store/authStore';
import { useTripStore } from '../../store/tripStore';
import { useSettingsStore } from '../../store/settingsStore';
+import { usePermissionsStore } from '../../store/permissionsStore';
import { resetAllStores, seedStore } from '../../../tests/helpers/store';
import { buildUser, buildTrip, buildBudgetItem, buildSettings } from '../../../tests/helpers/factories';
import BudgetPanel from './BudgetPanel';
@@ -418,4 +419,80 @@ describe('BudgetPanel', () => {
// Grand total card shows 300.00
expect(screen.getByText('300.00')).toBeInTheDocument();
});
+
+ it('FE-COMP-BUDGET-033: read-only mode hides add/delete/edit controls', async () => {
+ // Restrict budget_edit to trip owners only; user is not the owner (owner_id=1, user.id > 1)
+ seedStore(usePermissionsStore, { permissions: { budget_edit: 'trip_owner' } });
+ // Use a user with id != 1 so they're not the owner
+ seedStore(useAuthStore, { user: buildUser(), isAuthenticated: true });
+ seedStore(useTripStore, { trip: buildTrip({ id: 1, owner_id: 9999 }) });
+ const item = { ...buildBudgetItem({ trip_id: 1, category: 'Food', name: 'Read Only Item' }), total_price: 50 };
+ server.use(
+ http.get('/api/trips/1/budget', () => HttpResponse.json({ items: [item] }))
+ );
+ render(
);
+ await screen.findByText('Read Only Item');
+ // In read-only mode the Delete button should not be visible
+ expect(screen.queryByTitle('Delete')).not.toBeInTheDocument();
+ });
+
+ it('FE-COMP-BUDGET-034: read-only mode shows expense_date as text span', async () => {
+ seedStore(usePermissionsStore, { permissions: { budget_edit: 'trip_owner' } });
+ seedStore(useAuthStore, { user: buildUser(), isAuthenticated: true });
+ seedStore(useTripStore, { trip: buildTrip({ id: 1, owner_id: 9999 }) });
+ const item = { ...buildBudgetItem({ trip_id: 1, category: 'Transport', name: 'Train' }), total_price: 30, expense_date: '2025-06-15' };
+ server.use(
+ http.get('/api/trips/1/budget', () => HttpResponse.json({ items: [item] }))
+ );
+ render(
);
+ await screen.findByText('Train');
+ // expense_date is rendered as plain text in read-only mode
+ await screen.findByText('2025-06-15');
+ });
+
+ it('FE-COMP-BUDGET-035: settlement section with avatar renders user avatar image', async () => {
+ const user = userEvent.setup();
+ const item = { ...buildBudgetItem({ trip_id: 1, category: 'Food', name: 'Lunch' }), total_price: 60 };
+ server.use(
+ http.get('/api/trips/1/budget', () => HttpResponse.json({ items: [item] })),
+ http.get('/api/trips/1/budget/settlement', () =>
+ HttpResponse.json({
+ balances: [
+ { user_id: 1, username: 'alice', avatar_url: '/uploads/avatars/alice.jpg', balance: -30 },
+ { user_id: 2, username: 'bob', avatar_url: null, balance: 30 },
+ ],
+ flows: [{ from: { username: 'alice', avatar_url: '/uploads/avatars/alice.jpg' }, to: { username: 'bob', avatar_url: null }, amount: 30 }]
+ })
+ ),
+ http.get('/api/trips/1/budget/per-person', () => HttpResponse.json({ summary: [] })),
+ );
+ const tripMembers = [
+ { id: 1, username: 'alice', avatar_url: '/uploads/avatars/alice.jpg' },
+ { id: 2, username: 'bob', avatar_url: null },
+ ];
+ render(
);
+ await screen.findByText('Lunch');
+ // Trigger settlement display
+ const settlementBtn = await screen.findByRole('button', { name: /settlement/i });
+ await user.click(settlementBtn);
+ await screen.findByText('alice');
+ // Avatar image should be rendered for alice
+ const avatarImg = screen.getAllByRole('img');
+ expect(avatarImg.length).toBeGreaterThan(0);
+ });
+
+ it('FE-COMP-BUDGET-036: expense_date shows dash when not set in read-only mode', async () => {
+ seedStore(usePermissionsStore, { permissions: { budget_edit: 'trip_owner' } });
+ seedStore(useAuthStore, { user: buildUser(), isAuthenticated: true });
+ seedStore(useTripStore, { trip: buildTrip({ id: 1, owner_id: 9999 }) });
+ const item = { ...buildBudgetItem({ trip_id: 1, category: 'Food', name: 'Snack' }), total_price: 5, expense_date: null };
+ server.use(
+ http.get('/api/trips/1/budget', () => HttpResponse.json({ items: [item] }))
+ );
+ render(
);
+ await screen.findByText('Snack');
+ // When expense_date is null, the fallback '—' is shown
+ const dashes = screen.getAllByText('—');
+ expect(dashes.length).toBeGreaterThan(0);
+ });
});
diff --git a/client/src/components/Collab/CollabChat.tsx b/client/src/components/Collab/CollabChat.tsx
index ab6779c0..bba42f4c 100644
--- a/client/src/components/Collab/CollabChat.tsx
+++ b/client/src/components/Collab/CollabChat.tsx
@@ -370,6 +370,11 @@ export default function CollabChat({ tripId, currentUser }: CollabChatProps) {
const [showEmoji, setShowEmoji] = useState(false)
const [reactMenu, setReactMenu] = useState(null) // { msgId, x, y }
const [deletingIds, setDeletingIds] = useState(new Set())
+ const deleteTimersRef = useRef
[]>([])
+
+ useEffect(() => {
+ return () => { deleteTimersRef.current.forEach(clearTimeout) }
+ }, [])
const containerRef = useRef(null)
const messagesRef = useRef(messages)
@@ -483,13 +488,14 @@ export default function CollabChat({ tripId, currentUser }: CollabChatProps) {
requestAnimationFrame(() => {
setDeletingIds(prev => new Set(prev).add(msgId))
})
- setTimeout(async () => {
+ const t = setTimeout(async () => {
try {
await collabApi.deleteMessage(tripId, msgId)
setMessages(prev => prev.map(m => m.id === msgId ? { ...m, _deleted: true } : m))
} catch {}
setDeletingIds(prev => { const s = new Set(prev); s.delete(msgId); return s })
}, 400)
+ deleteTimersRef.current.push(t)
}, [tripId])
const handleReact = useCallback(async (msgId, emoji) => {
diff --git a/client/src/components/Collab/WhatsNextWidget.tsx b/client/src/components/Collab/WhatsNextWidget.tsx
index c5fd11a2..90d39caf 100644
--- a/client/src/components/Collab/WhatsNextWidget.tsx
+++ b/client/src/components/Collab/WhatsNextWidget.tsx
@@ -16,12 +16,13 @@ function formatTime(timeStr, is12h) {
}
function formatDayLabel(date, t, locale) {
- const d = new Date(date + 'T00:00:00')
const now = new Date()
- const tomorrow = new Date(); tomorrow.setDate(now.getDate() + 1)
+ const nowDate = now.toISOString().split('T')[0]
+ const tomorrowUtc = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate() + 1))
+ const tomorrowDate = tomorrowUtc.toISOString().split('T')[0]
- if (d.toDateString() === now.toDateString()) return t('collab.whatsNext.today') || 'Today'
- if (d.toDateString() === tomorrow.toDateString()) return t('collab.whatsNext.tomorrow') || 'Tomorrow'
+ if (date === nowDate) return t('collab.whatsNext.today') || 'Today'
+ if (date === tomorrowDate) return t('collab.whatsNext.tomorrow') || 'Tomorrow'
return new Date(date + 'T00:00:00Z').toLocaleDateString(locale || undefined, { weekday: 'short', day: 'numeric', month: 'short', timeZone: 'UTC' })
}
diff --git a/client/src/components/Memories/MemoriesPanel.tsx b/client/src/components/Memories/MemoriesPanel.tsx
index baed3cd3..ec14ab25 100644
--- a/client/src/components/Memories/MemoriesPanel.tsx
+++ b/client/src/components/Memories/MemoriesPanel.tsx
@@ -714,6 +714,23 @@ export default function MemoriesPanel({ tripId, startDate, endDate }: MemoriesPa
return (
+ {/* Disconnected banner — shown when photos exist but provider is unreachable */}
+ {!connected && allVisible.length > 0 && enabledProviders.length > 0 && (
+
+
+
+ {t('memories.providerDisconnectedBanner', {
+ provider_name: enabledProviders.length === 1 ? enabledProviders[0].name : enabledProviders.map(p => p.name).join(', ')
+ })}
+
+
+ )}
+
{/* Header */}
diff --git a/client/src/components/Notifications/InAppNotificationItem.test.tsx b/client/src/components/Notifications/InAppNotificationItem.test.tsx
index f8ac1081..1eb024bc 100644
--- a/client/src/components/Notifications/InAppNotificationItem.test.tsx
+++ b/client/src/components/Notifications/InAppNotificationItem.test.tsx
@@ -1,4 +1,4 @@
-// FE-COMP-NOTIF-001 to FE-COMP-NOTIF-010
+// FE-COMP-NOTIF-001 to FE-COMP-NOTIF-016
import { render, screen, waitFor } from '../../../tests/helpers/render';
import userEvent from '@testing-library/user-event';
import { useAuthStore } from '../../store/authStore';
@@ -99,4 +99,109 @@ describe('InAppNotificationItem', () => {
// Recent notification shows "just now"
expect(screen.getByText('just now')).toBeInTheDocument();
});
+
+ it('FE-COMP-NOTIF-011: shows avatar image when sender_avatar is provided', () => {
+ render(
+
+ );
+ expect(document.querySelector('img')).toBeInTheDocument();
+ expect(document.querySelector('img')?.getAttribute('src')).toBe('https://example.com/avatar.png');
+ });
+
+ it('FE-COMP-NOTIF-012: boolean notification shows Accept and Reject buttons', () => {
+ render(
+
+ );
+ expect(screen.getByText('Yes')).toBeInTheDocument();
+ expect(screen.getByText('No')).toBeInTheDocument();
+ });
+
+ it('FE-COMP-NOTIF-013: clicking Accept calls respondToBoolean with positive', async () => {
+ const user = userEvent.setup();
+ const respondToBoolean = vi.fn().mockResolvedValue(undefined);
+ seedStore(useInAppNotificationStore, { respondToBoolean });
+ render(
+
+ );
+ await user.click(screen.getByText('Yes'));
+ expect(respondToBoolean).toHaveBeenCalledWith(55, 'positive');
+ });
+
+ it('FE-COMP-NOTIF-014: clicking Reject calls respondToBoolean with negative', async () => {
+ const user = userEvent.setup();
+ const respondToBoolean = vi.fn().mockResolvedValue(undefined);
+ seedStore(useInAppNotificationStore, { respondToBoolean });
+ render(
+
+ );
+ await user.click(screen.getByText('No'));
+ expect(respondToBoolean).toHaveBeenCalledWith(66, 'negative');
+ });
+
+ it('FE-COMP-NOTIF-015: navigate notification shows action button', () => {
+ render(
+
+ );
+ // t('notifications.title') = "Notifications" — the navigate button renders this
+ const navigateBtn = document.querySelector('button[style*="pointer"]') ??
+ Array.from(document.querySelectorAll('button')).find(b => b.textContent?.includes('Notifications'));
+ expect(navigateBtn).toBeInTheDocument();
+ });
+
+ it('FE-COMP-NOTIF-016: clicking navigate button marks read and navigates', async () => {
+ const user = userEvent.setup();
+ const markRead = vi.fn().mockResolvedValue(undefined);
+ const onClose = vi.fn();
+ seedStore(useInAppNotificationStore, { markRead });
+ render(
+
+ );
+ // The navigate button renders t('notifications.title') = "Notifications"
+ const btn = Array.from(document.querySelectorAll('button')).find(
+ b => b.textContent?.includes('Notifications')
+ );
+ expect(btn).toBeTruthy();
+ await user.click(btn!);
+ expect(markRead).toHaveBeenCalledWith(77);
+ expect(onClose).toHaveBeenCalled();
+ });
});
diff --git a/client/src/components/OAuth/ScopeGroupPicker.test.tsx b/client/src/components/OAuth/ScopeGroupPicker.test.tsx
new file mode 100644
index 00000000..1dde39e7
--- /dev/null
+++ b/client/src/components/OAuth/ScopeGroupPicker.test.tsx
@@ -0,0 +1,119 @@
+// FE-COMP-SCOPE-001 to FE-COMP-SCOPE-009
+import { render, screen, waitFor } from '../../../tests/helpers/render';
+import userEvent from '@testing-library/user-event';
+import { resetAllStores } from '../../../tests/helpers/store';
+import ScopeGroupPicker from './ScopeGroupPicker';
+
+beforeEach(() => {
+ resetAllStores();
+});
+
+describe('ScopeGroupPicker', () => {
+ it('FE-COMP-SCOPE-001: renders scope groups', () => {
+ render(
);
+ // Several group headers should be visible
+ expect(screen.getAllByRole('button').length).toBeGreaterThan(0);
+ });
+
+ it('FE-COMP-SCOPE-002: shows Select All button when nothing selected', () => {
+ render(
);
+ expect(screen.getByRole('button', { name: /select all/i })).toBeInTheDocument();
+ });
+
+ it('FE-COMP-SCOPE-003: Select All calls onChange with all scopes', async () => {
+ const user = userEvent.setup();
+ const onChange = vi.fn();
+ render(
);
+ await user.click(screen.getByRole('button', { name: /select all/i }));
+ expect(onChange).toHaveBeenCalledTimes(1);
+ const called = onChange.mock.calls[0][0] as string[];
+ expect(called.length).toBeGreaterThan(0);
+ });
+
+ it('FE-COMP-SCOPE-004: shows Deselect All button when all selected', async () => {
+ // First collect all scopes by clicking Select All and capturing the callback
+ const user = userEvent.setup();
+ const captured: string[][] = [];
+ const { rerender } = render(
+
captured.push(s)} />
+ );
+ await user.click(screen.getByRole('button', { name: /select all/i }));
+ const allScopes = captured[0];
+
+ // Now rerender with all scopes selected
+ rerender( );
+ expect(screen.getByRole('button', { name: /deselect all/i })).toBeInTheDocument();
+ });
+
+ it('FE-COMP-SCOPE-005: Deselect All calls onChange with empty array', async () => {
+ const user = userEvent.setup();
+ const captured: string[][] = [];
+
+ // Get all scopes first
+ const { rerender } = render(
+ captured.push(s)} />
+ );
+ await user.click(screen.getByRole('button', { name: /select all/i }));
+ const allScopes = captured[0];
+
+ const onChange = vi.fn();
+ rerender( );
+ await user.click(screen.getByRole('button', { name: /deselect all/i }));
+ expect(onChange).toHaveBeenCalledWith([]);
+ });
+
+ it('FE-COMP-SCOPE-006: expanding a group reveals individual scope checkboxes', async () => {
+ const user = userEvent.setup();
+ render( );
+
+ // Groups are collapsed by default — checkboxes for individual scopes not visible
+ const groupToggles = screen.getAllByRole('button').filter(b =>
+ !b.textContent?.toLowerCase().includes('select all') &&
+ !b.textContent?.toLowerCase().includes('deselect all')
+ );
+ // Click the first group expand button
+ await user.click(groupToggles[0]);
+ // Individual scope checkboxes should now appear (more than just group-level ones)
+ const checkboxes = screen.getAllByRole('checkbox');
+ expect(checkboxes.length).toBeGreaterThan(0);
+ });
+
+ it('FE-COMP-SCOPE-007: group checkbox selects all scopes in the group', async () => {
+ const user = userEvent.setup();
+ const onChange = vi.fn();
+ render( );
+
+ const groupCheckboxes = screen.getAllByRole('checkbox');
+ await user.click(groupCheckboxes[0]);
+ expect(onChange).toHaveBeenCalledTimes(1);
+ const called = onChange.mock.calls[0][0] as string[];
+ expect(called.length).toBeGreaterThan(0);
+ });
+
+ it('FE-COMP-SCOPE-008: individual scope toggle adds/removes that scope', async () => {
+ const user = userEvent.setup();
+ const onChange = vi.fn();
+ render( );
+
+ // Expand first group
+ const groupToggles = screen.getAllByRole('button').filter(b =>
+ !b.textContent?.toLowerCase().includes('select all') &&
+ !b.textContent?.toLowerCase().includes('deselect all')
+ );
+ await user.click(groupToggles[0]);
+
+ // There are now individual scope checkboxes — click the second one (first is group-level)
+ const checkboxes = screen.getAllByRole('checkbox');
+ await user.click(checkboxes[1]); // individual scope
+ expect(onChange).toHaveBeenCalledTimes(1);
+ });
+
+ it('FE-COMP-SCOPE-009: count badge shown when some scopes selected in group', () => {
+ // Get any single scope key from the first group via Select All trick + manual slice
+ // We'll just select a scope by triggering group checkbox and passing it in
+ const firstGroupScope = 'trips:read'; // known scope from SCOPE_GROUPS
+ render( );
+ // Count badge like "(1/N)" should be visible
+ expect(screen.getByText(/\(\d+\/\d+\)/)).toBeInTheDocument();
+ });
+});
diff --git a/client/src/components/OAuth/ScopeGroupPicker.tsx b/client/src/components/OAuth/ScopeGroupPicker.tsx
new file mode 100644
index 00000000..aa69828b
--- /dev/null
+++ b/client/src/components/OAuth/ScopeGroupPicker.tsx
@@ -0,0 +1,96 @@
+import React, { useState } from 'react'
+import { ChevronDown, ChevronRight } from 'lucide-react'
+import { getScopesByGroup } from '../../api/oauthScopes'
+import { useTranslation } from '../../i18n'
+
+interface Props {
+ selected: string[]
+ onChange: (scopes: string[]) => void
+}
+
+export default function ScopeGroupPicker({ selected, onChange }: Props): React.ReactElement {
+ const { t } = useTranslation()
+ const [open, setOpen] = useState>({})
+
+ const scopesByGroup = getScopesByGroup(t)
+ const allScopeKeys = Object.values(scopesByGroup).flat().map(s => s.scope)
+ const allSelected = allScopeKeys.every(s => selected.includes(s))
+
+ return (
+
+
+ onChange(allSelected ? [] : allScopeKeys)}
+ className="text-xs px-2 py-0.5 rounded border transition-colors hover:bg-slate-100 dark:hover:bg-slate-700"
+ style={{ borderColor: 'var(--border-primary)', color: 'var(--text-secondary)' }}>
+ {allSelected ? t('settings.oauth.modal.deselectAll') : t('settings.oauth.modal.selectAll')}
+
+
+
+ {Object.entries(scopesByGroup).map(([group, groupScopes]) => {
+ const groupScopeKeys = groupScopes.map(s => s.scope)
+ const allGroupSelected = groupScopeKeys.every(s => selected.includes(s))
+ const someGroupSelected = groupScopeKeys.some(s => selected.includes(s))
+ return (
+
+
+ setOpen(prev => ({ ...prev, [group]: !prev[group] }))}
+ className="flex items-center gap-1 flex-1 text-xs font-semibold hover:opacity-70 transition-opacity text-left"
+ style={{ color: 'var(--text-secondary)' }}>
+ {open[group]
+ ?
+ : }
+ {group}
+ {someGroupSelected && (
+
+ ({groupScopeKeys.filter(s => selected.includes(s)).length}/{groupScopeKeys.length})
+
+ )}
+
+ { if (el) el.indeterminate = someGroupSelected && !allGroupSelected }}
+ onChange={e => onChange(
+ e.target.checked
+ ? [...new Set([...selected, ...groupScopeKeys])]
+ : selected.filter(s => !groupScopeKeys.includes(s))
+ )}
+ className="rounded"
+ title={allGroupSelected ? `Deselect all ${group}` : `Select all ${group}`}
+ />
+
+ {open[group] && (
+
+ {groupScopes.map(({ scope, label, description }) => (
+
+ onChange(
+ e.target.checked
+ ? [...selected, scope]
+ : selected.filter(s => s !== scope)
+ )}
+ className="mt-0.5 rounded flex-shrink-0"
+ />
+
+
{label}
+
{description}
+
+
+ ))}
+
+ )}
+
+ )
+ })}
+
+
+ )
+}
diff --git a/client/src/components/Planner/PlacesSidebar.test.tsx b/client/src/components/Planner/PlacesSidebar.test.tsx
index ba1557e6..79b52c17 100644
--- a/client/src/components/Planner/PlacesSidebar.test.tsx
+++ b/client/src/components/Planner/PlacesSidebar.test.tsx
@@ -5,6 +5,7 @@ import { http, HttpResponse } from 'msw';
import { useAuthStore } from '../../store/authStore';
import { useTripStore } from '../../store/tripStore';
import { usePermissionsStore } from '../../store/permissionsStore';
+import { placesApi } from '../../api/client';
import { resetAllStores, seedStore } from '../../../tests/helpers/store';
import { buildUser, buildTrip, buildPlace, buildCategory, buildDay, buildAssignment } from '../../../tests/helpers/factories';
import { server } from '../../../tests/helpers/msw/server';
@@ -443,11 +444,8 @@ describe('GPX import', () => {
});
it('FE-PLANNER-SIDEBAR-039: successful GPX import shows success toast', async () => {
- server.use(
- http.post('/api/trips/1/places/import/gpx', () =>
- HttpResponse.json({ count: 2, places: [{ id: 10 }, { id: 11 }] })
- ),
- );
+ // FormData POST hangs on CI — mock at the API boundary instead of MSW.
+ const importSpy = vi.spyOn(placesApi, 'importGpx').mockResolvedValueOnce({ count: 2, places: [{ id: 10 }, { id: 11 }] });
const loadTrip = vi.fn().mockResolvedValue(undefined);
seedStore(useTripStore, { loadTrip });
const addToast = vi.fn();
@@ -465,6 +463,7 @@ describe('GPX import', () => {
undefined,
);
});
+ importSpy.mockRestore();
});
});
diff --git a/client/src/components/Settings/IntegrationsTab.test.tsx b/client/src/components/Settings/IntegrationsTab.test.tsx
index 84eeb161..7170da6e 100644
--- a/client/src/components/Settings/IntegrationsTab.test.tsx
+++ b/client/src/components/Settings/IntegrationsTab.test.tsx
@@ -1,4 +1,4 @@
-// FE-COMP-INTEGRATIONS-001 to FE-COMP-INTEGRATIONS-018
+// FE-COMP-INTEGRATIONS-001 to FE-COMP-INTEGRATIONS-032
import { render, screen, waitFor } from '../../../tests/helpers/render';
import userEvent from '@testing-library/user-event';
import { http, HttpResponse } from 'msw';
@@ -7,6 +7,7 @@ import { useAuthStore } from '../../store/authStore';
import { useAddonStore } from '../../store/addonStore';
import { resetAllStores, seedStore } from '../../../tests/helpers/store';
import { buildUser } from '../../../tests/helpers/factories';
+import { ToastContainer } from '../shared/Toast';
import IntegrationsTab from './IntegrationsTab';
function enableMcp() {
@@ -40,6 +41,8 @@ beforeEach(() => {
server.use(
http.get('/api/auth/mcp-tokens', () => HttpResponse.json({ tokens: [] })),
http.get('/api/addons', () => HttpResponse.json({ addons: [] })),
+ http.get('/api/oauth/clients', () => HttpResponse.json({ clients: [] })),
+ http.get('/api/oauth/sessions', () => HttpResponse.json({ sessions: [] })),
);
});
@@ -69,18 +72,26 @@ describe('IntegrationsTab', () => {
expect(codeEl!.textContent).toContain('/mcp');
});
- it('FE-COMP-INTEGRATIONS-005: JSON config block is rendered', async () => {
+ it('FE-COMP-INTEGRATIONS-005: JSON config block is rendered when expanded', async () => {
+ const user = userEvent.setup();
enableMcp();
render( );
await screen.findByText('MCP Configuration');
+ // Config is collapsed by default — no yet
+ expect(document.querySelector('pre')).toBeNull();
+ // Expand by clicking the "Client Configuration" toggle
+ await user.click(screen.getByRole('button', { name: /Client Configuration/i }));
const preEl = document.querySelector('pre');
expect(preEl).not.toBeNull();
expect(preEl!.textContent).toContain('mcpServers');
});
it('FE-COMP-INTEGRATIONS-006: "no tokens" message shown when token list is empty', async () => {
+ const user = userEvent.setup();
enableMcp();
render( );
+ await screen.findByText('MCP Configuration');
+ await user.click(screen.getByRole('button', { name: /API Tokens/i }));
await screen.findByText('No tokens yet. Create one to connect MCP clients.');
});
@@ -95,8 +106,11 @@ describe('IntegrationsTab', () => {
}),
),
);
+ const user = userEvent.setup();
enableMcp();
render( );
+ await screen.findByText('MCP Configuration');
+ await user.click(screen.getByRole('button', { name: /API Tokens/i }));
await screen.findByText('My Token');
await screen.findByText('Other Token');
});
@@ -106,6 +120,7 @@ describe('IntegrationsTab', () => {
enableMcp();
render( );
await screen.findByText('MCP Configuration');
+ await user.click(screen.getByRole('button', { name: /API Tokens/i }));
const createBtn = screen.getByRole('button', { name: /Create New Token/i });
await user.click(createBtn);
await screen.findByText('Create API Token');
@@ -116,6 +131,7 @@ describe('IntegrationsTab', () => {
enableMcp();
render( );
await screen.findByText('MCP Configuration');
+ await user.click(screen.getByRole('button', { name: /API Tokens/i }));
await user.click(screen.getByRole('button', { name: /Create New Token/i }));
await screen.findByText('Create API Token');
const modalCreateBtn = screen.getByRole('button', { name: /^Create Token$/i });
@@ -127,6 +143,7 @@ describe('IntegrationsTab', () => {
enableMcp();
render( );
await screen.findByText('MCP Configuration');
+ await user.click(screen.getByRole('button', { name: /API Tokens/i }));
await user.click(screen.getByRole('button', { name: /Create New Token/i }));
await screen.findByText('Create API Token');
const input = screen.getByPlaceholderText(/Claude Desktop/i);
@@ -153,6 +170,7 @@ describe('IntegrationsTab', () => {
enableMcp();
render( );
await screen.findByText('MCP Configuration');
+ await user.click(screen.getByRole('button', { name: /API Tokens/i }));
await user.click(screen.getByRole('button', { name: /Create New Token/i }));
await screen.findByText('Create API Token');
const input = screen.getByPlaceholderText(/Claude Desktop/i);
@@ -182,6 +200,7 @@ describe('IntegrationsTab', () => {
enableMcp();
render( );
await screen.findByText('MCP Configuration');
+ await user.click(screen.getByRole('button', { name: /API Tokens/i }));
await user.click(screen.getByRole('button', { name: /Create New Token/i }));
await screen.findByText('Create API Token');
await user.type(screen.getByPlaceholderText(/Claude Desktop/i), 'test');
@@ -206,6 +225,8 @@ describe('IntegrationsTab', () => {
const user = userEvent.setup();
enableMcp();
render( );
+ await screen.findByText('MCP Configuration');
+ await user.click(screen.getByRole('button', { name: /API Tokens/i }));
await screen.findByText('Delete Me');
await user.click(screen.getByTitle('Delete Token'));
await screen.findByText('This token will stop working immediately. Any MCP client using it will lose access.');
@@ -230,6 +251,8 @@ describe('IntegrationsTab', () => {
const user = userEvent.setup();
enableMcp();
render( );
+ await screen.findByText('MCP Configuration');
+ await user.click(screen.getByRole('button', { name: /API Tokens/i }));
await screen.findByText('Delete Me');
await user.click(screen.getByTitle('Delete Token'));
// There are two "Delete Token" buttons: the trash icon (title) and the confirm button in modal
@@ -289,6 +312,8 @@ describe('IntegrationsTab', () => {
const user = userEvent.setup();
enableMcp();
render( );
+ await screen.findByText('MCP Configuration');
+ await user.click(screen.getByRole('button', { name: /API Tokens/i }));
await screen.findByText('Cancel Token');
await user.click(screen.getByTitle('Delete Token'));
await screen.findByRole('button', { name: /^Cancel$/i });
@@ -319,6 +344,7 @@ describe('IntegrationsTab', () => {
enableMcp();
render( );
await screen.findByText('MCP Configuration');
+ await user.click(screen.getByRole('button', { name: /API Tokens/i }));
await user.click(screen.getByRole('button', { name: /Create New Token/i }));
await screen.findByText('Create API Token');
const input = screen.getByPlaceholderText(/Claude Desktop/i);
@@ -328,4 +354,301 @@ describe('IntegrationsTab', () => {
expect(postCalled).toBe(true);
});
});
+
+ it('FE-COMP-INTEGRATIONS-019: default tab is OAuth 2.1 Clients — OAuth hint visible, token list hidden', async () => {
+ enableMcp();
+ render( );
+ await screen.findByText('MCP Configuration');
+ // OAuth hint is visible on the default tab
+ expect(screen.getByText(/Register OAuth 2\.1 clients/i)).toBeInTheDocument();
+ // API Tokens "no tokens" message is not rendered
+ expect(screen.queryByText('No tokens yet. Create one to connect MCP clients.')).toBeNull();
+ });
+
+ it('FE-COMP-INTEGRATIONS-020: switching tabs toggles content visibility', async () => {
+ const user = userEvent.setup();
+ enableMcp();
+ render( );
+ await screen.findByText('MCP Configuration');
+ // Default: OAuth hint visible, token list absent
+ expect(screen.getByText(/Register OAuth 2\.1 clients/i)).toBeInTheDocument();
+ expect(screen.queryByText('No tokens yet. Create one to connect MCP clients.')).toBeNull();
+ // Switch to API Tokens tab
+ await user.click(screen.getByRole('button', { name: /API Tokens/i }));
+ await screen.findByText('No tokens yet. Create one to connect MCP clients.');
+ expect(screen.queryByText(/Register OAuth 2\.1 clients/i)).toBeNull();
+ // Switch back to OAuth tab
+ await user.click(screen.getByRole('button', { name: /OAuth 2\.1 Clients/i }));
+ await screen.findByText(/Register OAuth 2\.1 clients/i);
+ expect(screen.queryByText('No tokens yet. Create one to connect MCP clients.')).toBeNull();
+ });
+
+ it('FE-COMP-INTEGRATIONS-021: OAuth client list renders when clients exist', async () => {
+ server.use(
+ http.get('/api/oauth/clients', () =>
+ HttpResponse.json({
+ clients: [
+ {
+ id: 'client-1',
+ client_id: 'clid-abc',
+ name: 'My OAuth App',
+ redirect_uris: ['http://localhost'],
+ allowed_scopes: ['trips:read', 'places:read'],
+ created_at: '2025-01-01T00:00:00Z',
+ },
+ ],
+ })
+ )
+ );
+ enableMcp();
+ render( );
+ await screen.findByText('My OAuth App');
+ expect(screen.getByText(/clid-abc/)).toBeInTheDocument();
+ });
+
+ it('FE-COMP-INTEGRATIONS-022: scope expansion toggle shows more/fewer scopes', async () => {
+ const user = userEvent.setup();
+ const scopes = ['trips:read', 'trips:write', 'places:read', 'places:write', 'budget:read', 'budget:write', 'packing:read'];
+ server.use(
+ http.get('/api/oauth/clients', () =>
+ HttpResponse.json({
+ clients: [
+ { id: 'c1', client_id: 'cid', name: 'Big App', redirect_uris: ['http://localhost'], allowed_scopes: scopes, created_at: '2025-01-01T00:00:00Z' },
+ ],
+ })
+ )
+ );
+ enableMcp();
+ render( );
+ await screen.findByText('Big App');
+ // "+2 more" button visible (7 scopes, 5 shown)
+ const moreBtn = screen.getByText(/^\+\d+$/);
+ await user.click(moreBtn);
+ // Show less / collapse button now visible
+ expect(screen.getByText('−')).toBeInTheDocument();
+ });
+
+ it('FE-COMP-INTEGRATIONS-023: active OAuth sessions section renders when sessions exist', async () => {
+ server.use(
+ http.get('/api/oauth/sessions', () =>
+ HttpResponse.json({
+ sessions: [
+ {
+ id: 10,
+ client_name: 'Claude Desktop',
+ scopes: ['trips:read'],
+ access_token_expires_at: '2025-12-31T00:00:00Z',
+ },
+ ],
+ })
+ )
+ );
+ enableMcp();
+ render( );
+ await screen.findByText('Claude Desktop');
+ expect(screen.getByText(/trips:read/)).toBeInTheDocument();
+ });
+
+ it('FE-COMP-INTEGRATIONS-024: Create OAuth Client modal opens and shows presets', async () => {
+ const user = userEvent.setup();
+ enableMcp();
+ render( );
+ await screen.findByText('MCP Configuration');
+ await user.click(screen.getByRole('button', { name: /New Client/i }));
+ await screen.findByText('Register OAuth Client');
+ expect(screen.getByText('Claude.ai')).toBeInTheDocument();
+ expect(screen.getByText('Claude Desktop')).toBeInTheDocument();
+ });
+
+ it('FE-COMP-INTEGRATIONS-025: clicking a preset fills form fields', async () => {
+ const user = userEvent.setup();
+ enableMcp();
+ render( );
+ await screen.findByText('MCP Configuration');
+ await user.click(screen.getByRole('button', { name: /New Client/i }));
+ await screen.findByText('Register OAuth Client');
+ // Presets render as buttons — click "Claude.ai" preset
+ const presetBtns = screen.getAllByRole('button', { name: /Claude\.ai/i });
+ await user.click(presetBtns[0]);
+ // Name field should be filled with 'Claude.ai'
+ const nameInput = screen.getByPlaceholderText(/Claude Web, My MCP App/i);
+ expect((nameInput as HTMLInputElement).value).toBe('Claude.ai');
+ });
+
+ it('FE-COMP-INTEGRATIONS-026: creating client shows success view with client_id and secret', async () => {
+ const user = userEvent.setup();
+ server.use(
+ http.post('/api/oauth/clients', () =>
+ HttpResponse.json({
+ client: {
+ id: 'new-id',
+ client_id: 'clid-new',
+ client_secret: 'secret-value',
+ name: 'Test Client',
+ redirect_uris: ['http://localhost'],
+ allowed_scopes: ['trips:read'],
+ created_at: '2025-01-01T00:00:00Z',
+ },
+ })
+ )
+ );
+ enableMcp();
+ render( );
+ await screen.findByText('MCP Configuration');
+ await user.click(screen.getByRole('button', { name: /New Client/i }));
+ await screen.findByText('Register OAuth Client');
+
+ const nameInput = screen.getByPlaceholderText(/Claude Web, My MCP App/i);
+ await user.type(nameInput, 'Test Client');
+ const uriInput = screen.getByPlaceholderText(/https:\/\/your-app/i);
+ await user.type(uriInput, 'http://localhost');
+ await user.click(screen.getByRole('button', { name: /Register Client/i }));
+ // Success view shows client credentials (there may be multiple matches in list + modal)
+ await screen.findAllByText(/clid-new/);
+ const secretEls = await screen.findAllByText(/secret-value/);
+ expect(secretEls.length).toBeGreaterThan(0);
+ });
+
+ it('FE-COMP-INTEGRATIONS-027: Done button closes created-client modal', async () => {
+ const user = userEvent.setup();
+ server.use(
+ http.post('/api/oauth/clients', () =>
+ HttpResponse.json({
+ client: {
+ id: 'n2',
+ client_id: 'clid-n2',
+ client_secret: 'secret-n2',
+ name: 'TC2',
+ redirect_uris: ['http://localhost'],
+ allowed_scopes: ['trips:read'],
+ created_at: '2025-01-01T00:00:00Z',
+ },
+ })
+ )
+ );
+ enableMcp();
+ render( );
+ await screen.findByText('MCP Configuration');
+ await user.click(screen.getByRole('button', { name: /New Client/i }));
+ await screen.findByText('Register OAuth Client');
+ await user.type(screen.getByPlaceholderText(/Claude Web, My MCP App/i), 'TC2');
+ await user.type(screen.getByPlaceholderText(/https:\/\/your-app/i), 'http://localhost');
+ await user.click(screen.getByRole('button', { name: /Register Client/i }));
+ await screen.findAllByText(/clid-n2/);
+ // Check the "Client Registered" modal title is visible before Done
+ expect(screen.getByText('Client Registered')).toBeInTheDocument();
+ await user.click(screen.getByRole('button', { name: /^Done$/i }));
+ await waitFor(() => {
+ expect(screen.queryByText('Client Registered')).toBeNull();
+ });
+ });
+
+ it('FE-COMP-INTEGRATIONS-028: delete OAuth client confirmation removes client from list', async () => {
+ const user = userEvent.setup();
+ server.use(
+ http.get('/api/oauth/clients', () =>
+ HttpResponse.json({
+ clients: [
+ { id: 'del-1', client_id: 'cid-del', name: 'Delete Me', redirect_uris: ['http://localhost'], allowed_scopes: ['trips:read'], created_at: '2025-01-01T00:00:00Z' },
+ ],
+ })
+ ),
+ http.delete('/api/oauth/clients/del-1', () => HttpResponse.json({ success: true }))
+ );
+ enableMcp();
+ render(<> >);
+ await screen.findByText('Delete Me');
+ await user.click(screen.getByTitle('Delete Client'));
+ // Confirmation modal
+ await screen.findByRole('heading', { name: 'Delete Client' });
+ const confirmBtns = screen.getAllByRole('button', { name: /Delete Client/i });
+ // Modal confirm button is last in DOM (modal renders after list)
+ await user.click(confirmBtns[confirmBtns.length - 1]);
+ await waitFor(() => {
+ expect(screen.queryByText('Delete Me')).toBeNull();
+ });
+ });
+
+ it('FE-COMP-INTEGRATIONS-029: rotate secret confirmation shows new secret', async () => {
+ const user = userEvent.setup();
+ server.use(
+ http.get('/api/oauth/clients', () =>
+ HttpResponse.json({
+ clients: [
+ { id: 'rot-1', client_id: 'cid-rot', name: 'Rotate Me', redirect_uris: ['http://localhost'], allowed_scopes: ['trips:read'], created_at: '2025-01-01T00:00:00Z' },
+ ],
+ })
+ ),
+ http.post('/api/oauth/clients/rot-1/rotate', () =>
+ HttpResponse.json({ client_secret: 'new-rotated-secret' })
+ )
+ );
+ enableMcp();
+ render( );
+ await screen.findByText('Rotate Me');
+ await user.click(screen.getByTitle('Rotate Secret'));
+ await screen.findByText('Rotate Secret');
+ // Confirm — button text is 'Rotate'
+ const rotateBtns = screen.getAllByRole('button', { name: /^Rotate$/i });
+ await user.click(rotateBtns[rotateBtns.length - 1]);
+ await screen.findByText(/new-rotated-secret/);
+ });
+
+ it('FE-COMP-INTEGRATIONS-030: revoke OAuth session removes it from list', async () => {
+ const user = userEvent.setup();
+ server.use(
+ http.get('/api/oauth/sessions', () =>
+ HttpResponse.json({
+ sessions: [
+ { id: 99, client_name: 'Revoke App', scopes: ['trips:read'], access_token_expires_at: '2025-12-31T00:00:00Z' },
+ ],
+ })
+ ),
+ http.delete('/api/oauth/sessions/99', () => HttpResponse.json({ success: true }))
+ );
+ enableMcp();
+ render(<> >);
+ await screen.findByText('Revoke App');
+ await user.click(screen.getByText('Revoke'));
+ // Confirmation modal
+ await screen.findByText('Revoke Session');
+ const revokeBtns = screen.getAllByRole('button', { name: /^Revoke$/i });
+ // Modal confirm button is last in DOM
+ await user.click(revokeBtns[revokeBtns.length - 1]);
+ await waitFor(() => {
+ expect(screen.queryByText('Revoke App')).toBeNull();
+ });
+ });
+
+ it('FE-COMP-INTEGRATIONS-031: Register Client button disabled when name or URI is empty', async () => {
+ const user = userEvent.setup();
+ enableMcp();
+ render( );
+ await screen.findByText('MCP Configuration');
+ await user.click(screen.getByRole('button', { name: /New Client/i }));
+ await screen.findByText('Register OAuth Client');
+ const createBtn = screen.getByRole('button', { name: /Register Client/i });
+ expect(createBtn).toBeDisabled();
+ // Type only name, not URI → still disabled
+ await user.type(screen.getByPlaceholderText(/Claude Web, My MCP App/i), 'Test');
+ expect(createBtn).toBeDisabled();
+ });
+
+ it('FE-COMP-INTEGRATIONS-032: error toast shown when create OAuth client fails', async () => {
+ const user = userEvent.setup();
+ server.use(
+ http.post('/api/oauth/clients', () =>
+ HttpResponse.json({ error: 'server error' }, { status: 500 })
+ )
+ );
+ enableMcp();
+ render(<> >);
+ await screen.findByText('MCP Configuration');
+ await user.click(screen.getByRole('button', { name: /New Client/i }));
+ await screen.findByText('Register OAuth Client');
+ await user.type(screen.getByPlaceholderText(/Claude Web, My MCP App/i), 'Fail Client');
+ await user.type(screen.getByPlaceholderText(/https:\/\/your-app/i), 'http://localhost');
+ await user.click(screen.getByRole('button', { name: /Register Client/i }));
+ await screen.findByText(/Failed to register/i);
+ });
});
diff --git a/client/src/components/Settings/IntegrationsTab.tsx b/client/src/components/Settings/IntegrationsTab.tsx
index f56dbcdf..430da0f6 100644
--- a/client/src/components/Settings/IntegrationsTab.tsx
+++ b/client/src/components/Settings/IntegrationsTab.tsx
@@ -1,12 +1,87 @@
import Section from './Section'
-import React, { useEffect, useState } from 'react'
+import React, { useEffect, useRef, useState } from 'react'
import { useTranslation } from '../../i18n'
import { useToast } from '../shared/Toast'
-import { Trash2, Copy, Terminal, Plus, Check } from 'lucide-react'
-import { authApi } from '../../api/client'
+import { Trash2, Copy, Terminal, Plus, Check, KeyRound, ChevronDown, ChevronRight, RefreshCw } from 'lucide-react'
+import { authApi, oauthApi } from '../../api/client'
import { useAddonStore } from '../../store/addonStore'
import PhotoProvidersSection from './PhotoProvidersSection'
+import { ALL_SCOPES } from '../../api/oauthScopes'
+import ScopeGroupPicker from '../OAuth/ScopeGroupPicker'
+interface OAuthPreset {
+ id: string
+ label: string
+ name: string
+ uris: string
+ scopes: string[]
+}
+
+const OAUTH_PRESETS: OAuthPreset[] = [
+ {
+ id: 'claude-web',
+ label: 'Claude.ai',
+ name: 'Claude.ai',
+ uris: 'https://claude.ai/api/mcp/auth_callback',
+ scopes: ALL_SCOPES.filter(s => !s.includes(':delete')),
+ },
+ {
+ id: 'claude-desktop',
+ label: 'Claude Desktop',
+ name: 'Claude Desktop',
+ uris: 'http://localhost',
+ scopes: ALL_SCOPES.filter(s => !s.includes(':delete')),
+ },
+ {
+ id: 'cursor',
+ label: 'Cursor',
+ name: 'Cursor',
+ uris: 'http://localhost',
+ scopes: ALL_SCOPES.filter(s => !s.includes(':delete')),
+ },
+ {
+ id: 'vscode',
+ label: 'VS Code',
+ name: 'VS Code / Copilot',
+ uris: 'http://localhost',
+ scopes: ALL_SCOPES.filter(s => s.endsWith(':read')),
+ },
+ {
+ id: 'windsurf',
+ label: 'Windsurf',
+ name: 'Windsurf',
+ uris: 'http://localhost',
+ scopes: ALL_SCOPES.filter(s => !s.includes(':delete')),
+ },
+ {
+ id: 'zed',
+ label: 'Zed',
+ name: 'Zed',
+ uris: 'http://localhost',
+ scopes: ALL_SCOPES.filter(s => !s.includes(':delete')),
+ },
+]
+
+
+interface OAuthClient {
+ id: string
+ name: string
+ client_id: string
+ redirect_uris: string[]
+ allowed_scopes: string[]
+ created_at: string
+ client_secret?: string // only present on create
+}
+
+interface OAuthSession {
+ id: number
+ client_id: string
+ client_name: string
+ scopes: string[]
+ access_token_expires_at: string
+ refresh_token_expires_at: string
+ created_at: string
+}
interface McpToken {
id: number
@@ -26,6 +101,28 @@ export default function IntegrationsTab(): React.ReactElement {
loadAddons()
}, [loadAddons])
+ // OAuth clients state
+ const [oauthClients, setOauthClients] = useState([])
+ const [oauthSessions, setOauthSessions] = useState([])
+ const [oauthCreateOpen, setOauthCreateOpen] = useState(false)
+ const [oauthNewName, setOauthNewName] = useState('')
+ const [oauthNewUris, setOauthNewUris] = useState('')
+ const [oauthNewScopes, setOauthNewScopes] = useState([])
+ const [oauthCreating, setOauthCreating] = useState(false)
+ const [oauthCreatedClient, setOauthCreatedClient] = useState(null)
+ const [oauthDeleteId, setOauthDeleteId] = useState(null)
+ const [oauthRevokeId, setOauthRevokeId] = useState(null)
+ const [oauthRotateId, setOauthRotateId] = useState(null)
+ const [oauthRotatedSecret, setOauthRotatedSecret] = useState(null)
+ const [oauthRotating, setOauthRotating] = useState(false)
+ // oauthScopesOpen is managed internally by ScopeGroupPicker
+ const [oauthScopesExpanded, setOauthScopesExpanded] = useState>({})
+
+ // MCP sub-tab state
+ const [activeMcpTab, setActiveMcpTab] = useState<'oauth' | 'apitokens'>('oauth')
+ const [configOpenOAuth, setConfigOpenOAuth] = useState(false)
+ const [configOpenToken, setConfigOpenToken] = useState(false)
+
// MCP state
const [mcpTokens, setMcpTokens] = useState([])
const [mcpModalOpen, setMcpModalOpen] = useState(false)
@@ -34,8 +131,26 @@ export default function IntegrationsTab(): React.ReactElement {
const [mcpCreating, setMcpCreating] = useState(false)
const [mcpDeleteId, setMcpDeleteId] = useState(null)
const [copiedKey, setCopiedKey] = useState(null)
+ const copyTimerRef = useRef | null>(null)
+
+ useEffect(() => {
+ return () => { if (copyTimerRef.current) clearTimeout(copyTimerRef.current) }
+ }, [])
const mcpEndpoint = `${window.location.origin}/mcp`
+ const mcpJsonConfigOAuth = `{
+ "mcpServers": {
+ "trek": {
+ "command": "npx",
+ "args": [
+ "mcp-remote",
+ "${mcpEndpoint}",
+ "--static-oauth-client-info",
+ "{\\"client_id\\": \\"\\", \\"client_secret\\": \\"\\"}"
+ ]
+ }
+ }
+}`
const mcpJsonConfig = `{
"mcpServers": {
"trek": {
@@ -85,10 +200,72 @@ export default function IntegrationsTab(): React.ReactElement {
const handleCopy = (text: string, key: string) => {
navigator.clipboard.writeText(text).then(() => {
setCopiedKey(key)
- setTimeout(() => setCopiedKey(null), 2000)
+ if (copyTimerRef.current) clearTimeout(copyTimerRef.current)
+ copyTimerRef.current = setTimeout(() => setCopiedKey(null), 2000)
})
}
+ // Load OAuth clients and sessions
+ useEffect(() => {
+ if (mcpEnabled) {
+ oauthApi.clients.list().then(d => setOauthClients(d.clients || [])).catch(() => {})
+ oauthApi.sessions.list().then(d => setOauthSessions(d.sessions || [])).catch(() => {})
+ }
+ }, [mcpEnabled])
+
+ const handleCreateOAuthClient = async () => {
+ if (!oauthNewName.trim() || !oauthNewUris.trim()) return
+ setOauthCreating(true)
+ try {
+ const uris = oauthNewUris.split('\n').map(u => u.trim()).filter(Boolean)
+ const d = await oauthApi.clients.create({ name: oauthNewName.trim(), redirect_uris: uris, allowed_scopes: oauthNewScopes })
+ setOauthCreatedClient(d.client)
+ setOauthClients(prev => [...prev, { ...d.client, client_secret: undefined }])
+ setOauthNewName('')
+ setOauthNewUris('')
+ setOauthNewScopes([])
+ } catch {
+ toast.error(t('settings.oauth.toast.createError'))
+ } finally {
+ setOauthCreating(false)
+ }
+ }
+
+ const handleDeleteOAuthClient = async (id: string) => {
+ try {
+ await oauthApi.clients.delete(id)
+ setOauthClients(prev => prev.filter(c => c.id !== id))
+ setOauthDeleteId(null)
+ toast.success(t('settings.oauth.toast.deleted'))
+ } catch {
+ toast.error(t('settings.oauth.toast.deleteError'))
+ }
+ }
+
+ const handleRotateSecret = async (id: string) => {
+ setOauthRotating(true)
+ try {
+ const d = await oauthApi.clients.rotate(id)
+ setOauthRotatedSecret(d.client_secret)
+ setOauthRotateId(null)
+ } catch {
+ toast.error(t('settings.oauth.toast.rotateError'))
+ } finally {
+ setOauthRotating(false)
+ }
+ }
+
+ const handleRevokeSession = async (id: number) => {
+ try {
+ await oauthApi.sessions.revoke(id)
+ setOauthSessions(prev => prev.filter(s => s.id !== id))
+ setOauthRevokeId(null)
+ toast.success(t('settings.oauth.toast.revoked'))
+ } catch {
+ toast.error(t('settings.oauth.toast.revokeError'))
+ }
+ }
+
return (
<>
@@ -109,63 +286,217 @@ export default function IntegrationsTab(): React.ReactElement {
- {/* JSON config box */}
-
-
- {t('settings.mcp.clientConfig')}
- handleCopy(mcpJsonConfig, 'json')}
- className="flex items-center gap-1.5 px-2.5 py-1 rounded text-xs border transition-colors hover:bg-slate-100 dark:hover:bg-slate-700"
- style={{ borderColor: 'var(--border-primary)', color: 'var(--text-secondary)' }}>
- {copiedKey === 'json' ? : }
- {copiedKey === 'json' ? t('settings.mcp.copied') : t('settings.mcp.copy')}
-
-
-
- {mcpJsonConfig}
-
-
{t('settings.mcp.clientConfigHint')}
+ {/* Sub-tab bar */}
+
+ setActiveMcpTab('oauth')}
+ className={`flex-1 px-3 py-1.5 text-sm font-medium rounded-md transition-colors ${
+ activeMcpTab === 'oauth' ? 'bg-slate-900 text-white' : 'text-slate-600 hover:text-slate-900 hover:bg-slate-50'
+ }`}>
+ {t('settings.oauth.clients')}
+
+ setActiveMcpTab('apitokens')}
+ className={`flex-1 px-3 py-1.5 text-sm font-medium rounded-md transition-colors flex items-center justify-center gap-2 ${
+ activeMcpTab === 'apitokens' ? 'bg-slate-900 text-white' : 'text-slate-600 hover:text-slate-900 hover:bg-slate-50'
+ }`}>
+ {t('settings.mcp.apiTokens')}
+
+ Deprecated
+
+
- {/* Token list */}
-
-
-
{t('settings.mcp.apiTokens')}
-
{ setMcpModalOpen(true); setMcpCreatedToken(null); setMcpNewName('') }}
- className="flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-sm font-medium transition-colors"
- style={{ background: 'var(--accent-primary, #4f46e5)', color: '#fff' }}>
- {t('settings.mcp.createToken')}
-
-
-
- {mcpTokens.length === 0 ? (
-
- {t('settings.mcp.noTokens')}
-
- ) : (
+ {/* OAuth 2.1 Clients tab */}
+ {activeMcpTab === 'oauth' && (
+ <>
+ {/* JSON config — OAuth (collapsible) */}
- {mcpTokens.map((token, i) => (
-
-
-
{token.name}
-
- {token.token_prefix}...
- {t('settings.mcp.tokenCreatedAt')} {new Date(token.created_at).toLocaleDateString(locale)}
- {token.last_used_at && (
- · {t('settings.mcp.tokenUsedAt')} {new Date(token.last_used_at).toLocaleDateString(locale)}
- )}
-
+
setConfigOpenOAuth(o => !o)}
+ className="w-full flex items-center justify-between px-3 py-2.5 transition-colors hover:bg-slate-50 dark:hover:bg-slate-800"
+ style={{ background: 'var(--bg-secondary)' }}>
+ {t('settings.mcp.clientConfig')}
+ {configOpenOAuth ? : }
+
+ {configOpenOAuth && (
+
+
+ handleCopy(mcpJsonConfigOAuth, 'json-oauth')}
+ className="flex items-center gap-1.5 px-2.5 py-1 rounded text-xs border transition-colors hover:bg-slate-100 dark:hover:bg-slate-700"
+ style={{ borderColor: 'var(--border-primary)', color: 'var(--text-secondary)' }}>
+ {copiedKey === 'json-oauth' ? : }
+ {copiedKey === 'json-oauth' ? t('settings.mcp.copied') : t('settings.mcp.copy')}
+
-
setMcpDeleteId(token.id)}
- className="p-1.5 rounded-lg transition-colors hover:bg-red-50 hover:text-red-600 dark:hover:bg-red-900/20"
- style={{ color: 'var(--text-tertiary)' }} title={t('settings.mcp.deleteTokenTitle')}>
-
-
+
+ {mcpJsonConfigOAuth}
+
+
{t('settings.mcp.clientConfigHintOAuth')}
- ))}
+ )}
- )}
-
+
+
+
{t('settings.oauth.clientsHint')}
+
+
+
{ setOauthCreateOpen(true); setOauthCreatedClient(null); setOauthNewName(''); setOauthNewUris(''); setOauthNewScopes([]) }}
+ className="flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-sm font-medium transition-colors bg-slate-900 text-white hover:bg-slate-700">
+ {t('settings.oauth.createClient')}
+
+
+
+ {oauthClients.length === 0 ? (
+
+ {t('settings.oauth.noClients')}
+
+ ) : (
+
+ {oauthClients.map((client, i) => (
+
+
+
+
+
{client.name}
+
+ {t('settings.oauth.clientId')}: {client.client_id}
+ {t('settings.mcp.tokenCreatedAt')} {new Date(client.created_at).toLocaleDateString(locale)}
+
+
+ {(oauthScopesExpanded[client.id] ? client.allowed_scopes : client.allowed_scopes.slice(0, 5)).map(s => (
+ {s}
+ ))}
+ {client.allowed_scopes.length > 5 && (
+ setOauthScopesExpanded(prev => ({ ...prev, [client.id]: !prev[client.id] }))}
+ className="px-1.5 py-0.5 rounded text-xs transition-colors hover:bg-slate-100 dark:hover:bg-slate-700"
+ style={{ color: 'var(--text-tertiary)', border: '1px solid var(--border-primary)' }}>
+ {oauthScopesExpanded[client.id] ? '−' : `+${client.allowed_scopes.length - 5}`}
+
+ )}
+
+
+
setOauthRotateId(client.id)}
+ className="p-1.5 rounded-lg transition-colors hover:bg-amber-50 hover:text-amber-600 dark:hover:bg-amber-900/20"
+ style={{ color: 'var(--text-tertiary)' }} title={t('settings.oauth.rotateSecret')}>
+
+
+
setOauthDeleteId(client.id)}
+ className="p-1.5 rounded-lg transition-colors hover:bg-red-50 hover:text-red-600 dark:hover:bg-red-900/20"
+ style={{ color: 'var(--text-tertiary)' }} title={t('settings.oauth.deleteClient')}>
+
+
+
+
+ ))}
+
+ )}
+
+
+ {/* Active OAuth Sessions */}
+ {oauthSessions.length > 0 && (
+
+
{t('settings.oauth.activeSessions')}
+
+ {oauthSessions.map((session, i) => (
+
+
+
{session.client_name}
+
+ {t('settings.oauth.sessionScopes')}: {session.scopes.join(', ')}
+ {t('settings.oauth.sessionExpires')} {new Date(session.access_token_expires_at).toLocaleDateString(locale)}
+
+
+
setOauthRevokeId(session.id)}
+ className="px-2.5 py-1 rounded text-xs border transition-colors hover:bg-red-50 hover:text-red-600 dark:hover:bg-red-900/20"
+ style={{ borderColor: 'var(--border-primary)', color: 'var(--text-tertiary)' }}>
+ {t('settings.oauth.revoke')}
+
+
+ ))}
+
+
+ )}
+ >
+ )}
+
+ {/* API Tokens tab (deprecated) */}
+ {activeMcpTab === 'apitokens' && (
+ <>
+
+
⚠
+
{t('settings.mcp.apiTokensDeprecated')}
+
+
+ {/* JSON config — API Token (collapsible) */}
+
+
setConfigOpenToken(o => !o)}
+ className="w-full flex items-center justify-between px-3 py-2.5 transition-colors hover:bg-slate-50 dark:hover:bg-slate-800"
+ style={{ background: 'var(--bg-secondary)' }}>
+ {t('settings.mcp.clientConfig')}
+ {configOpenToken ? : }
+
+ {configOpenToken && (
+
+
+ handleCopy(mcpJsonConfig, 'json-token')}
+ className="flex items-center gap-1.5 px-2.5 py-1 rounded text-xs border transition-colors hover:bg-slate-100 dark:hover:bg-slate-700"
+ style={{ borderColor: 'var(--border-primary)', color: 'var(--text-secondary)' }}>
+ {copiedKey === 'json-token' ? : }
+ {copiedKey === 'json-token' ? t('settings.mcp.copied') : t('settings.mcp.copy')}
+
+
+
+ {mcpJsonConfig}
+
+
{t('settings.mcp.clientConfigHint')}
+
+ )}
+
+
+
+
{ setMcpModalOpen(true); setMcpCreatedToken(null); setMcpNewName('') }}
+ className="flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-sm font-medium transition-colors opacity-60"
+ style={{ background: 'var(--bg-tertiary, #e5e7eb)', color: 'var(--text-secondary)' }}>
+ {t('settings.mcp.createToken')}
+
+
+
+ {mcpTokens.length === 0 ? (
+
+ {t('settings.mcp.noTokens')}
+
+ ) : (
+
+ {mcpTokens.map((token, i) => (
+
+
+
{token.name}
+
+ {token.token_prefix}...
+ {t('settings.mcp.tokenCreatedAt')} {new Date(token.created_at).toLocaleDateString(locale)}
+ {token.last_used_at && (
+ · {t('settings.mcp.tokenUsedAt')} {new Date(token.last_used_at).toLocaleDateString(locale)}
+ )}
+
+
+
setMcpDeleteId(token.id)}
+ className="p-1.5 rounded-lg transition-colors hover:bg-red-50 hover:text-red-600 dark:hover:bg-red-900/20"
+ style={{ color: 'var(--text-tertiary)' }} title={t('settings.mcp.deleteTokenTitle')}>
+
+
+
+ ))}
+
+ )}
+ >
+ )}
)}
@@ -182,7 +513,7 @@ export default function IntegrationsTab(): React.ReactElement {
setMcpNewName(e.target.value)}
onKeyDown={e => e.key === 'Enter' && handleCreateMcpToken()}
placeholder={t('settings.mcp.modal.tokenNamePlaceholder')}
- className="w-full px-3 py-2.5 border rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-indigo-300"
+ className="w-full px-3 py-2.5 border rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-slate-400"
style={{ borderColor: 'var(--border-primary)', background: 'var(--bg-secondary)', color: 'var(--text-primary)' }}
autoFocus />
@@ -192,8 +523,7 @@ export default function IntegrationsTab(): React.ReactElement {
{t('common.cancel')}
+ className="px-4 py-2 rounded-lg text-sm font-medium text-white bg-slate-900 hover:bg-slate-700 disabled:opacity-50">
{mcpCreating ? t('settings.mcp.modal.creating') : t('settings.mcp.modal.create')}
@@ -217,8 +547,7 @@ export default function IntegrationsTab(): React.ReactElement {
{ setMcpModalOpen(false); setMcpCreatedToken(null) }}
- className="px-4 py-2 rounded-lg text-sm font-medium text-white"
- style={{ background: 'var(--accent-primary, #4f46e5)' }}>
+ className="px-4 py-2 rounded-lg text-sm font-medium text-white bg-slate-900 hover:bg-slate-700">
{t('settings.mcp.modal.done')}
@@ -248,6 +577,216 @@ export default function IntegrationsTab(): React.ReactElement {
)}
+
+ {/* Create OAuth Client modal */}
+ {oauthCreateOpen && (
+
{ if (e.target === e.currentTarget && !oauthCreatedClient) setOauthCreateOpen(false) }}>
+
+ {!oauthCreatedClient ? (
+ <>
+
{t('settings.oauth.modal.createTitle')}
+
+
+
{t('settings.oauth.modal.presets')}
+
+ {OAUTH_PRESETS.map(preset => (
+ {
+ setOauthNewName(preset.name)
+ setOauthNewUris(preset.uris)
+ setOauthNewScopes(preset.scopes)
+ }}
+ className="px-2.5 py-1 rounded-md text-xs font-medium border transition-colors hover:bg-slate-100 dark:hover:bg-slate-700"
+ style={{ borderColor: 'var(--border-primary)', color: 'var(--text-secondary)', background: 'var(--bg-secondary)' }}>
+ {preset.label}
+
+ ))}
+
+
+
+
+ {t('settings.oauth.modal.clientName')}
+ setOauthNewName(e.target.value)}
+ placeholder={t('settings.oauth.modal.clientNamePlaceholder')}
+ className="w-full px-3 py-2.5 border rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-slate-400"
+ style={{ borderColor: 'var(--border-primary)', background: 'var(--bg-secondary)', color: 'var(--text-primary)' }}
+ autoFocus />
+
+
+
+
{t('settings.oauth.modal.redirectUris')}
+
+
+
+
{t('settings.oauth.modal.scopes')}
+
{t('settings.oauth.modal.scopesHint')}
+
+
+
+
+ setOauthCreateOpen(false)}
+ className="px-4 py-2 rounded-lg text-sm border" style={{ borderColor: 'var(--border-primary)', color: 'var(--text-secondary)' }}>
+ {t('common.cancel')}
+
+
+ {oauthCreating ? t('settings.oauth.modal.creating') : t('settings.oauth.modal.create')}
+
+
+ >
+ ) : (
+ <>
+
{t('settings.oauth.modal.createdTitle')}
+
+
⚠
+
{t('settings.oauth.modal.createdWarning')}
+
+
+
+
+
{t('settings.oauth.clientId')}
+
+
+ {oauthCreatedClient.client_id}
+
+ handleCopy(oauthCreatedClient.client_id, 'new-client-id')}
+ className="p-2 rounded-lg border transition-colors hover:bg-slate-100 dark:hover:bg-slate-700"
+ style={{ borderColor: 'var(--border-primary)' }}>
+ {copiedKey === 'new-client-id' ? : }
+
+
+
+
+
{t('settings.oauth.clientSecret')}
+
+
+ {oauthCreatedClient.client_secret}
+
+ handleCopy(oauthCreatedClient.client_secret!, 'new-client-secret')}
+ className="p-2 rounded-lg border transition-colors hover:bg-slate-100 dark:hover:bg-slate-700"
+ style={{ borderColor: 'var(--border-primary)' }}>
+ {copiedKey === 'new-client-secret' ? : }
+
+
+
+
+
+
+ { setOauthCreateOpen(false); setOauthCreatedClient(null) }}
+ className="px-4 py-2 rounded-lg text-sm font-medium text-white bg-slate-900 hover:bg-slate-700">
+ {t('settings.mcp.modal.done')}
+
+
+ >
+ )}
+
+
+ )}
+
+ {/* Delete OAuth Client confirm */}
+ {oauthDeleteId !== null && (
+
{ if (e.target === e.currentTarget) setOauthDeleteId(null) }}>
+
+
{t('settings.oauth.deleteClient')}
+
{t('settings.oauth.deleteClientMessage')}
+
+ setOauthDeleteId(null)}
+ className="px-4 py-2 rounded-lg text-sm border" style={{ borderColor: 'var(--border-primary)', color: 'var(--text-secondary)' }}>
+ {t('common.cancel')}
+
+ handleDeleteOAuthClient(oauthDeleteId)}
+ className="px-4 py-2 rounded-lg text-sm font-medium text-white bg-red-600 hover:bg-red-700">
+ {t('settings.oauth.deleteClient')}
+
+
+
+
+ )}
+
+ {/* Rotate OAuth Client Secret confirm */}
+ {oauthRotateId !== null && (
+
{ if (e.target === e.currentTarget) setOauthRotateId(null) }}>
+
+
{t('settings.oauth.rotateSecret')}
+
{t('settings.oauth.rotateSecretMessage')}
+
+ setOauthRotateId(null)}
+ className="px-4 py-2 rounded-lg text-sm border" style={{ borderColor: 'var(--border-primary)', color: 'var(--text-secondary)' }}>
+ {t('common.cancel')}
+
+ handleRotateSecret(oauthRotateId)} disabled={oauthRotating}
+ className="px-4 py-2 rounded-lg text-sm font-medium text-white bg-slate-900 hover:bg-slate-700 disabled:opacity-50">
+ {oauthRotating ? t('settings.oauth.rotateSecretConfirming') : t('settings.oauth.rotateSecretConfirm')}
+
+
+
+
+ )}
+
+ {/* Rotated Secret display */}
+ {oauthRotatedSecret !== null && (
+
+
+
{t('settings.oauth.rotateSecretDoneTitle')}
+
+
⚠
+
{t('settings.oauth.rotateSecretDoneWarning')}
+
+
+
{t('settings.oauth.clientSecret')}
+
+
+ {oauthRotatedSecret}
+
+ handleCopy(oauthRotatedSecret, 'rotated-secret')}
+ className="p-2 rounded-lg border transition-colors hover:bg-slate-100 dark:hover:bg-slate-700"
+ style={{ borderColor: 'var(--border-primary)' }}>
+ {copiedKey === 'rotated-secret' ? : }
+
+
+
+
+ setOauthRotatedSecret(null)}
+ className="px-4 py-2 rounded-lg text-sm font-medium text-white bg-slate-900 hover:bg-slate-700">
+ {t('settings.mcp.modal.done')}
+
+
+
+
+ )}
+
+ {/* Revoke OAuth Session confirm */}
+ {oauthRevokeId !== null && (
+
{ if (e.target === e.currentTarget) setOauthRevokeId(null) }}>
+
+
{t('settings.oauth.revokeSession')}
+
{t('settings.oauth.revokeSessionMessage')}
+
+ setOauthRevokeId(null)}
+ className="px-4 py-2 rounded-lg text-sm border" style={{ borderColor: 'var(--border-primary)', color: 'var(--text-secondary)' }}>
+ {t('common.cancel')}
+
+ handleRevokeSession(oauthRevokeId)}
+ className="px-4 py-2 rounded-lg text-sm font-medium text-white bg-red-600 hover:bg-red-700">
+ {t('settings.oauth.revoke')}
+
+
+
+
+ )}
>
)
}
diff --git a/client/src/components/Settings/PhotoProvidersSection.tsx b/client/src/components/Settings/PhotoProvidersSection.tsx
index 4c00e292..845d1db4 100644
--- a/client/src/components/Settings/PhotoProvidersSection.tsx
+++ b/client/src/components/Settings/PhotoProvidersSection.tsx
@@ -11,6 +11,7 @@ interface ProviderField {
label: string
input_type: string
placeholder?: string | null
+ hint?: string | null
required: boolean
secret: boolean
settings_key?: string | null
@@ -71,6 +72,10 @@ export default function PhotoProvidersSection(): React.ReactElement {
const payload: Record
= {}
for (const field of getProviderFields(provider)) {
const payloadKey = field.payload_key || field.settings_key || field.key
+ if (field.input_type === 'checkbox') {
+ payload[payloadKey] = values[field.key] === 'true'
+ continue
+ }
const value = (values[field.key] || '').trim()
if (field.secret && !value) continue
payload[payloadKey] = value
@@ -102,6 +107,18 @@ export default function PhotoProvidersSection(): React.ReactElement {
const cfg = getProviderConfig(provider)
const fields = getProviderFields(provider)
+ // Seed checkbox defaults before the async settings load resolves
+ const checkboxDefaults: Record = {}
+ for (const field of fields) {
+ if (field.input_type === 'checkbox') checkboxDefaults[field.key] = 'false'
+ }
+ if (Object.keys(checkboxDefaults).length > 0) {
+ setProviderValues(prev => ({
+ ...prev,
+ [provider.id]: { ...checkboxDefaults, ...(prev[provider.id] || {}) },
+ }))
+ }
+
if (cfg.settings_get) {
apiClient.get(cfg.settings_get).then(res => {
if (isCancelled) return
@@ -112,7 +129,13 @@ export default function PhotoProvidersSection(): React.ReactElement {
if (field.secret) continue
const sourceKey = field.settings_key || field.payload_key || field.key
const rawValue = (res.data as Record)[sourceKey]
- nextValues[field.key] = typeof rawValue === 'string' ? rawValue : rawValue != null ? String(rawValue) : ''
+ if (rawValue != null) {
+ nextValues[field.key] = typeof rawValue === 'string' ? rawValue : String(rawValue)
+ } else if (field.input_type === 'checkbox') {
+ nextValues[field.key] = 'false'
+ } else {
+ nextValues[field.key] = ''
+ }
}
setProviderValues(prev => ({
...prev,
@@ -198,14 +221,31 @@ export default function PhotoProvidersSection(): React.ReactElement {
{fields.map(field => (
))}
@@ -228,11 +268,16 @@ export default function PhotoProvidersSection(): React.ReactElement {
: }
{t('memories.testConnection')}
- {connected && (
+ {connected ? (
{t('memories.connected')}
+ ) : (
+
+
+ {t('memories.disconnected')}
+
)}
diff --git a/client/src/components/Trips/TripMembersModal.tsx b/client/src/components/Trips/TripMembersModal.tsx
index 47a6b548..7ef8cfed 100644
--- a/client/src/components/Trips/TripMembersModal.tsx
+++ b/client/src/components/Trips/TripMembersModal.tsx
@@ -1,4 +1,4 @@
-import { useState, useEffect } from 'react'
+import { useState, useEffect, useRef } from 'react'
import Modal from '../shared/Modal'
import { tripsApi, authApi, shareApi } from '../../api/client'
import { useToast } from '../shared/Toast'
@@ -40,6 +40,11 @@ function ShareLinkSection({ tripId, t }: { tripId: number; t: (key: string, para
const [copied, setCopied] = useState(false)
const [perms, setPerms] = useState({ share_map: true, share_bookings: true, share_packing: false, share_budget: false, share_collab: false })
const toast = useToast()
+ const copyTimerRef = useRef | null>(null)
+
+ useEffect(() => {
+ return () => { if (copyTimerRef.current) clearTimeout(copyTimerRef.current) }
+ }, [])
useEffect(() => {
shareApi.getLink(tripId).then(d => {
@@ -77,7 +82,8 @@ function ShareLinkSection({ tripId, t }: { tripId: number; t: (key: string, para
if (shareUrl) {
navigator.clipboard.writeText(shareUrl)
setCopied(true)
- setTimeout(() => setCopied(false), 2000)
+ if (copyTimerRef.current) clearTimeout(copyTimerRef.current)
+ copyTimerRef.current = setTimeout(() => setCopied(false), 2000)
}
}
diff --git a/client/src/components/shared/Toast.tsx b/client/src/components/shared/Toast.tsx
index 29ceb601..28d47e28 100644
--- a/client/src/components/shared/Toast.tsx
+++ b/client/src/components/shared/Toast.tsx
@@ -1,4 +1,4 @@
-import React, { createContext, useContext, useState, useCallback, useEffect } from 'react'
+import React, { useState, useCallback, useEffect, useRef } from 'react'
import { CheckCircle, XCircle, AlertCircle, Info, X } from 'lucide-react'
type ToastType = 'success' | 'error' | 'warning' | 'info'
@@ -28,18 +28,27 @@ const ICON_COLORS: Record = {
export function ToastContainer() {
const [toasts, setToasts] = useState([])
+ const timersRef = useRef[]>([])
+
+ useEffect(() => {
+ return () => {
+ timersRef.current.forEach(clearTimeout)
+ }
+ }, [])
const addToast = useCallback((message: string, type: ToastType = 'info', duration: number = 3000) => {
const id = ++toastIdCounter
setToasts(prev => [...prev, { id, message, type, duration, removing: false }])
if (duration > 0) {
- setTimeout(() => {
+ const t1 = setTimeout(() => {
setToasts(prev => prev.map(t => t.id === id ? { ...t, removing: true } : t))
- setTimeout(() => {
+ const t2 = setTimeout(() => {
setToasts(prev => prev.filter(t => t.id !== id))
}, 400)
+ timersRef.current.push(t2)
}, duration)
+ timersRef.current.push(t1)
}
return id
@@ -47,9 +56,10 @@ export function ToastContainer() {
const removeToast = useCallback((id: number) => {
setToasts(prev => prev.map(t => t.id === id ? { ...t, removing: true } : t))
- setTimeout(() => {
+ const t = setTimeout(() => {
setToasts(prev => prev.filter(t => t.id !== id))
}, 400)
+ timersRef.current.push(t)
}, [])
useEffect(() => {
diff --git a/client/src/i18n/translations/ar.ts b/client/src/i18n/translations/ar.ts
index cdcac563..df59b102 100644
--- a/client/src/i18n/translations/ar.ts
+++ b/client/src/i18n/translations/ar.ts
@@ -184,9 +184,6 @@ const ar: Record = {
'admin.notifications.none': 'معطّل',
'admin.notifications.email': 'البريد الإلكتروني (SMTP)',
'admin.notifications.webhook': 'Webhook',
- 'admin.notifications.events': 'أحداث الإشعارات',
- 'admin.notifications.eventsHint': 'اختر الأحداث التي تُفعّل الإشعارات لجميع المستخدمين.',
- 'admin.notifications.configureFirst': 'قم بتكوين إعدادات SMTP أو Webhook أدناه أولاً، ثم قم بتفعيل الأحداث.',
'admin.notifications.save': 'حفظ إعدادات الإشعارات',
'admin.notifications.saved': 'تم حفظ إعدادات الإشعارات',
'admin.notifications.testWebhook': 'إرسال webhook تجريبي',
@@ -233,6 +230,7 @@ const ar: Record = {
'settings.mcp.endpoint': 'نقطة نهاية MCP',
'settings.mcp.clientConfig': 'إعداد العميل',
'settings.mcp.clientConfigHint': 'استبدل برمز API من القائمة أدناه. قد يحتاج مسار npx إلى ضبط وفق نظامك (مثلاً C:\\PROGRA~1\\nodejs\\npx.cmd على Windows).',
+ 'settings.mcp.clientConfigHintOAuth': 'استبدل و ببيانات الاعتماد المعروضة في عميل OAuth 2.1 الذي أنشأته أعلاه. سيفتح mcp-remote متصفحك لإتمام التفويض في أول اتصال. قد يحتاج مسار npx إلى تعديل حسب نظامك (مثال: C:\PROGRA~1\nodejs\npx.cmd على Windows).',
'settings.mcp.copy': 'نسخ',
'settings.mcp.copied': 'تم النسخ!',
'settings.mcp.apiTokens': 'رموز API',
@@ -254,6 +252,48 @@ const ar: Record = {
'settings.mcp.toast.createError': 'فشل إنشاء الرمز',
'settings.mcp.toast.deleted': 'تم حذف الرمز',
'settings.mcp.toast.deleteError': 'فشل حذف الرمز',
+ 'settings.mcp.apiTokensDeprecated': 'رموز API قديمة وستُزال في إصدار مستقبلي. يُرجى استخدام عملاء OAuth 2.1 بدلاً منها.',
+ 'settings.oauth.clients': 'عملاء OAuth 2.1',
+ 'settings.oauth.clientsHint': 'سجّل عملاء OAuth 2.1 للسماح لتطبيقات MCP الخارجية (Claude Web وCursor وغيرها) بالاتصال دون رموز ثابتة.',
+ 'settings.oauth.createClient': 'عميل جديد',
+ 'settings.oauth.noClients': 'لا يوجد عملاء OAuth مسجلون.',
+ 'settings.oauth.clientId': 'معرّف العميل',
+ 'settings.oauth.clientSecret': 'سر العميل',
+ 'settings.oauth.deleteClient': 'حذف العميل',
+ 'settings.oauth.deleteClientMessage': 'سيتم حذف هذا العميل وجميع الجلسات النشطة بشكل دائم. ستفقد أي تطبيق يستخدمه وصوله فوراً.',
+ 'settings.oauth.rotateSecret': 'تجديد السر',
+ 'settings.oauth.rotateSecretMessage': 'سيتم إنشاء سر عميل جديد وإبطال جميع الجلسات الحالية فوراً. حدّث تطبيقك قبل إغلاق هذا الحوار.',
+ 'settings.oauth.rotateSecretConfirm': 'تجديد',
+ 'settings.oauth.rotateSecretConfirming': 'جارٍ التجديد…',
+ 'settings.oauth.rotateSecretDoneTitle': 'تم إنشاء سر جديد',
+ 'settings.oauth.rotateSecretDoneWarning': 'يُعرض هذا السر مرة واحدة فقط. انسخه الآن وحدّث تطبيقك — تم إبطال جميع الجلسات السابقة.',
+ 'settings.oauth.activeSessions': 'جلسات OAuth النشطة',
+ 'settings.oauth.sessionScopes': 'النطاقات',
+ 'settings.oauth.sessionExpires': 'تنتهي',
+ 'settings.oauth.revoke': 'إلغاء',
+ 'settings.oauth.revokeSession': 'إلغاء الجلسة',
+ 'settings.oauth.revokeSessionMessage': 'سيؤدي هذا إلى إلغاء الوصول لهذه الجلسة OAuth فوراً.',
+ 'settings.oauth.modal.createTitle': 'تسجيل عميل OAuth',
+ 'settings.oauth.modal.presets': 'إعدادات سريعة',
+ 'settings.oauth.modal.clientName': 'اسم التطبيق',
+ 'settings.oauth.modal.clientNamePlaceholder': 'مثال: Claude Web، تطبيق MCP الخاص بي',
+ 'settings.oauth.modal.redirectUris': 'عناوين URI لإعادة التوجيه',
+ 'settings.oauth.modal.redirectUrisPlaceholder': 'https://your-app.com/callback\nhttps://your-app.com/auth',
+ 'settings.oauth.modal.redirectUrisHint': 'عنوان URI واحد لكل سطر. يُطلب HTTPS (localhost مستثنى). يُطبق تطابق دقيق.',
+ 'settings.oauth.modal.scopes': 'النطاقات المسموح بها',
+ 'settings.oauth.modal.scopesHint': 'list_trips وget_trip_summary متاحان دائماً — لا يُطلب نطاق. يساعدان الذكاء الاصطناعي في اكتشاف معرّفات الرحلات.',
+ 'settings.oauth.modal.selectAll': 'تحديد الكل',
+ 'settings.oauth.modal.deselectAll': 'إلغاء تحديد الكل',
+ 'settings.oauth.modal.creating': 'جارٍ التسجيل…',
+ 'settings.oauth.modal.create': 'تسجيل العميل',
+ 'settings.oauth.modal.createdTitle': 'تم تسجيل العميل',
+ 'settings.oauth.modal.createdWarning': 'يُعرض سر العميل مرة واحدة فقط. انسخه الآن — لا يمكن استرداده.',
+ 'settings.oauth.toast.createError': 'فشل تسجيل عميل OAuth',
+ 'settings.oauth.toast.deleted': 'تم حذف عميل OAuth',
+ 'settings.oauth.toast.deleteError': 'فشل حذف عميل OAuth',
+ 'settings.oauth.toast.revoked': 'تم إلغاء الجلسة',
+ 'settings.oauth.toast.revokeError': 'فشل إلغاء الجلسة',
+ 'settings.oauth.toast.rotateError': 'فشل تجديد سر العميل',
'settings.account': 'الحساب',
'settings.about': 'حول',
'settings.about.reportBug': 'الإبلاغ عن خطأ',
@@ -410,9 +450,10 @@ const ar: Record = {
'admin.tabs.config': 'التخصيص',
'admin.tabs.templates': 'قوالب التعبئة',
'admin.tabs.addons': 'الإضافات',
- 'admin.tabs.mcpTokens': 'رموز MCP',
- 'admin.mcpTokens.title': 'رموز MCP',
- 'admin.mcpTokens.subtitle': 'إدارة رموز API لجميع المستخدمين',
+ 'admin.tabs.mcpTokens': 'وصول MCP',
+ 'admin.mcpTokens.title': 'وصول MCP',
+ 'admin.mcpTokens.subtitle': 'إدارة جلسات OAuth ورموز API لجميع المستخدمين',
+ 'admin.mcpTokens.sectionTitle': 'رموز API',
'admin.mcpTokens.owner': 'المالك',
'admin.mcpTokens.tokenName': 'اسم الرمز',
'admin.mcpTokens.created': 'تاريخ الإنشاء',
@@ -424,6 +465,17 @@ const ar: Record = {
'admin.mcpTokens.deleteSuccess': 'تم حذف الرمز',
'admin.mcpTokens.deleteError': 'فشل حذف الرمز',
'admin.mcpTokens.loadError': 'فشل تحميل الرموز',
+ 'admin.oauthSessions.sectionTitle': 'جلسات OAuth',
+ 'admin.oauthSessions.clientName': 'العميل',
+ 'admin.oauthSessions.owner': 'المالك',
+ 'admin.oauthSessions.scopes': 'الصلاحيات',
+ 'admin.oauthSessions.created': 'تاريخ الإنشاء',
+ 'admin.oauthSessions.empty': 'لا توجد جلسات OAuth نشطة',
+ 'admin.oauthSessions.revokeTitle': 'إلغاء الجلسة',
+ 'admin.oauthSessions.revokeMessage': 'سيتم إلغاء جلسة OAuth هذه فوراً. سيفقد العميل وصوله إلى MCP.',
+ 'admin.oauthSessions.revokeSuccess': 'تم إلغاء الجلسة',
+ 'admin.oauthSessions.revokeError': 'فشل إلغاء الجلسة',
+ 'admin.oauthSessions.loadError': 'فشل تحميل جلسات OAuth',
'admin.tabs.github': 'GitHub',
'admin.stats.users': 'المستخدمون',
'admin.stats.trips': 'الرحلات',
@@ -1009,6 +1061,7 @@ const ar: Record = {
'budget.totalBudget': 'إجمالي الميزانية',
'budget.byCategory': 'حسب الفئة',
'budget.editTooltip': 'انقر للتعديل',
+ 'budget.linkedToReservation': 'مرتبط بحجز — عدّل الاسم هناك',
'budget.confirm.deleteCategory': 'هل تريد حذف الفئة "{name}" مع {count} إدخالات؟',
'budget.deleteCategory': 'حذف الفئة',
'budget.perPerson': 'لكل شخص',
@@ -1109,6 +1162,9 @@ const ar: Record = {
'packing.template': 'قالب',
'packing.templateApplied': 'تمت إضافة {count} عنصر من القالب',
'packing.templateError': 'فشل تطبيق القالب',
+ 'packing.saveAsTemplate': 'حفظ كقالب',
+ 'packing.templateName': 'اسم القالب',
+ 'packing.templateSaved': 'تم حفظ قائمة الحقائب كقالب',
'packing.bags': 'أمتعة',
'packing.noBag': 'غير معيّن',
'packing.totalWeight': 'الوزن الإجمالي',
@@ -1382,6 +1438,7 @@ const ar: Record = {
'memories.title': 'صور',
'memories.notConnected': 'Immich غير متصل',
'memories.notConnectedHint': 'قم بتوصيل Immich في الإعدادات لعرض صور رحلتك هنا.',
+ 'memories.notConnectedMultipleHint': 'قم بتوصيل أحد موفري الصور هؤلاء: {provider_names} في الإعدادات لتتمكن من إضافة صور إلى هذه الرحلة.',
'memories.noDates': 'أضف تواريخ لرحلتك لتحميل الصور.',
'memories.noPhotos': 'لم يتم العثور على صور',
'memories.noPhotosHint': 'لم يتم العثور على صور في Immich لفترة هذه الرحلة.',
@@ -1392,26 +1449,35 @@ const ar: Record = {
'memories.reviewTitle': 'مراجعة صورك',
'memories.reviewHint': 'انقر على الصور لاستبعادها من المشاركة.',
'memories.shareCount': 'مشاركة {count} صور',
- 'memories.immichUrl': 'عنوان خادم Immich',
- 'memories.immichApiKey': 'مفتاح API',
+ 'memories.providerUrl': 'عنوان URL للخادم',
+ 'memories.providerApiKey': 'مفتاح API',
+ 'memories.providerUsername': 'اسم المستخدم',
+ 'memories.providerPassword': 'كلمة المرور',
+ 'memories.providerOTP': 'رمز MFA (إذا كان مفعلاً)',
+ 'memories.skipSSLVerification': 'تخطي التحقق من شهادة SSL',
+ 'memories.providerUrlHintSynology': 'أدرج مسار تطبيق Photos في URL، مثل https://nas:5001/photo',
'memories.testConnection': 'اختبار الاتصال',
'memories.testFirst': 'اختبر الاتصال أولاً',
'memories.connected': 'متصل',
'memories.disconnected': 'غير متصل',
'memories.connectionSuccess': 'تم الاتصال بـ Immich',
'memories.connectionError': 'تعذر الاتصال بـ Immich',
- 'memories.saved': 'تم حفظ إعدادات Immich',
+ 'memories.saved': 'تم حفظ إعدادات {provider_name}',
+ 'memories.providerDisconnectedBanner': 'اتصالك بـ {provider_name} مفقود. أعد الاتصال في الإعدادات لعرض الصور.',
+ 'memories.saveError': 'تعذّر حفظ إعدادات {provider_name}',
'memories.oldest': 'الأقدم أولاً',
'memories.newest': 'الأحدث أولاً',
'memories.allLocations': 'جميع المواقع',
'memories.addPhotos': 'إضافة صور',
'memories.linkAlbum': 'ربط ألبوم',
'memories.selectAlbum': 'اختيار ألبوم Immich',
+ 'memories.selectAlbumMultiple': 'اختيار ألبوم',
'memories.noAlbums': 'لم يتم العثور على ألبومات',
'memories.syncAlbum': 'مزامنة الألبوم',
'memories.unlinkAlbum': 'إلغاء الربط',
'memories.photos': 'صور',
'memories.selectPhotos': 'اختيار صور من Immich',
+ 'memories.selectPhotosMultiple': 'اختيار الصور',
'memories.selectHint': 'انقر على الصور لتحديدها.',
'memories.selected': 'محدد',
'memories.addSelected': 'إضافة {count} صور',
@@ -1568,6 +1634,8 @@ const ar: Record = {
'notifications.markUnread': 'تحديد كغير مقروء',
'notifications.delete': 'حذف',
'notifications.system': 'النظام',
+ 'notifications.synologySessionCleared.title': 'تم قطع اتصال Synology Photos',
+ 'notifications.synologySessionCleared.text': 'تغير خادمك أو حسابك — انتقل إلى الإعدادات لاختبار اتصالك مرة أخرى.',
'memories.error.loadAlbums': 'فشل تحميل الألبومات',
'memories.error.linkAlbum': 'فشل ربط الألبوم',
'memories.error.unlinkAlbum': 'فشل إلغاء ربط الألبوم',
@@ -1690,6 +1758,70 @@ const ar: Record = {
'notif.generic.text': 'لديك إشعار جديد',
'notif.dev.unknown_event.title': '[DEV] حدث غير معروف',
'notif.dev.unknown_event.text': 'نوع الحدث "{event}" غير مسجل في EVENT_NOTIFICATION_CONFIG',
+
+ // OAuth scope groups
+ 'oauth.scope.group.trips': 'الرحلات',
+ 'oauth.scope.group.places': 'الأماكن',
+ 'oauth.scope.group.atlas': 'Atlas',
+ 'oauth.scope.group.packing': 'الأمتعة',
+ 'oauth.scope.group.todos': 'المهام',
+ 'oauth.scope.group.budget': 'الميزانية',
+ 'oauth.scope.group.reservations': 'الحجوزات',
+ 'oauth.scope.group.collab': 'التعاون',
+ 'oauth.scope.group.notifications': 'الإشعارات',
+ 'oauth.scope.group.vacay': 'الإجازة',
+ 'oauth.scope.group.geo': 'Geo',
+ 'oauth.scope.group.weather': 'الطقس',
+
+ // OAuth scope labels & descriptions
+ 'oauth.scope.trips:read.label': 'عرض الرحلات وخطط السفر',
+ 'oauth.scope.trips:read.description': 'قراءة الرحلات والأيام والملاحظات والأعضاء',
+ 'oauth.scope.trips:write.label': 'تحرير الرحلات وخطط السفر',
+ 'oauth.scope.trips:write.description': 'إنشاء وتحديث الرحلات والأيام والملاحظات وإدارة الأعضاء',
+ 'oauth.scope.trips:delete.label': 'حذف الرحلات',
+ 'oauth.scope.trips:delete.description': 'حذف الرحلات بأكملها نهائياً — هذا الإجراء لا يمكن التراجع عنه',
+ 'oauth.scope.trips:share.label': 'إدارة روابط المشاركة',
+ 'oauth.scope.trips:share.description': 'إنشاء روابط مشاركة عامة وتحديثها وإلغاؤها',
+ 'oauth.scope.places:read.label': 'عرض الأماكن وبيانات الخريطة',
+ 'oauth.scope.places:read.description': 'قراءة الأماكن وتعيينات الأيام والعلامات والفئات',
+ 'oauth.scope.places:write.label': 'إدارة الأماكن',
+ 'oauth.scope.places:write.description': 'إنشاء وتحديث وحذف الأماكن والتعيينات والعلامات',
+ 'oauth.scope.atlas:read.label': 'عرض Atlas',
+ 'oauth.scope.atlas:read.description': 'قراءة الدول والمناطق المزارة وقائمة الأمنيات',
+ 'oauth.scope.atlas:write.label': 'إدارة Atlas',
+ 'oauth.scope.atlas:write.description': 'تعليم الدول والمناطق كمزارة، وإدارة قائمة الأمنيات',
+ 'oauth.scope.packing:read.label': 'عرض قوائم الأمتعة',
+ 'oauth.scope.packing:read.description': 'قراءة عناصر الأمتعة والحقائب ومُسنَدي الفئات',
+ 'oauth.scope.packing:write.label': 'إدارة قوائم الأمتعة',
+ 'oauth.scope.packing:write.description': 'إضافة وتحديث وحذف وتبديل وإعادة ترتيب عناصر الأمتعة والحقائب',
+ 'oauth.scope.todos:read.label': 'عرض قوائم المهام',
+ 'oauth.scope.todos:read.description': 'قراءة مهام الرحلة ومُسنَدي الفئات',
+ 'oauth.scope.todos:write.label': 'إدارة قوائم المهام',
+ 'oauth.scope.todos:write.description': 'إنشاء وتحديث وتبديل وحذف وإعادة ترتيب المهام',
+ 'oauth.scope.budget:read.label': 'عرض الميزانية',
+ 'oauth.scope.budget:read.description': 'قراءة بنود الميزانية وتفاصيل النفقات',
+ 'oauth.scope.budget:write.label': 'إدارة الميزانية',
+ 'oauth.scope.budget:write.description': 'إنشاء وتحديث وحذف بنود الميزانية',
+ 'oauth.scope.reservations:read.label': 'عرض الحجوزات',
+ 'oauth.scope.reservations:read.description': 'قراءة الحجوزات وتفاصيل الإقامة',
+ 'oauth.scope.reservations:write.label': 'إدارة الحجوزات',
+ 'oauth.scope.reservations:write.description': 'إنشاء وتحديث وحذف وإعادة ترتيب الحجوزات',
+ 'oauth.scope.collab:read.label': 'عرض التعاون',
+ 'oauth.scope.collab:read.description': 'قراءة ملاحظات التعاون والاستطلاعات والرسائل',
+ 'oauth.scope.collab:write.label': 'إدارة التعاون',
+ 'oauth.scope.collab:write.description': 'إنشاء وتحديث وحذف الملاحظات والاستطلاعات والرسائل التعاونية',
+ 'oauth.scope.notifications:read.label': 'عرض الإشعارات',
+ 'oauth.scope.notifications:read.description': 'قراءة إشعارات التطبيق وأعداد غير المقروءة',
+ 'oauth.scope.notifications:write.label': 'إدارة الإشعارات',
+ 'oauth.scope.notifications:write.description': 'تعليم الإشعارات كمقروءة والرد عليها',
+ 'oauth.scope.vacay:read.label': 'عرض خطط الإجازة',
+ 'oauth.scope.vacay:read.description': 'قراءة بيانات تخطيط الإجازة والإدخالات والإحصاءات',
+ 'oauth.scope.vacay:write.label': 'إدارة خطط الإجازة',
+ 'oauth.scope.vacay:write.description': 'إنشاء وإدارة إدخالات الإجازة والعطلات وخطط الفريق',
+ 'oauth.scope.geo:read.label': 'الخرائط والترميز الجغرافي',
+ 'oauth.scope.geo:read.description': 'البحث عن مواقع وحل عناوين الخرائط والترميز الجغرافي العكسي للإحداثيات',
+ 'oauth.scope.weather:read.label': 'توقعات الطقس',
+ 'oauth.scope.weather:read.description': 'جلب توقعات الطقس لمواقع الرحلة وتواريخها',
}
export default ar
diff --git a/client/src/i18n/translations/br.ts b/client/src/i18n/translations/br.ts
index fc113772..7c9b00a8 100644
--- a/client/src/i18n/translations/br.ts
+++ b/client/src/i18n/translations/br.ts
@@ -179,9 +179,6 @@ const br: Record = {
'admin.notifications.none': 'Desativado',
'admin.notifications.email': 'E-mail (SMTP)',
'admin.notifications.webhook': 'Webhook',
- 'admin.notifications.events': 'Eventos de notificação',
- 'admin.notifications.eventsHint': 'Escolha quais eventos acionam notificações para todos os usuários.',
- 'admin.notifications.configureFirst': 'Configure primeiro as configurações SMTP ou webhook abaixo, depois ative os eventos.',
'admin.notifications.save': 'Salvar configurações de notificação',
'admin.notifications.saved': 'Configurações de notificação salvas',
'admin.notifications.testWebhook': 'Enviar webhook de teste',
@@ -295,6 +292,7 @@ const br: Record = {
'settings.mcp.endpoint': 'Endpoint MCP',
'settings.mcp.clientConfig': 'Configuração do cliente',
'settings.mcp.clientConfigHint': 'Substitua por um token de API da lista abaixo. O caminho para o npx pode precisar ser ajustado para o seu sistema (ex.: C:\\PROGRA~1\\nodejs\\npx.cmd no Windows).',
+ 'settings.mcp.clientConfigHintOAuth': 'Substitua e pelas credenciais exibidas no cliente OAuth 2.1 criado acima. O mcp-remote abrirá seu navegador para concluir a autorização na primeira conexão. O caminho para o npx pode precisar ser ajustado para seu sistema (ex.: C:\\PROGRA~1\\nodejs\\npx.cmd no Windows).',
'settings.mcp.copy': 'Copiar',
'settings.mcp.copied': 'Copiado!',
'settings.mcp.apiTokens': 'Tokens de API',
@@ -316,6 +314,48 @@ const br: Record = {
'settings.mcp.toast.createError': 'Falha ao criar token',
'settings.mcp.toast.deleted': 'Token excluído',
'settings.mcp.toast.deleteError': 'Falha ao excluir token',
+ 'settings.mcp.apiTokensDeprecated': 'Os tokens de API estão obsoletos e serão removidos em uma versão futura. Por favor, use Clientes OAuth 2.1.',
+ 'settings.oauth.clients': 'Clientes OAuth 2.1',
+ 'settings.oauth.clientsHint': 'Registre clientes OAuth 2.1 para permitir que aplicações MCP de terceiros (Claude Web, Cursor, etc.) se conectem sem tokens estáticos.',
+ 'settings.oauth.createClient': 'Novo cliente',
+ 'settings.oauth.noClients': 'Nenhum cliente OAuth registrado.',
+ 'settings.oauth.clientId': 'ID do cliente',
+ 'settings.oauth.clientSecret': 'Segredo do cliente',
+ 'settings.oauth.deleteClient': 'Excluir cliente',
+ 'settings.oauth.deleteClientMessage': 'Este cliente e todas as sessões ativas serão removidos permanentemente. Qualquer aplicação que o utilize perderá o acesso imediatamente.',
+ 'settings.oauth.rotateSecret': 'Renovar segredo',
+ 'settings.oauth.rotateSecretMessage': 'Um novo segredo de cliente será gerado e todas as sessões existentes serão invalidadas imediatamente. Atualize sua aplicação antes de fechar esta janela.',
+ 'settings.oauth.rotateSecretConfirm': 'Renovar',
+ 'settings.oauth.rotateSecretConfirming': 'Renovando…',
+ 'settings.oauth.rotateSecretDoneTitle': 'Novo segredo gerado',
+ 'settings.oauth.rotateSecretDoneWarning': 'Este segredo é exibido apenas uma vez. Copie-o agora e atualize sua aplicação — todas as sessões anteriores foram invalidadas.',
+ 'settings.oauth.activeSessions': 'Sessões OAuth ativas',
+ 'settings.oauth.sessionScopes': 'Escopos',
+ 'settings.oauth.sessionExpires': 'Expira',
+ 'settings.oauth.revoke': 'Revogar',
+ 'settings.oauth.revokeSession': 'Revogar sessão',
+ 'settings.oauth.revokeSessionMessage': 'Isso revogará imediatamente o acesso desta sessão OAuth.',
+ 'settings.oauth.modal.createTitle': 'Registrar cliente OAuth',
+ 'settings.oauth.modal.presets': 'Configurações rápidas',
+ 'settings.oauth.modal.clientName': 'Nome da aplicação',
+ 'settings.oauth.modal.clientNamePlaceholder': 'ex.: Claude Web, Meu app MCP',
+ 'settings.oauth.modal.redirectUris': 'URIs de redirecionamento',
+ 'settings.oauth.modal.redirectUrisPlaceholder': 'https://your-app.com/callback\nhttps://your-app.com/auth',
+ 'settings.oauth.modal.redirectUrisHint': 'Uma URI por linha. HTTPS obrigatório (localhost isento). Correspondência exata.',
+ 'settings.oauth.modal.scopes': 'Escopos permitidos',
+ 'settings.oauth.modal.scopesHint': 'list_trips e get_trip_summary estão sempre disponíveis — sem escopo necessário. Permitem à IA descobrir IDs de viagem.',
+ 'settings.oauth.modal.selectAll': 'Selecionar tudo',
+ 'settings.oauth.modal.deselectAll': 'Desmarcar tudo',
+ 'settings.oauth.modal.creating': 'Registrando…',
+ 'settings.oauth.modal.create': 'Registrar cliente',
+ 'settings.oauth.modal.createdTitle': 'Cliente registrado',
+ 'settings.oauth.modal.createdWarning': 'O segredo do cliente é exibido apenas uma vez. Copie-o agora — não pode ser recuperado.',
+ 'settings.oauth.toast.createError': 'Falha ao registrar cliente OAuth',
+ 'settings.oauth.toast.deleted': 'Cliente OAuth excluído',
+ 'settings.oauth.toast.deleteError': 'Falha ao excluir cliente OAuth',
+ 'settings.oauth.toast.revoked': 'Sessão revogada',
+ 'settings.oauth.toast.revokeError': 'Falha ao revogar sessão',
+ 'settings.oauth.toast.rotateError': 'Falha ao renovar segredo do cliente',
'settings.mustChangePassword': 'Você deve alterar sua senha antes de continuar. Defina uma nova senha abaixo.',
// Login
@@ -463,7 +503,7 @@ const br: Record = {
'admin.keyValid': 'Conectado',
'admin.keyInvalid': 'Inválida',
'admin.keySaved': 'Chaves de API salvas',
- 'admin.oidcTitle': 'Single Sign-On (OIDC)',
+ 'admin.oidcTitle': 'Login Único (OIDC)',
'admin.oidcSubtitle': 'Permitir login via provedores externos como Google, Apple, Authentik ou Keycloak.',
'admin.oidcDisplayName': 'Nome exibido',
'admin.oidcIssuer': 'URL do emissor',
@@ -513,7 +553,7 @@ const br: Record = {
'admin.addons.catalog.budget.description': 'Acompanhe despesas e planeje o orçamento da viagem',
'admin.addons.catalog.documents.name': 'Documentos',
'admin.addons.catalog.documents.description': 'Armazene e gerencie documentos de viagem',
- 'admin.addons.catalog.vacay.name': 'Vacay',
+ 'admin.addons.catalog.vacay.name': 'Férias',
'admin.addons.catalog.vacay.description': 'Planejador de férias pessoal com visão em calendário',
'admin.addons.catalog.atlas.name': 'Atlas',
'admin.addons.catalog.atlas.description': 'Mapa mundial com países visitados e estatísticas',
@@ -546,7 +586,7 @@ const br: Record = {
'admin.weather.requestsDesc': 'Grátis, sem chave de API',
'admin.weather.locationHint': 'O clima usa o primeiro lugar com coordenadas de cada dia. Se nenhum lugar estiver atribuído ao dia, qualquer lugar da lista serve como referência.',
- 'admin.tabs.audit': 'Audit',
+ 'admin.tabs.audit': 'Auditoria',
'admin.audit.subtitle': 'Eventos sensíveis de segurança e administração (backups, usuários, 2FA, configurações).',
'admin.audit.empty': 'Nenhum registro de auditoria.',
@@ -990,6 +1030,7 @@ const br: Record = {
'budget.totalBudget': 'Orçamento total',
'budget.byCategory': 'Por categoria',
'budget.editTooltip': 'Clique para editar',
+ 'budget.linkedToReservation': 'Vinculado a uma reserva — edite o nome por lá',
'budget.confirm.deleteCategory': 'Excluir a categoria "{name}" com {count} lançamento(s)?',
'budget.deleteCategory': 'Excluir categoria',
'budget.perPerson': 'Por pessoa',
@@ -1090,6 +1131,9 @@ const br: Record = {
'packing.template': 'Modelo',
'packing.templateApplied': '{count} itens adicionados do modelo',
'packing.templateError': 'Falha ao aplicar modelo',
+ 'packing.saveAsTemplate': 'Salvar como modelo',
+ 'packing.templateName': 'Nome do modelo',
+ 'packing.templateSaved': 'Lista de bagagem salva como modelo',
'packing.bags': 'Malas',
'packing.noBag': 'Sem mala',
'packing.totalWeight': 'Peso total',
@@ -1433,6 +1477,7 @@ const br: Record = {
'memories.title': 'Fotos',
'memories.notConnected': 'Immich não conectado',
'memories.notConnectedHint': 'Conecte sua instância Immich nas Configurações para ver suas fotos de viagem aqui.',
+ 'memories.notConnectedMultipleHint': 'Conecte um destes provedores de fotos: {provider_names} nas Configurações para poder adicionar fotos a esta viagem.',
'memories.noDates': 'Adicione datas à sua viagem para carregar fotos.',
'memories.noPhotos': 'Nenhuma foto encontrada',
'memories.noPhotosHint': 'Nenhuma foto encontrada no Immich para o período desta viagem.',
@@ -1443,23 +1488,32 @@ const br: Record = {
'memories.reviewTitle': 'Revise suas fotos',
'memories.reviewHint': 'Clique nas fotos para excluí-las do compartilhamento.',
'memories.shareCount': 'Compartilhar {count} fotos',
- 'memories.immichUrl': 'URL do servidor Immich',
- 'memories.immichApiKey': 'Chave da API',
+ 'memories.providerUrl': 'URL do servidor',
+ 'memories.providerApiKey': 'Chave de API',
+ 'memories.providerUsername': 'Nome de usuário',
+ 'memories.providerPassword': 'Senha',
+ 'memories.providerOTP': 'Código MFA (se habilitado)',
+ 'memories.skipSSLVerification': 'Pular verificação de certificado SSL',
+ 'memories.providerUrlHintSynology': 'Inclua o caminho do aplicativo Photos na URL, ex. https://nas:5001/photo',
'memories.testConnection': 'Testar conexão',
'memories.testFirst': 'Teste a conexão primeiro',
'memories.connected': 'Conectado',
'memories.disconnected': 'Não conectado',
'memories.connectionSuccess': 'Conectado ao Immich',
'memories.connectionError': 'Não foi possível conectar ao Immich',
- 'memories.saved': 'Configurações do Immich salvas',
+ 'memories.saved': 'Configurações do {provider_name} salvas',
+ 'memories.providerDisconnectedBanner': 'Sua conexão com {provider_name} foi perdida. Reconecte nas Configurações para ver as fotos.',
+ 'memories.saveError': 'Não foi possível salvar as configurações de {provider_name}',
'memories.addPhotos': 'Adicionar fotos',
'memories.linkAlbum': 'Vincular álbum',
'memories.selectAlbum': 'Selecionar álbum do Immich',
+ 'memories.selectAlbumMultiple': 'Selecionar álbum',
'memories.noAlbums': 'Nenhum álbum encontrado',
'memories.syncAlbum': 'Sincronizar álbum',
'memories.unlinkAlbum': 'Desvincular',
'memories.photos': 'fotos',
'memories.selectPhotos': 'Selecionar fotos do Immich',
+ 'memories.selectPhotosMultiple': 'Selecionar fotos',
'memories.selectHint': 'Toque nas fotos para selecioná-las.',
'memories.selected': 'selecionadas',
'memories.addSelected': 'Adicionar {count} fotos',
@@ -1477,9 +1531,10 @@ const br: Record = {
// Permissions
'admin.tabs.permissions': 'Permissões',
- 'admin.tabs.mcpTokens': 'Tokens MCP',
- 'admin.mcpTokens.title': 'Tokens MCP',
- 'admin.mcpTokens.subtitle': 'Gerenciar tokens de API de todos os usuários',
+ 'admin.tabs.mcpTokens': 'Acesso MCP',
+ 'admin.mcpTokens.title': 'Acesso MCP',
+ 'admin.mcpTokens.subtitle': 'Gerenciar sessões OAuth e tokens de API de todos os usuários',
+ 'admin.mcpTokens.sectionTitle': 'Tokens de API',
'admin.mcpTokens.owner': 'Proprietário',
'admin.mcpTokens.tokenName': 'Nome do Token',
'admin.mcpTokens.created': 'Criado',
@@ -1491,6 +1546,17 @@ const br: Record = {
'admin.mcpTokens.deleteSuccess': 'Token excluído',
'admin.mcpTokens.deleteError': 'Falha ao excluir token',
'admin.mcpTokens.loadError': 'Falha ao carregar tokens',
+ 'admin.oauthSessions.sectionTitle': 'Sessões OAuth',
+ 'admin.oauthSessions.clientName': 'Cliente',
+ 'admin.oauthSessions.owner': 'Proprietário',
+ 'admin.oauthSessions.scopes': 'Permissões',
+ 'admin.oauthSessions.created': 'Criado',
+ 'admin.oauthSessions.empty': 'Nenhuma sessão OAuth ativa',
+ 'admin.oauthSessions.revokeTitle': 'Revogar sessão',
+ 'admin.oauthSessions.revokeMessage': 'Esta sessão OAuth será revogada imediatamente. O cliente perderá o acesso MCP.',
+ 'admin.oauthSessions.revokeSuccess': 'Sessão revogada',
+ 'admin.oauthSessions.revokeError': 'Falha ao revogar sessão',
+ 'admin.oauthSessions.loadError': 'Falha ao carregar sessões OAuth',
'perm.title': 'Configurações de Permissões',
'perm.subtitle': 'Controle quem pode realizar ações no aplicativo',
'perm.saved': 'Configurações de permissões salvas',
@@ -1563,6 +1629,8 @@ const br: Record = {
'notifications.markUnread': 'Marcar como não lido',
'notifications.delete': 'Excluir',
'notifications.system': 'Sistema',
+ 'notifications.synologySessionCleared.title': 'Synology Photos desconectado',
+ 'notifications.synologySessionCleared.text': 'Seu servidor ou conta foi alterado — vá para Configurações para testar sua conexão novamente.',
'memories.error.loadAlbums': 'Falha ao carregar álbuns',
'memories.error.linkAlbum': 'Falha ao vincular álbum',
'memories.error.unlinkAlbum': 'Falha ao desvincular álbum',
@@ -1918,6 +1986,70 @@ const br: Record = {
'dayplan.mobile.createNew': 'Criar novo lugar',
'admin.addons.catalog.journey.name': 'Jornada',
'admin.addons.catalog.journey.description': 'Rastreamento de viagens e diário de viajante com check-ins, fotos e histórias diárias',
+
+ // OAuth scope groups
+ 'oauth.scope.group.trips': 'Viagens',
+ 'oauth.scope.group.places': 'Locais',
+ 'oauth.scope.group.atlas': 'Atlas',
+ 'oauth.scope.group.packing': 'Bagagem',
+ 'oauth.scope.group.todos': 'Tarefas',
+ 'oauth.scope.group.budget': 'Orçamento',
+ 'oauth.scope.group.reservations': 'Reservas',
+ 'oauth.scope.group.collab': 'Colaboração',
+ 'oauth.scope.group.notifications': 'Notificações',
+ 'oauth.scope.group.vacay': 'Férias',
+ 'oauth.scope.group.geo': 'Geo',
+ 'oauth.scope.group.weather': 'Clima',
+
+ // OAuth scope labels & descriptions
+ 'oauth.scope.trips:read.label': 'Ver viagens e itinerários',
+ 'oauth.scope.trips:read.description': 'Ler viagens, dias, notas e membros',
+ 'oauth.scope.trips:write.label': 'Editar viagens e itinerários',
+ 'oauth.scope.trips:write.description': 'Criar e atualizar viagens, dias, notas e gerenciar membros',
+ 'oauth.scope.trips:delete.label': 'Excluir viagens',
+ 'oauth.scope.trips:delete.description': 'Excluir viagens permanentemente — esta ação é irreversível',
+ 'oauth.scope.trips:share.label': 'Gerenciar links de compartilhamento',
+ 'oauth.scope.trips:share.description': 'Criar, atualizar e revogar links de compartilhamento públicos',
+ 'oauth.scope.places:read.label': 'Ver locais e dados do mapa',
+ 'oauth.scope.places:read.description': 'Ler locais, atribuições de dias, tags e categorias',
+ 'oauth.scope.places:write.label': 'Gerenciar locais',
+ 'oauth.scope.places:write.description': 'Criar, atualizar e excluir locais, atribuições e tags',
+ 'oauth.scope.atlas:read.label': 'Ver Atlas',
+ 'oauth.scope.atlas:read.description': 'Ler países visitados, regiões e lista de desejos',
+ 'oauth.scope.atlas:write.label': 'Gerenciar Atlas',
+ 'oauth.scope.atlas:write.description': 'Marcar países e regiões como visitados, gerenciar lista de desejos',
+ 'oauth.scope.packing:read.label': 'Ver listas de bagagem',
+ 'oauth.scope.packing:read.description': 'Ler itens, malas e responsáveis por categoria',
+ 'oauth.scope.packing:write.label': 'Gerenciar listas de bagagem',
+ 'oauth.scope.packing:write.description': 'Adicionar, atualizar, excluir, marcar e reordenar itens e malas',
+ 'oauth.scope.todos:read.label': 'Ver listas de tarefas',
+ 'oauth.scope.todos:read.description': 'Ler tarefas da viagem e responsáveis por categoria',
+ 'oauth.scope.todos:write.label': 'Gerenciar listas de tarefas',
+ 'oauth.scope.todos:write.description': 'Criar, atualizar, marcar, excluir e reordenar tarefas',
+ 'oauth.scope.budget:read.label': 'Ver orçamento',
+ 'oauth.scope.budget:read.description': 'Ler itens de orçamento e detalhamento de despesas',
+ 'oauth.scope.budget:write.label': 'Gerenciar orçamento',
+ 'oauth.scope.budget:write.description': 'Criar, atualizar e excluir itens de orçamento',
+ 'oauth.scope.reservations:read.label': 'Ver reservas',
+ 'oauth.scope.reservations:read.description': 'Ler reservas e detalhes de acomodação',
+ 'oauth.scope.reservations:write.label': 'Gerenciar reservas',
+ 'oauth.scope.reservations:write.description': 'Criar, atualizar, excluir e reordenar reservas',
+ 'oauth.scope.collab:read.label': 'Ver colaboração',
+ 'oauth.scope.collab:read.description': 'Ler notas colaborativas, enquetes e mensagens',
+ 'oauth.scope.collab:write.label': 'Gerenciar colaboração',
+ 'oauth.scope.collab:write.description': 'Criar, atualizar e excluir notas, enquetes e mensagens',
+ 'oauth.scope.notifications:read.label': 'Ver notificações',
+ 'oauth.scope.notifications:read.description': 'Ler notificações e contagens não lidas',
+ 'oauth.scope.notifications:write.label': 'Gerenciar notificações',
+ 'oauth.scope.notifications:write.description': 'Marcar notificações como lidas e respondê-las',
+ 'oauth.scope.vacay:read.label': 'Ver planos de férias',
+ 'oauth.scope.vacay:read.description': 'Ler dados de planejamento de férias, entradas e estatísticas',
+ 'oauth.scope.vacay:write.label': 'Gerenciar planos de férias',
+ 'oauth.scope.vacay:write.description': 'Criar e gerenciar entradas de férias, feriados e planos de equipe',
+ 'oauth.scope.geo:read.label': 'Mapas e geocodificação',
+ 'oauth.scope.geo:read.description': 'Pesquisar locais, resolver URLs de mapa e geocodificar coordenadas',
+ 'oauth.scope.weather:read.label': 'Previsão do tempo',
+ 'oauth.scope.weather:read.description': 'Obter previsão do tempo para locais e datas da viagem',
}
export default br
diff --git a/client/src/i18n/translations/cs.ts b/client/src/i18n/translations/cs.ts
index 24bef1ac..43369867 100644
--- a/client/src/i18n/translations/cs.ts
+++ b/client/src/i18n/translations/cs.ts
@@ -181,6 +181,7 @@ const cs: Record = {
'settings.mcp.endpoint': 'MCP endpoint',
'settings.mcp.clientConfig': 'Konfigurace klienta',
'settings.mcp.clientConfigHint': 'Nahraďte API tokenem ze seznamu níže. Cestu k npx může být nutné upravit pro váš systém (např. C:\\PROGRA~1\\nodejs\\npx.cmd ve Windows).',
+ 'settings.mcp.clientConfigHintOAuth': 'Nahraďte a přihlašovacími údaji ze klienta OAuth 2.1, který jste vytvořili výše. mcp-remote při prvním připojení otevře prohlížeč pro dokončení autorizace. Cestu k npx může být nutné upravit pro váš systém (např. C:\\PROGRA~1\\nodejs\\npx.cmd ve Windows).',
'settings.mcp.copy': 'Kopírovat',
'settings.mcp.copied': 'Zkopírováno!',
'settings.mcp.apiTokens': 'API tokeny',
@@ -202,6 +203,48 @@ const cs: Record = {
'settings.mcp.toast.createError': 'Nepodařilo se vytvořit token',
'settings.mcp.toast.deleted': 'Token smazán',
'settings.mcp.toast.deleteError': 'Nepodařilo se smazat token',
+ 'settings.mcp.apiTokensDeprecated': 'API tokeny jsou zastaralé a budou odstraněny v budoucí verzi. Místo toho použijte klienty OAuth 2.1.',
+ 'settings.oauth.clients': 'Klienti OAuth 2.1',
+ 'settings.oauth.clientsHint': 'Zaregistrujte klienty OAuth 2.1, aby se aplikace MCP třetích stran (Claude Web, Cursor atd.) mohly připojit bez statických tokenů.',
+ 'settings.oauth.createClient': 'Nový klient',
+ 'settings.oauth.noClients': 'Žádní klienti OAuth nejsou zaregistrováni.',
+ 'settings.oauth.clientId': 'ID klienta',
+ 'settings.oauth.clientSecret': 'Tajný klíč klienta',
+ 'settings.oauth.deleteClient': 'Smazat klienta',
+ 'settings.oauth.deleteClientMessage': 'Tento klient a všechny aktivní relace budou trvale odstraněny. Jakákoliv aplikace, která ho používá, okamžitě ztratí přístup.',
+ 'settings.oauth.rotateSecret': 'Obnovit tajný klíč',
+ 'settings.oauth.rotateSecretMessage': 'Bude vygenerován nový tajný klíč klienta a všechny stávající relace budou okamžitě zneplatněny. Aktualizujte aplikaci před zavřením tohoto dialogu.',
+ 'settings.oauth.rotateSecretConfirm': 'Obnovit',
+ 'settings.oauth.rotateSecretConfirming': 'Obnovování…',
+ 'settings.oauth.rotateSecretDoneTitle': 'Nový tajný klíč vygenerován',
+ 'settings.oauth.rotateSecretDoneWarning': 'Tento tajný klíč se zobrazí pouze jednou. Zkopírujte ho nyní a aktualizujte aplikaci — všechny předchozí relace byly zneplatněny.',
+ 'settings.oauth.activeSessions': 'Aktivní relace OAuth',
+ 'settings.oauth.sessionScopes': 'Oprávnění',
+ 'settings.oauth.sessionExpires': 'Vyprší',
+ 'settings.oauth.revoke': 'Odvolat',
+ 'settings.oauth.revokeSession': 'Odvolat relaci',
+ 'settings.oauth.revokeSessionMessage': 'Tím se okamžitě odvolá přístup pro tuto relaci OAuth.',
+ 'settings.oauth.modal.createTitle': 'Zaregistrovat klienta OAuth',
+ 'settings.oauth.modal.presets': 'Rychlá nastavení',
+ 'settings.oauth.modal.clientName': 'Název aplikace',
+ 'settings.oauth.modal.clientNamePlaceholder': 'např. Claude Web, Moje MCP aplikace',
+ 'settings.oauth.modal.redirectUris': 'Přesměrovací URI',
+ 'settings.oauth.modal.redirectUrisPlaceholder': 'https://your-app.com/callback\nhttps://your-app.com/auth',
+ 'settings.oauth.modal.redirectUrisHint': 'Jedno URI na řádek. Vyžadováno HTTPS (localhost vyjmuto). Vyžadována přesná shoda.',
+ 'settings.oauth.modal.scopes': 'Povolená oprávnění',
+ 'settings.oauth.modal.scopesHint': 'list_trips a get_trip_summary jsou vždy dostupné — bez požadovaného oprávnění. Umožňují AI zjistit potřebná ID výletů.',
+ 'settings.oauth.modal.selectAll': 'Vybrat vše',
+ 'settings.oauth.modal.deselectAll': 'Zrušit výběr',
+ 'settings.oauth.modal.creating': 'Registrování…',
+ 'settings.oauth.modal.create': 'Zaregistrovat klienta',
+ 'settings.oauth.modal.createdTitle': 'Klient zaregistrován',
+ 'settings.oauth.modal.createdWarning': 'Tajný klíč klienta se zobrazí pouze jednou. Zkopírujte ho nyní — nelze ho obnovit.',
+ 'settings.oauth.toast.createError': 'Registrace klienta OAuth se nezdařila',
+ 'settings.oauth.toast.deleted': 'Klient OAuth smazán',
+ 'settings.oauth.toast.deleteError': 'Smazání klienta OAuth se nezdařilo',
+ 'settings.oauth.toast.revoked': 'Relace odvolána',
+ 'settings.oauth.toast.revokeError': 'Odvolání relace se nezdařilo',
+ 'settings.oauth.toast.rotateError': 'Obnovení tajného klíče klienta se nezdařilo',
'settings.account': 'Účet',
'settings.about': 'O aplikaci',
'settings.about.reportBug': 'Nahlásit chybu',
@@ -274,9 +317,6 @@ const cs: Record = {
'admin.notifications.none': 'Vypnuto',
'admin.notifications.email': 'E-mail (SMTP)',
'admin.notifications.webhook': 'Webhook',
- 'admin.notifications.events': 'Události oznámení',
- 'admin.notifications.eventsHint': 'Vyberte, které události spouštějí oznámení pro všechny uživatele.',
- 'admin.notifications.configureFirst': 'Nejprve nakonfigurujte nastavení SMTP nebo webhooku níže, poté povolte události.',
'admin.notifications.save': 'Uložit nastavení oznámení',
'admin.notifications.saved': 'Nastavení oznámení uloženo',
'admin.notifications.testWebhook': 'Odeslat testovací webhook',
@@ -550,9 +590,10 @@ const cs: Record = {
'admin.audit.col.details': 'Detaily',
// MCP Tokens
- 'admin.tabs.mcpTokens': 'MCP tokeny',
- 'admin.mcpTokens.title': 'MCP tokeny',
- 'admin.mcpTokens.subtitle': 'Správa API tokenů všech uživatelů',
+ 'admin.tabs.mcpTokens': 'MCP přístup',
+ 'admin.mcpTokens.title': 'MCP přístup',
+ 'admin.mcpTokens.subtitle': 'Správa OAuth relací a API tokenů všech uživatelů',
+ 'admin.mcpTokens.sectionTitle': 'API tokeny',
'admin.mcpTokens.owner': 'Vlastník',
'admin.mcpTokens.tokenName': 'Název tokenu',
'admin.mcpTokens.created': 'Vytvořen',
@@ -564,6 +605,17 @@ const cs: Record = {
'admin.mcpTokens.deleteSuccess': 'Token smazán',
'admin.mcpTokens.deleteError': 'Nepodařilo se smazat token',
'admin.mcpTokens.loadError': 'Nepodařilo se načíst tokeny',
+ 'admin.oauthSessions.sectionTitle': 'OAuth relace',
+ 'admin.oauthSessions.clientName': 'Klient',
+ 'admin.oauthSessions.owner': 'Vlastník',
+ 'admin.oauthSessions.scopes': 'Oprávnění',
+ 'admin.oauthSessions.created': 'Vytvořeno',
+ 'admin.oauthSessions.empty': 'Žádné aktivní OAuth relace',
+ 'admin.oauthSessions.revokeTitle': 'Zrušit relaci',
+ 'admin.oauthSessions.revokeMessage': 'Tato OAuth relace bude okamžitě zrušena. Klient ztratí přístup k MCP.',
+ 'admin.oauthSessions.revokeSuccess': 'Relace zrušena',
+ 'admin.oauthSessions.revokeError': 'Nepodařilo se zrušit relaci',
+ 'admin.oauthSessions.loadError': 'Nepodařilo se načíst OAuth relace',
// GitHub
'admin.tabs.github': 'GitHub',
@@ -1007,6 +1059,7 @@ const cs: Record = {
'budget.totalBudget': 'Celkový rozpočet',
'budget.byCategory': 'Podle kategorie',
'budget.editTooltip': 'Klikněte pro úpravu',
+ 'budget.linkedToReservation': 'Propojeno s rezervací — název upravte tam',
'budget.confirm.deleteCategory': 'Opravdu chcete smazat kategorii „{name}” s {count} položkami?',
'budget.deleteCategory': 'Smazat kategorii',
'budget.perPerson': 'Na osobu',
@@ -1097,7 +1150,7 @@ const cs: Record = {
'packing.menuCheckAll': 'Označit vše',
'packing.menuUncheckAll': 'Odznačit vše',
'packing.menuDeleteCat': 'Smazat kategorii',
- 'packing.assignUser': 'Přiřadit uživateli',
+ 'packing.assignUser': 'Přiřadit uživatele',
'packing.noMembers': 'Žádní členové cesty',
'packing.addItem': 'Přidat položku',
'packing.addItemPlaceholder': 'Název položky...',
@@ -1107,6 +1160,9 @@ const cs: Record = {
'packing.template': 'Šablona',
'packing.templateApplied': '{count} položek přidáno ze šablony',
'packing.templateError': 'Šablonu se nepodařilo použít',
+ 'packing.saveAsTemplate': 'Uložit jako šablonu',
+ 'packing.templateName': 'Název šablony',
+ 'packing.templateSaved': 'Seznam balení uložen jako šablona',
'packing.bags': 'Zavazadla',
'packing.noBag': 'Nepřiřazeno',
'packing.totalWeight': 'Celková váha',
@@ -1380,6 +1436,7 @@ const cs: Record = {
'memories.title': 'Fotky',
'memories.notConnected': 'Immich není připojen',
'memories.notConnectedHint': 'Připojte svoji instanci Immich v Nastavení, abyste zde viděli fotky z cest.',
+ 'memories.notConnectedMultipleHint': 'Pro přidání fotek k tomuto výletu připojte v Nastavení jednoho z těchto poskytovatelů fotek: {provider_names}.',
'memories.noDates': 'Přidejte data k cestě pro načtení fotek.',
'memories.noPhotos': 'Nenalezeny žádné fotky',
'memories.noPhotosHint': 'V Immich nebyly nalezeny žádné fotky pro období této cesty.',
@@ -1390,23 +1447,32 @@ const cs: Record = {
'memories.reviewTitle': 'Zkontrolujte své fotky',
'memories.reviewHint': 'Klikněte na fotky pro vyloučení ze sdílení.',
'memories.shareCount': 'Sdílet {count} fotek',
- 'memories.immichUrl': 'URL serveru Immich',
- 'memories.immichApiKey': 'API klíč',
+ 'memories.providerUrl': 'URL serveru',
+ 'memories.providerApiKey': 'API klíč',
+ 'memories.providerUsername': 'Uživatelské jméno',
+ 'memories.providerPassword': 'Heslo',
+ 'memories.providerOTP': 'MFA kód (pokud je povoleno)',
+ 'memories.skipSSLVerification': 'Přeskočit ověření SSL certifikátu',
+ 'memories.providerUrlHintSynology': 'Zahrňte cestu aplikace Photos do URL, např. https://nas:5001/photo',
'memories.testConnection': 'Otestovat připojení',
'memories.testFirst': 'Nejprve otestujte připojení',
'memories.connected': 'Připojeno',
'memories.disconnected': 'Nepřipojeno',
'memories.connectionSuccess': 'Připojeno k Immich',
'memories.connectionError': 'Nepodařilo se připojit k Immich',
- 'memories.saved': 'Nastavení Immich uloženo',
+ 'memories.saved': 'Nastavení {provider_name} uloženo',
+ 'memories.providerDisconnectedBanner': 'Vaše připojení k {provider_name} bylo ztraceno. Obnovte připojení v Nastavení pro zobrazení fotek.',
+ 'memories.saveError': 'Nepodařilo se uložit nastavení {provider_name}',
'memories.addPhotos': 'Přidat fotky',
'memories.linkAlbum': 'Propojit album',
'memories.selectAlbum': 'Vybrat album z Immich',
+ 'memories.selectAlbumMultiple': 'Vybrat album',
'memories.noAlbums': 'Žádná alba nenalezena',
'memories.syncAlbum': 'Synchronizovat album',
'memories.unlinkAlbum': 'Odpojit',
'memories.photos': 'fotek',
'memories.selectPhotos': 'Vybrat fotky z Immich',
+ 'memories.selectPhotosMultiple': 'Vybrat fotky',
'memories.selectHint': 'Klepněte na fotky pro jejich výběr.',
'memories.selected': 'vybráno',
'memories.addSelected': 'Přidat {count} fotek',
@@ -1566,6 +1632,8 @@ const cs: Record = {
'notifications.markUnread': 'Označit jako nepřečtené',
'notifications.delete': 'Smazat',
'notifications.system': 'Systém',
+ 'notifications.synologySessionCleared.title': 'Synology Photos odpojeno',
+ 'notifications.synologySessionCleared.text': 'Váš server nebo účet se změnil — přejděte do Nastavení a znovu otestujte připojení.',
'settings.mustChangePassword': 'Před pokračováním musíte změnit heslo.',
'atlas.searchCountry': 'Hledat zemi...',
'memories.error.loadAlbums': 'Načtení alb se nezdařilo',
@@ -1923,6 +1991,69 @@ const cs: Record = {
'dayplan.mobile.createNew': 'Vytvořit nové místo',
'admin.addons.catalog.journey.name': 'Cestovní deník',
'admin.addons.catalog.journey.description': 'Sledování cest a cestovní deník s odbaveními, fotkami a denními příběhy',
+ // OAuth scope groups
+ 'oauth.scope.group.trips': 'Výlety',
+ 'oauth.scope.group.places': 'Místa',
+ 'oauth.scope.group.atlas': 'Atlas',
+ 'oauth.scope.group.packing': 'Balení',
+ 'oauth.scope.group.todos': 'Úkoly',
+ 'oauth.scope.group.budget': 'Rozpočet',
+ 'oauth.scope.group.reservations': 'Rezervace',
+ 'oauth.scope.group.collab': 'Spolupráce',
+ 'oauth.scope.group.notifications': 'Oznámení',
+ 'oauth.scope.group.vacay': 'Dovolená',
+ 'oauth.scope.group.geo': 'Geo',
+ 'oauth.scope.group.weather': 'Počasí',
+
+ // OAuth scope labels & descriptions
+ 'oauth.scope.trips:read.label': 'Zobrazit výlety a itineráře',
+ 'oauth.scope.trips:read.description': 'Číst výlety, dny, poznámky a členy',
+ 'oauth.scope.trips:write.label': 'Upravit výlety a itineráře',
+ 'oauth.scope.trips:write.description': 'Vytvářet a aktualizovat výlety, dny, poznámky a spravovat členy',
+ 'oauth.scope.trips:delete.label': 'Mazat výlety',
+ 'oauth.scope.trips:delete.description': 'Trvale smazat celé výlety — tato akce je nevratná',
+ 'oauth.scope.trips:share.label': 'Spravovat sdílené odkazy',
+ 'oauth.scope.trips:share.description': 'Vytvářet, aktualizovat a rušit veřejné sdílené odkazy',
+ 'oauth.scope.places:read.label': 'Zobrazit místa a mapová data',
+ 'oauth.scope.places:read.description': 'Číst místa, denní přiřazení, štítky a kategorie',
+ 'oauth.scope.places:write.label': 'Spravovat místa',
+ 'oauth.scope.places:write.description': 'Vytvářet, aktualizovat a mazat místa, přiřazení a štítky',
+ 'oauth.scope.atlas:read.label': 'Zobrazit Atlas',
+ 'oauth.scope.atlas:read.description': 'Číst navštívené země, regiony a seznam přání',
+ 'oauth.scope.atlas:write.label': 'Spravovat Atlas',
+ 'oauth.scope.atlas:write.description': 'Označovat navštívené země a regiony, spravovat seznam přání',
+ 'oauth.scope.packing:read.label': 'Zobrazit seznamy balení',
+ 'oauth.scope.packing:read.description': 'Číst položky, tašky a přiřazení kategorií',
+ 'oauth.scope.packing:write.label': 'Spravovat seznamy balení',
+ 'oauth.scope.packing:write.description': 'Přidávat, aktualizovat, mazat, označovat a řadit položky a tašky',
+ 'oauth.scope.todos:read.label': 'Zobrazit seznamy úkolů',
+ 'oauth.scope.todos:read.description': 'Číst úkoly výletu a přiřazení kategorií',
+ 'oauth.scope.todos:write.label': 'Spravovat seznamy úkolů',
+ 'oauth.scope.todos:write.description': 'Vytvářet, aktualizovat, označovat, mazat a řadit úkoly',
+ 'oauth.scope.budget:read.label': 'Zobrazit rozpočet',
+ 'oauth.scope.budget:read.description': 'Číst položky rozpočtu a přehled výdajů',
+ 'oauth.scope.budget:write.label': 'Spravovat rozpočet',
+ 'oauth.scope.budget:write.description': 'Vytvářet, aktualizovat a mazat položky rozpočtu',
+ 'oauth.scope.reservations:read.label': 'Zobrazit rezervace',
+ 'oauth.scope.reservations:read.description': 'Číst rezervace a podrobnosti ubytování',
+ 'oauth.scope.reservations:write.label': 'Spravovat rezervace',
+ 'oauth.scope.reservations:write.description': 'Vytvářet, aktualizovat, mazat a řadit rezervace',
+ 'oauth.scope.collab:read.label': 'Zobrazit spolupráci',
+ 'oauth.scope.collab:read.description': 'Číst poznámky, ankety a zprávy spolupráce',
+ 'oauth.scope.collab:write.label': 'Spravovat spolupráci',
+ 'oauth.scope.collab:write.description': 'Vytvářet, aktualizovat a mazat poznámky, ankety a zprávy',
+ 'oauth.scope.notifications:read.label': 'Zobrazit oznámení',
+ 'oauth.scope.notifications:read.description': 'Číst oznámení v aplikaci a počty nepřečtených',
+ 'oauth.scope.notifications:write.label': 'Spravovat oznámení',
+ 'oauth.scope.notifications:write.description': 'Označovat oznámení jako přečtená a reagovat na ně',
+ 'oauth.scope.vacay:read.label': 'Zobrazit plány dovolené',
+ 'oauth.scope.vacay:read.description': 'Číst data plánování dovolené, záznamy a statistiky',
+ 'oauth.scope.vacay:write.label': 'Spravovat plány dovolené',
+ 'oauth.scope.vacay:write.description': 'Vytvářet a spravovat záznamy dovolené, svátky a týmové plány',
+ 'oauth.scope.geo:read.label': 'Mapy a geokódování',
+ 'oauth.scope.geo:read.description': 'Vyhledávat místa, řešit URL map a zpětně geokódovat souřadnice',
+ 'oauth.scope.weather:read.label': 'Předpovědi počasí',
+ 'oauth.scope.weather:read.description': 'Získávat předpovědi počasí pro místa a data výletu',
}
export default cs
diff --git a/client/src/i18n/translations/de.ts b/client/src/i18n/translations/de.ts
index e50759e8..a6fe1cd9 100644
--- a/client/src/i18n/translations/de.ts
+++ b/client/src/i18n/translations/de.ts
@@ -228,6 +228,7 @@ const de: Record = {
'settings.mcp.endpoint': 'MCP-Endpunkt',
'settings.mcp.clientConfig': 'Client-Konfiguration',
'settings.mcp.clientConfigHint': 'Ersetze durch ein API-Token aus der Liste unten. Der Pfad zu npx muss ggf. für dein System angepasst werden (z. B. C:\\PROGRA~1\\nodejs\\npx.cmd unter Windows).',
+ 'settings.mcp.clientConfigHintOAuth': 'Ersetze und durch die Zugangsdaten des oben erstellten OAuth 2.1-Clients. mcp-remote öffnet beim ersten Verbindungsaufbau deinen Browser zur Autorisierung. Der Pfad zu npx muss ggf. für dein System angepasst werden (z. B. C:\\PROGRA~1\\nodejs\\npx.cmd unter Windows).',
'settings.mcp.copy': 'Kopieren',
'settings.mcp.copied': 'Kopiert!',
'settings.mcp.apiTokens': 'API-Tokens',
@@ -249,6 +250,48 @@ const de: Record = {
'settings.mcp.toast.createError': 'Token konnte nicht erstellt werden',
'settings.mcp.toast.deleted': 'Token gelöscht',
'settings.mcp.toast.deleteError': 'Token konnte nicht gelöscht werden',
+ 'settings.mcp.apiTokensDeprecated': 'API-Tokens sind veraltet und werden in einer zukünftigen Version entfernt. Bitte verwende stattdessen OAuth 2.1-Clients.',
+ 'settings.oauth.clients': 'OAuth 2.1-Clients',
+ 'settings.oauth.clientsHint': 'Registriere OAuth 2.1-Clients, damit externe MCP-Anwendungen (Claude Web, Cursor usw.) sich ohne statische Tokens verbinden können.',
+ 'settings.oauth.createClient': 'Neuer Client',
+ 'settings.oauth.noClients': 'Keine OAuth-Clients registriert.',
+ 'settings.oauth.clientId': 'Client-ID',
+ 'settings.oauth.clientSecret': 'Client-Secret',
+ 'settings.oauth.deleteClient': 'Client löschen',
+ 'settings.oauth.deleteClientMessage': 'Dieser Client und alle aktiven Sessions werden dauerhaft entfernt. Jede Anwendung, die ihn nutzt, verliert sofort den Zugriff.',
+ 'settings.oauth.rotateSecret': 'Secret erneuern',
+ 'settings.oauth.rotateSecretMessage': 'Ein neues Client-Secret wird generiert und alle bestehenden Sessions werden sofort ungültig. Aktualisiere deine Anwendung, bevor du diesen Dialog schließt.',
+ 'settings.oauth.rotateSecretConfirm': 'Erneuern',
+ 'settings.oauth.rotateSecretConfirming': 'Wird erneuert…',
+ 'settings.oauth.rotateSecretDoneTitle': 'Neues Secret generiert',
+ 'settings.oauth.rotateSecretDoneWarning': 'Dieses Secret wird nur einmal angezeigt. Kopiere es jetzt und aktualisiere deine Anwendung — alle vorherigen Sessions wurden ungültig gemacht.',
+ 'settings.oauth.activeSessions': 'Aktive OAuth-Sessions',
+ 'settings.oauth.sessionScopes': 'Berechtigungen',
+ 'settings.oauth.sessionExpires': 'Läuft ab',
+ 'settings.oauth.revoke': 'Widerrufen',
+ 'settings.oauth.revokeSession': 'Session widerrufen',
+ 'settings.oauth.revokeSessionMessage': 'Dadurch wird der Zugriff für diese OAuth-Session sofort widerrufen.',
+ 'settings.oauth.modal.createTitle': 'OAuth-Client registrieren',
+ 'settings.oauth.modal.presets': 'Schnellvorlagen',
+ 'settings.oauth.modal.clientName': 'Anwendungsname',
+ 'settings.oauth.modal.clientNamePlaceholder': 'z. B. Claude Web, Meine MCP-App',
+ 'settings.oauth.modal.redirectUris': 'Redirect-URIs',
+ 'settings.oauth.modal.redirectUrisPlaceholder': 'https://your-app.com/callback\nhttps://your-app.com/auth',
+ 'settings.oauth.modal.redirectUrisHint': 'Eine URI pro Zeile. HTTPS erforderlich (localhost ausgenommen). Exakte Übereinstimmung erforderlich.',
+ 'settings.oauth.modal.scopes': 'Erlaubte Berechtigungen',
+ 'settings.oauth.modal.scopesHint': 'list_trips und get_trip_summary sind immer verfügbar — keine Berechtigung nötig. Sie helfen der KI, Trip-IDs zu ermitteln.',
+ 'settings.oauth.modal.selectAll': 'Alle auswählen',
+ 'settings.oauth.modal.deselectAll': 'Alle abwählen',
+ 'settings.oauth.modal.creating': 'Wird registriert…',
+ 'settings.oauth.modal.create': 'Client registrieren',
+ 'settings.oauth.modal.createdTitle': 'Client registriert',
+ 'settings.oauth.modal.createdWarning': 'Das Client-Secret wird nur einmal angezeigt. Kopiere es jetzt — es kann nicht wiederhergestellt werden.',
+ 'settings.oauth.toast.createError': 'OAuth-Client konnte nicht registriert werden',
+ 'settings.oauth.toast.deleted': 'OAuth-Client gelöscht',
+ 'settings.oauth.toast.deleteError': 'OAuth-Client konnte nicht gelöscht werden',
+ 'settings.oauth.toast.revoked': 'Session widerrufen',
+ 'settings.oauth.toast.revokeError': 'Session konnte nicht widerrufen werden',
+ 'settings.oauth.toast.rotateError': 'Client-Secret konnte nicht erneuert werden',
'settings.account': 'Konto',
'settings.about': 'Über',
'settings.about.reportBug': 'Bug melden',
@@ -454,11 +497,11 @@ const de: Record = {
'admin.requireMfaHint': 'Benutzer ohne 2FA müssen die Einrichtung unter Einstellungen abschließen, bevor sie die App nutzen können.',
'admin.apiKeys': 'API-Schlüssel',
'admin.apiKeysHint': 'Optional. Aktiviert erweiterte Ortsdaten wie Fotos und Wetter.',
- 'admin.mapsKey': 'Google Maps API Key',
+ 'admin.mapsKey': 'Google Maps API-Schlüssel',
'admin.mapsKeyHint': 'Für Ortsuche benötigt. Erstellen unter console.cloud.google.com',
'admin.mapsKeyHintLong': 'Ohne API Key wird OpenStreetMap für die Ortssuche genutzt. Mit Google API Key können zusätzlich Bilder, Bewertungen und Öffnungszeiten geladen werden. Erstellen unter console.cloud.google.com.',
'admin.recommended': 'Empfohlen',
- 'admin.weatherKey': 'OpenWeatherMap API Key',
+ 'admin.weatherKey': 'OpenWeatherMap API-Schlüssel',
'admin.weatherKeyHint': 'Für Wetterdaten. Kostenlos unter openweathermap.org',
'admin.validateKey': 'Test',
'admin.keyValid': 'Verbunden',
@@ -526,7 +569,7 @@ const de: Record = {
'admin.addons.subtitleAfter': ' nach deinen Wünschen anzupassen.',
'admin.addons.enabled': 'Aktiviert',
'admin.addons.disabled': 'Deaktiviert',
- 'admin.addons.type.trip': 'Trip',
+ 'admin.addons.type.trip': 'Reise',
'admin.addons.type.global': 'Global',
'admin.addons.type.integration': 'Integration',
'admin.addons.tripHint': 'Verfügbar als Tab innerhalb jedes Trips',
@@ -548,9 +591,10 @@ const de: Record = {
'admin.weather.locationHint': 'Das Wetter wird anhand des ersten Ortes mit Koordinaten im jeweiligen Tag berechnet. Ist kein Ort am Tag eingeplant, wird ein beliebiger Ort aus der Ortsliste als Referenz verwendet.',
// MCP Tokens
- 'admin.tabs.mcpTokens': 'MCP-Tokens',
- 'admin.mcpTokens.title': 'MCP-Tokens',
- 'admin.mcpTokens.subtitle': 'API-Tokens aller Benutzer verwalten',
+ 'admin.tabs.mcpTokens': 'MCP-Zugang',
+ 'admin.mcpTokens.title': 'MCP-Zugang',
+ 'admin.mcpTokens.subtitle': 'OAuth-Sitzungen und API-Tokens aller Benutzer verwalten',
+ 'admin.mcpTokens.sectionTitle': 'API-Tokens',
'admin.mcpTokens.owner': 'Besitzer',
'admin.mcpTokens.tokenName': 'Token-Name',
'admin.mcpTokens.created': 'Erstellt',
@@ -562,6 +606,17 @@ const de: Record = {
'admin.mcpTokens.deleteSuccess': 'Token gelöscht',
'admin.mcpTokens.deleteError': 'Token konnte nicht gelöscht werden',
'admin.mcpTokens.loadError': 'Tokens konnten nicht geladen werden',
+ 'admin.oauthSessions.sectionTitle': 'OAuth-Sitzungen',
+ 'admin.oauthSessions.clientName': 'Client',
+ 'admin.oauthSessions.owner': 'Besitzer',
+ 'admin.oauthSessions.scopes': 'Berechtigungen',
+ 'admin.oauthSessions.created': 'Erstellt',
+ 'admin.oauthSessions.empty': 'Keine aktiven OAuth-Sitzungen',
+ 'admin.oauthSessions.revokeTitle': 'Sitzung widerrufen',
+ 'admin.oauthSessions.revokeMessage': 'Diese OAuth-Sitzung wird sofort widerrufen. Der Client verliert den MCP-Zugang.',
+ 'admin.oauthSessions.revokeSuccess': 'Sitzung widerrufen',
+ 'admin.oauthSessions.revokeError': 'Sitzung konnte nicht widerrufen werden',
+ 'admin.oauthSessions.loadError': 'OAuth-Sitzungen konnten nicht geladen werden',
// GitHub
'admin.tabs.github': 'GitHub',
@@ -718,7 +773,7 @@ const de: Record = {
'atlas.addToBucketHint': 'Als Wunschziel speichern',
'atlas.bucketWhen': 'Wann möchtest du dorthin reisen?',
'atlas.statsTab': 'Statistik',
- 'atlas.bucketTab': 'Bucket List',
+ 'atlas.bucketTab': 'Wunschliste',
'atlas.addBucket': 'Zur Bucket List hinzufügen',
'atlas.bucketNotesPlaceholder': 'Notizen (optional)',
'atlas.bucketEmpty': 'Deine Bucket List ist leer',
@@ -731,7 +786,7 @@ const de: Record = {
'atlas.lastTrip': 'Letzter Trip',
'atlas.nextTrip': 'Nächster Trip',
'atlas.daysLeft': 'Tage',
- 'atlas.streak': 'Streak',
+ 'atlas.streak': 'Serie',
'atlas.years': 'Jahre',
'atlas.yearInRow': 'Jahr in Folge',
'atlas.yearsInRow': 'Jahre in Folge',
@@ -843,7 +898,7 @@ const de: Record = {
'places.noCategory': 'Keine Kategorie',
'places.categoryNamePlaceholder': 'Kategoriename',
'places.formTime': 'Uhrzeit',
- 'places.startTime': 'Start',
+ 'places.startTime': 'Startzeit',
'places.endTime': 'Ende',
'places.endTimeBeforeStart': 'Endzeit liegt vor der Startzeit',
'places.timeCollision': 'Zeitliche Überschneidung mit:',
@@ -898,7 +953,7 @@ const de: Record = {
'reservations.timeAlt': 'Uhrzeit (alternativ, z.B. 19:30)',
'reservations.notes': 'Notizen',
'reservations.notesPlaceholder': 'Zusätzliche Notizen...',
- 'reservations.meta.airline': 'Airline',
+ 'reservations.meta.airline': 'Fluggesellschaft',
'reservations.meta.flightNumber': 'Flugnr.',
'reservations.meta.from': 'Von',
'reservations.meta.to': 'Nach',
@@ -1383,6 +1438,7 @@ const de: Record = {
'memories.title': 'Fotos',
'memories.notConnected': 'Immich nicht verbunden',
'memories.notConnectedHint': 'Verbinde deine Immich-Instanz in den Einstellungen, um deine Reisefotos hier zu sehen.',
+ 'memories.notConnectedMultipleHint': 'Verbinde einen dieser Fotoanbieter: {provider_names} in den Einstellungen, um Fotos zu dieser Reise hinzufügen zu können.',
'memories.noDates': 'Füge Daten zu deiner Reise hinzu, um Fotos zu laden.',
'memories.noPhotos': 'Keine Fotos gefunden',
'memories.noPhotosHint': 'Keine Fotos in Immich für den Zeitraum dieser Reise gefunden.',
@@ -1393,21 +1449,32 @@ const de: Record = {
'memories.reviewTitle': 'Deine Fotos prüfen',
'memories.reviewHint': 'Klicke auf Fotos, um sie vom Teilen auszuschließen.',
'memories.shareCount': '{count} Fotos teilen',
+ 'memories.providerUrl': 'Server-URL',
+ 'memories.providerApiKey': 'API-Schlüssel',
+ 'memories.providerUsername': 'Benutzername',
+ 'memories.providerPassword': 'Passwort',
+ 'memories.providerOTP': 'MFA-Code (falls aktiviert)',
+ 'memories.skipSSLVerification': 'SSL-Zertifikatsprüfung überspringen',
+ 'memories.providerUrlHintSynology': 'Füge den Fotos-App-Pfad in die URL ein, z.B. https://nas:5001/photo',
'memories.testConnection': 'Verbindung testen',
'memories.testFirst': 'Verbindung zuerst testen',
'memories.connected': 'Verbunden',
'memories.disconnected': 'Nicht verbunden',
'memories.connectionSuccess': 'Verbindung zu Immich hergestellt',
'memories.connectionError': 'Verbindung zu Immich fehlgeschlagen',
- 'memories.saved': 'Immich-Einstellungen gespeichert',
+ 'memories.saved': '{provider_name}-Einstellungen gespeichert',
+ 'memories.providerDisconnectedBanner': 'Deine {provider_name}-Verbindung wurde getrennt. Verbinde erneut in den Einstellungen, um Fotos anzuzeigen.',
+ 'memories.saveError': '{provider_name}-Einstellungen konnten nicht gespeichert werden',
'memories.addPhotos': 'Fotos hinzufügen',
'memories.linkAlbum': 'Album verknüpfen',
'memories.selectAlbum': 'Immich-Album auswählen',
+ 'memories.selectAlbumMultiple': 'Album auswählen',
'memories.noAlbums': 'Keine Alben gefunden',
'memories.syncAlbum': 'Album synchronisieren',
'memories.unlinkAlbum': 'Album trennen',
'memories.photos': 'Fotos',
'memories.selectPhotos': 'Fotos aus Immich auswählen',
+ 'memories.selectPhotosMultiple': 'Fotos auswählen',
'memories.selectHint': 'Tippe auf Fotos um sie auszuwählen.',
'memories.selected': 'ausgewählt',
'memories.addSelected': '{count} Fotos hinzufügen',
@@ -1567,6 +1634,8 @@ const de: Record = {
'notifications.markUnread': 'Als ungelesen markieren',
'notifications.delete': 'Löschen',
'notifications.system': 'System',
+ 'notifications.synologySessionCleared.title': 'Synology Photos getrennt',
+ 'notifications.synologySessionCleared.text': 'Dein Server oder Konto hat sich geändert — gehe zu Einstellungen, um deine Verbindung erneut zu testen.',
'memories.error.loadAlbums': 'Alben konnten nicht geladen werden',
'memories.error.linkAlbum': 'Album konnte nicht verknüpft werden',
'memories.error.unlinkAlbum': 'Album konnte nicht getrennt werden',
@@ -1593,7 +1662,7 @@ const de: Record = {
// Todo
'todo.subtab.packing': 'Packliste',
- 'todo.subtab.todo': 'To-Do',
+ 'todo.subtab.todo': 'Aufgaben',
'todo.completed': 'erledigt',
'todo.filter.all': 'Alle',
'todo.filter.open': 'Offen',
@@ -1628,7 +1697,7 @@ const de: Record = {
// Notification system (added from feat/notification-system)
'settings.notifyVersionAvailable': 'Neue Version verfügbar',
'settings.notificationPreferences.noChannels': 'Keine Benachrichtigungskanäle konfiguriert. Bitte einen Administrator, E-Mail- oder Webhook-Benachrichtigungen einzurichten.',
- 'settings.webhookUrl.label': 'Webhook URL',
+ 'settings.webhookUrl.label': 'Webhook-URL',
'settings.webhookUrl.placeholder': 'https://discord.com/api/webhooks/...',
'settings.webhookUrl.hint': 'Gib deine Discord-, Slack- oder benutzerdefinierte Webhook-URL ein, um Benachrichtigungen zu erhalten.',
'settings.webhookUrl.save': 'Speichern',
@@ -1917,6 +1986,70 @@ const de: Record = {
'memories.saveError': 'Could not save {provider_name} settings',
'memories.selectAlbumMultiple': 'Select Album',
'memories.selectPhotosMultiple': 'Select Photos',
+
+ // OAuth scope groups
+ 'oauth.scope.group.trips': 'Reisen',
+ 'oauth.scope.group.places': 'Orte',
+ 'oauth.scope.group.atlas': 'Atlas',
+ 'oauth.scope.group.packing': 'Packliste',
+ 'oauth.scope.group.todos': 'Aufgaben',
+ 'oauth.scope.group.budget': 'Budget',
+ 'oauth.scope.group.reservations': 'Buchungen',
+ 'oauth.scope.group.collab': 'Zusammenarbeit',
+ 'oauth.scope.group.notifications': 'Benachrichtigungen',
+ 'oauth.scope.group.vacay': 'Urlaub',
+ 'oauth.scope.group.geo': 'Geo',
+ 'oauth.scope.group.weather': 'Wetter',
+
+ // OAuth scope labels & descriptions
+ 'oauth.scope.trips:read.label': 'Reisen und Reisepläne anzeigen',
+ 'oauth.scope.trips:read.description': 'Reisen, Tage, Tagesnotizen und Mitglieder lesen',
+ 'oauth.scope.trips:write.label': 'Reisen und Reisepläne bearbeiten',
+ 'oauth.scope.trips:write.description': 'Reisen, Tage und Notizen erstellen, aktualisieren und Mitglieder verwalten',
+ 'oauth.scope.trips:delete.label': 'Reisen löschen',
+ 'oauth.scope.trips:delete.description': 'Reisen dauerhaft löschen — diese Aktion ist unwiderruflich',
+ 'oauth.scope.trips:share.label': 'Freigabelinks verwalten',
+ 'oauth.scope.trips:share.description': 'Öffentliche Freigabelinks erstellen, aktualisieren und widerrufen',
+ 'oauth.scope.places:read.label': 'Orte und Kartendaten anzeigen',
+ 'oauth.scope.places:read.description': 'Orte, Tageszuweisungen, Tags und Kategorien lesen',
+ 'oauth.scope.places:write.label': 'Orte verwalten',
+ 'oauth.scope.places:write.description': 'Orte, Zuweisungen und Tags erstellen, aktualisieren und löschen',
+ 'oauth.scope.atlas:read.label': 'Atlas anzeigen',
+ 'oauth.scope.atlas:read.description': 'Besuchte Länder, Regionen und Wunschliste lesen',
+ 'oauth.scope.atlas:write.label': 'Atlas verwalten',
+ 'oauth.scope.atlas:write.description': 'Länder und Regionen als besucht markieren, Wunschliste verwalten',
+ 'oauth.scope.packing:read.label': 'Packlisten anzeigen',
+ 'oauth.scope.packing:read.description': 'Packgegenstände, Taschen und Kategoriezuweisungen lesen',
+ 'oauth.scope.packing:write.label': 'Packlisten verwalten',
+ 'oauth.scope.packing:write.description': 'Packgegenstände und Taschen hinzufügen, aktualisieren, löschen, abhaken und sortieren',
+ 'oauth.scope.todos:read.label': 'Aufgabenlisten anzeigen',
+ 'oauth.scope.todos:read.description': 'Reiseaufgaben und Kategoriezuweisungen lesen',
+ 'oauth.scope.todos:write.label': 'Aufgabenlisten verwalten',
+ 'oauth.scope.todos:write.description': 'Aufgaben erstellen, aktualisieren, abhaken, löschen und sortieren',
+ 'oauth.scope.budget:read.label': 'Budget anzeigen',
+ 'oauth.scope.budget:read.description': 'Budgeteinträge und Ausgabenaufschlüsselung lesen',
+ 'oauth.scope.budget:write.label': 'Budget verwalten',
+ 'oauth.scope.budget:write.description': 'Budgeteinträge erstellen, aktualisieren und löschen',
+ 'oauth.scope.reservations:read.label': 'Buchungen anzeigen',
+ 'oauth.scope.reservations:read.description': 'Buchungen und Unterkunftsdetails lesen',
+ 'oauth.scope.reservations:write.label': 'Buchungen verwalten',
+ 'oauth.scope.reservations:write.description': 'Buchungen erstellen, aktualisieren, löschen und sortieren',
+ 'oauth.scope.collab:read.label': 'Zusammenarbeit anzeigen',
+ 'oauth.scope.collab:read.description': 'Kollaborationsnotizen, Umfragen und Nachrichten lesen',
+ 'oauth.scope.collab:write.label': 'Zusammenarbeit verwalten',
+ 'oauth.scope.collab:write.description': 'Kollaborationsnotizen, Umfragen und Nachrichten erstellen, aktualisieren und löschen',
+ 'oauth.scope.notifications:read.label': 'Benachrichtigungen anzeigen',
+ 'oauth.scope.notifications:read.description': 'In-App-Benachrichtigungen und ungelesene Zählungen lesen',
+ 'oauth.scope.notifications:write.label': 'Benachrichtigungen verwalten',
+ 'oauth.scope.notifications:write.description': 'Benachrichtigungen als gelesen markieren und darauf reagieren',
+ 'oauth.scope.vacay:read.label': 'Urlaubspläne anzeigen',
+ 'oauth.scope.vacay:read.description': 'Urlaubsplanungsdaten, Einträge und Statistiken lesen',
+ 'oauth.scope.vacay:write.label': 'Urlaubspläne verwalten',
+ 'oauth.scope.vacay:write.description': 'Urlaubseinträge, Feiertage und Teampläne erstellen und verwalten',
+ 'oauth.scope.geo:read.label': 'Karten & Geocodierung',
+ 'oauth.scope.geo:read.description': 'Orte suchen, Karten-URLs auflösen und Koordinaten rückwärts geokodieren',
+ 'oauth.scope.weather:read.label': 'Wettervorhersagen',
+ 'oauth.scope.weather:read.description': 'Wettervorhersagen für Reiseorte und -daten abrufen',
}
export default de
diff --git a/client/src/i18n/translations/en.ts b/client/src/i18n/translations/en.ts
index a811e318..c481f899 100644
--- a/client/src/i18n/translations/en.ts
+++ b/client/src/i18n/translations/en.ts
@@ -252,6 +252,7 @@ const en: Record = {
'settings.mcp.endpoint': 'MCP Endpoint',
'settings.mcp.clientConfig': 'Client Configuration',
'settings.mcp.clientConfigHint': 'Replace with an API token from the list below. The path to npx may need to be adjusted for your system (e.g. C:\\PROGRA~1\\nodejs\\npx.cmd on Windows).',
+ 'settings.mcp.clientConfigHintOAuth': 'Replace and with the credentials shown in the OAuth 2.1 client you created above. mcp-remote will open your browser to complete the authorization the first time you connect. The path to npx may need to be adjusted for your system (e.g. C:\PROGRA~1\nodejs\npx.cmd on Windows).',
'settings.mcp.copy': 'Copy',
'settings.mcp.copied': 'Copied!',
'settings.mcp.apiTokens': 'API Tokens',
@@ -273,6 +274,48 @@ const en: Record = {
'settings.mcp.toast.createError': 'Failed to create token',
'settings.mcp.toast.deleted': 'Token deleted',
'settings.mcp.toast.deleteError': 'Failed to delete token',
+ 'settings.mcp.apiTokensDeprecated': 'API Tokens are deprecated and will be removed in a future release. Please use OAuth 2.1 Clients instead.',
+ 'settings.oauth.clients': 'OAuth 2.1 Clients',
+ 'settings.oauth.clientsHint': 'Register OAuth 2.1 clients to let third-party MCP applications (Claude Web, Cursor, etc.) connect without static tokens.',
+ 'settings.oauth.createClient': 'New Client',
+ 'settings.oauth.noClients': 'No OAuth clients registered.',
+ 'settings.oauth.clientId': 'Client ID',
+ 'settings.oauth.clientSecret': 'Client Secret',
+ 'settings.oauth.deleteClient': 'Delete Client',
+ 'settings.oauth.deleteClientMessage': 'This client and all active sessions will be permanently removed. Any application using it will lose access immediately.',
+ 'settings.oauth.rotateSecret': 'Rotate Secret',
+ 'settings.oauth.rotateSecretMessage': 'A new client secret will be generated and all existing sessions will be invalidated immediately. Update your application before closing this dialog.',
+ 'settings.oauth.rotateSecretConfirm': 'Rotate',
+ 'settings.oauth.rotateSecretConfirming': 'Rotating…',
+ 'settings.oauth.rotateSecretDoneTitle': 'New Secret Generated',
+ 'settings.oauth.rotateSecretDoneWarning': 'This secret is shown only once. Copy it now and update your application — all previous sessions have been invalidated.',
+ 'settings.oauth.activeSessions': 'Active OAuth Sessions',
+ 'settings.oauth.sessionScopes': 'Scopes',
+ 'settings.oauth.sessionExpires': 'Expires',
+ 'settings.oauth.revoke': 'Revoke',
+ 'settings.oauth.revokeSession': 'Revoke Session',
+ 'settings.oauth.revokeSessionMessage': 'This will immediately revoke access for this OAuth session.',
+ 'settings.oauth.modal.createTitle': 'Register OAuth Client',
+ 'settings.oauth.modal.presets': 'Quick presets',
+ 'settings.oauth.modal.clientName': 'Application Name',
+ 'settings.oauth.modal.clientNamePlaceholder': 'e.g. Claude Web, My MCP App',
+ 'settings.oauth.modal.redirectUris': 'Redirect URIs',
+ 'settings.oauth.modal.redirectUrisPlaceholder': 'https://your-app.com/callback\nhttps://your-app.com/auth',
+ 'settings.oauth.modal.redirectUrisHint': 'One URI per line. HTTPS required (localhost exempt). Exact match enforced.',
+ 'settings.oauth.modal.scopes': 'Allowed Scopes',
+ 'settings.oauth.modal.scopesHint': 'list_trips and get_trip_summary are always available — no scope required. They let the AI discover trip IDs needed to use any other tool.',
+ 'settings.oauth.modal.selectAll': 'Select all',
+ 'settings.oauth.modal.deselectAll': 'Deselect all',
+ 'settings.oauth.modal.creating': 'Registering…',
+ 'settings.oauth.modal.create': 'Register Client',
+ 'settings.oauth.modal.createdTitle': 'Client Registered',
+ 'settings.oauth.modal.createdWarning': 'The client secret is shown only once. Copy it now — it cannot be recovered.',
+ 'settings.oauth.toast.createError': 'Failed to register OAuth client',
+ 'settings.oauth.toast.deleted': 'OAuth client deleted',
+ 'settings.oauth.toast.deleteError': 'Failed to delete OAuth client',
+ 'settings.oauth.toast.revoked': 'Session revoked',
+ 'settings.oauth.toast.revokeError': 'Failed to revoke session',
+ 'settings.oauth.toast.rotateError': 'Failed to rotate client secret',
'settings.account': 'Account',
'settings.about': 'About',
'settings.about.reportBug': 'Report a Bug',
@@ -573,9 +616,10 @@ const en: Record = {
'admin.weather.locationHint': 'Weather is based on the first place with coordinates in each day. If no place is assigned to a day, any place from the place list is used as a reference.',
// GitHub
- 'admin.tabs.mcpTokens': 'MCP Tokens',
- 'admin.mcpTokens.title': 'MCP Tokens',
- 'admin.mcpTokens.subtitle': 'Manage API tokens across all users',
+ 'admin.tabs.mcpTokens': 'MCP Access',
+ 'admin.mcpTokens.title': 'MCP Access',
+ 'admin.mcpTokens.subtitle': 'Manage OAuth sessions and API tokens across all users',
+ 'admin.mcpTokens.sectionTitle': 'API Tokens',
'admin.mcpTokens.owner': 'Owner',
'admin.mcpTokens.tokenName': 'Token Name',
'admin.mcpTokens.created': 'Created',
@@ -587,6 +631,17 @@ const en: Record = {
'admin.mcpTokens.deleteSuccess': 'Token deleted',
'admin.mcpTokens.deleteError': 'Failed to delete token',
'admin.mcpTokens.loadError': 'Failed to load tokens',
+ 'admin.oauthSessions.sectionTitle': 'OAuth Sessions',
+ 'admin.oauthSessions.clientName': 'Client',
+ 'admin.oauthSessions.owner': 'Owner',
+ 'admin.oauthSessions.scopes': 'Scopes',
+ 'admin.oauthSessions.created': 'Created',
+ 'admin.oauthSessions.empty': 'No active OAuth sessions',
+ 'admin.oauthSessions.revokeTitle': 'Revoke Session',
+ 'admin.oauthSessions.revokeMessage': 'This will revoke the OAuth session immediately. The client will lose MCP access.',
+ 'admin.oauthSessions.revokeSuccess': 'Session revoked',
+ 'admin.oauthSessions.revokeError': 'Failed to revoke session',
+ 'admin.oauthSessions.loadError': 'Failed to load OAuth sessions',
'admin.tabs.github': 'GitHub',
'admin.audit.subtitle': 'Security-sensitive and administration events (backups, users, MFA, settings).',
@@ -1423,6 +1478,9 @@ const en: Record = {
'memories.providerApiKey': 'API Key',
'memories.providerUsername': 'Username',
'memories.providerPassword': 'Password',
+ 'memories.providerOTP': 'MFA code (if enabled)',
+ 'memories.skipSSLVerification': 'Skip SSL certificate verification',
+ 'memories.providerUrlHintSynology': 'Include the Photos app path in the URL, e.g. https://nas:5001/photo',
'memories.testConnection': 'Test connection',
'memories.testFirst': 'Test connection first',
'memories.connected': 'Connected',
@@ -1430,6 +1488,7 @@ const en: Record = {
'memories.connectionSuccess': 'Connected to {provider_name}',
'memories.connectionError': 'Could not connect to {provider_name}',
'memories.saved': '{provider_name} settings saved',
+ 'memories.providerDisconnectedBanner': 'Your {provider_name} connection is lost. Reconnect in Settings to view photos.',
'memories.saveError': 'Could not save {provider_name} settings',
//------------------------
'memories.addPhotos': 'Add photos',
@@ -1612,6 +1671,8 @@ const en: Record = {
'notifications.markUnread': 'Mark as unread',
'notifications.delete': 'Delete',
'notifications.system': 'System',
+ 'notifications.synologySessionCleared.title': 'Synology Photos disconnected',
+ 'notifications.synologySessionCleared.text': 'Your server or account changed — go to Settings to test your connection again.',
// Notification test keys (dev only)
'notifications.versionAvailable.title': 'Update Available',
@@ -1954,6 +2015,70 @@ const en: Record = {
'admin.addons.catalog.journey.name': 'Journey',
'admin.addons.catalog.journey.description': 'Trip tracking & travel journal with check-ins, photos, and daily stories',
+
+ // OAuth scope groups
+ 'oauth.scope.group.trips': 'Trips',
+ 'oauth.scope.group.places': 'Places',
+ 'oauth.scope.group.atlas': 'Atlas',
+ 'oauth.scope.group.packing': 'Packing',
+ 'oauth.scope.group.todos': 'To-dos',
+ 'oauth.scope.group.budget': 'Budget',
+ 'oauth.scope.group.reservations': 'Reservations',
+ 'oauth.scope.group.collab': 'Collaboration',
+ 'oauth.scope.group.notifications': 'Notifications',
+ 'oauth.scope.group.vacay': 'Vacation',
+ 'oauth.scope.group.geo': 'Geo',
+ 'oauth.scope.group.weather': 'Weather',
+
+ // OAuth scope labels & descriptions
+ 'oauth.scope.trips:read.label': 'View trips & itineraries',
+ 'oauth.scope.trips:read.description': 'Read trips, days, day notes, and members',
+ 'oauth.scope.trips:write.label': 'Edit trips & itineraries',
+ 'oauth.scope.trips:write.description': 'Create and update trips, days, notes, and manage members',
+ 'oauth.scope.trips:delete.label': 'Delete trips',
+ 'oauth.scope.trips:delete.description': 'Permanently delete entire trips — this action is irreversible',
+ 'oauth.scope.trips:share.label': 'Manage share links',
+ 'oauth.scope.trips:share.description': 'Create, update, and revoke public share links for trips',
+ 'oauth.scope.places:read.label': 'View places & map data',
+ 'oauth.scope.places:read.description': 'Read places, day assignments, tags, and categories',
+ 'oauth.scope.places:write.label': 'Manage places',
+ 'oauth.scope.places:write.description': 'Create, update, and delete places, assignments, and tags',
+ 'oauth.scope.atlas:read.label': 'View Atlas',
+ 'oauth.scope.atlas:read.description': 'Read visited countries, regions, and bucket list',
+ 'oauth.scope.atlas:write.label': 'Manage Atlas',
+ 'oauth.scope.atlas:write.description': 'Mark countries and regions visited, manage bucket list',
+ 'oauth.scope.packing:read.label': 'View packing lists',
+ 'oauth.scope.packing:read.description': 'Read packing items, bags, and category assignees',
+ 'oauth.scope.packing:write.label': 'Manage packing lists',
+ 'oauth.scope.packing:write.description': 'Add, update, delete, toggle, and reorder packing items and bags',
+ 'oauth.scope.todos:read.label': 'View to-do lists',
+ 'oauth.scope.todos:read.description': 'Read trip to-do items and category assignees',
+ 'oauth.scope.todos:write.label': 'Manage to-do lists',
+ 'oauth.scope.todos:write.description': 'Create, update, toggle, delete, and reorder to-do items',
+ 'oauth.scope.budget:read.label': 'View budget',
+ 'oauth.scope.budget:read.description': 'Read budget items and expense breakdown',
+ 'oauth.scope.budget:write.label': 'Manage budget',
+ 'oauth.scope.budget:write.description': 'Create, update, and delete budget items',
+ 'oauth.scope.reservations:read.label': 'View reservations',
+ 'oauth.scope.reservations:read.description': 'Read reservations and accommodation details',
+ 'oauth.scope.reservations:write.label': 'Manage reservations',
+ 'oauth.scope.reservations:write.description': 'Create, update, delete, and reorder reservations',
+ 'oauth.scope.collab:read.label': 'View collaboration',
+ 'oauth.scope.collab:read.description': 'Read collab notes, polls, and messages',
+ 'oauth.scope.collab:write.label': 'Manage collaboration',
+ 'oauth.scope.collab:write.description': 'Create, update, and delete collab notes, polls, and messages',
+ 'oauth.scope.notifications:read.label': 'View notifications',
+ 'oauth.scope.notifications:read.description': 'Read in-app notifications and unread counts',
+ 'oauth.scope.notifications:write.label': 'Manage notifications',
+ 'oauth.scope.notifications:write.description': 'Mark notifications as read and respond to them',
+ 'oauth.scope.vacay:read.label': 'View vacation plans',
+ 'oauth.scope.vacay:read.description': 'Read vacation planning data, entries, and stats',
+ 'oauth.scope.vacay:write.label': 'Manage vacation plans',
+ 'oauth.scope.vacay:write.description': 'Create and manage vacation entries, holidays, and team plans',
+ 'oauth.scope.geo:read.label': 'Maps & geocoding',
+ 'oauth.scope.geo:read.description': 'Search locations, resolve map URLs, and reverse geocode coordinates',
+ 'oauth.scope.weather:read.label': 'Weather forecasts',
+ 'oauth.scope.weather:read.description': 'Fetch weather forecasts for trip locations and dates',
}
export default en
diff --git a/client/src/i18n/translations/es.ts b/client/src/i18n/translations/es.ts
index c9ac40d9..15756839 100644
--- a/client/src/i18n/translations/es.ts
+++ b/client/src/i18n/translations/es.ts
@@ -180,9 +180,6 @@ const es: Record = {
'admin.notifications.none': 'Desactivado',
'admin.notifications.email': 'Correo (SMTP)',
'admin.notifications.webhook': 'Webhook',
- 'admin.notifications.events': 'Eventos de notificación',
- 'admin.notifications.eventsHint': 'Elige qué eventos activan notificaciones para todos los usuarios.',
- 'admin.notifications.configureFirst': 'Configura primero los ajustes SMTP o webhook a continuación, luego activa los eventos.',
'admin.notifications.save': 'Guardar configuración de notificaciones',
'admin.notifications.saved': 'Configuración de notificaciones guardada',
'admin.notifications.testWebhook': 'Enviar webhook de prueba',
@@ -229,6 +226,7 @@ const es: Record = {
'settings.mcp.endpoint': 'Endpoint MCP',
'settings.mcp.clientConfig': 'Configuración del cliente',
'settings.mcp.clientConfigHint': 'Reemplaza con un token de la lista de abajo. Es posible que debas ajustar la ruta de npx según tu sistema (p. ej. C:\\PROGRA~1\\nodejs\\npx.cmd en Windows).',
+ 'settings.mcp.clientConfigHintOAuth': 'Reemplaza y con las credenciales del cliente OAuth 2.1 que creaste arriba. mcp-remote abrirá el navegador para completar la autorización la primera vez que te conectes. Es posible que debas ajustar la ruta de npx según tu sistema (p. ej. C:\\PROGRA~1\\nodejs\\npx.cmd en Windows).',
'settings.mcp.copy': 'Copiar',
'settings.mcp.copied': '¡Copiado!',
'settings.mcp.apiTokens': 'Tokens de API',
@@ -250,6 +248,48 @@ const es: Record = {
'settings.mcp.toast.createError': 'Error al crear el token',
'settings.mcp.toast.deleted': 'Token eliminado',
'settings.mcp.toast.deleteError': 'Error al eliminar el token',
+ 'settings.mcp.apiTokensDeprecated': 'Los tokens de API están obsoletos y se eliminarán en una versión futura. Utilice los clientes OAuth 2.1 en su lugar.',
+ 'settings.oauth.clients': 'Clientes OAuth 2.1',
+ 'settings.oauth.clientsHint': 'Registre clientes OAuth 2.1 para que las aplicaciones MCP de terceros (Claude Web, Cursor, etc.) puedan conectarse sin tokens estáticos.',
+ 'settings.oauth.createClient': 'Nuevo cliente',
+ 'settings.oauth.noClients': 'No hay clientes OAuth registrados.',
+ 'settings.oauth.clientId': 'ID de cliente',
+ 'settings.oauth.clientSecret': 'Secreto de cliente',
+ 'settings.oauth.deleteClient': 'Eliminar cliente',
+ 'settings.oauth.deleteClientMessage': 'Este cliente y todas las sesiones activas se eliminarán permanentemente. Cualquier aplicación que lo use perderá el acceso inmediatamente.',
+ 'settings.oauth.rotateSecret': 'Renovar secreto',
+ 'settings.oauth.rotateSecretMessage': 'Se generará un nuevo secreto de cliente y todas las sesiones existentes se invalidarán de inmediato. Actualice su aplicación antes de cerrar este diálogo.',
+ 'settings.oauth.rotateSecretConfirm': 'Renovar',
+ 'settings.oauth.rotateSecretConfirming': 'Renovando…',
+ 'settings.oauth.rotateSecretDoneTitle': 'Nuevo secreto generado',
+ 'settings.oauth.rotateSecretDoneWarning': 'Este secreto solo se muestra una vez. Cópielo ahora y actualice su aplicación — todas las sesiones anteriores han sido invalidadas.',
+ 'settings.oauth.activeSessions': 'Sesiones OAuth activas',
+ 'settings.oauth.sessionScopes': 'Ámbitos',
+ 'settings.oauth.sessionExpires': 'Expira',
+ 'settings.oauth.revoke': 'Revocar',
+ 'settings.oauth.revokeSession': 'Revocar sesión',
+ 'settings.oauth.revokeSessionMessage': 'Esto revocará inmediatamente el acceso de esta sesión OAuth.',
+ 'settings.oauth.modal.createTitle': 'Registrar cliente OAuth',
+ 'settings.oauth.modal.presets': 'Ajustes rápidos',
+ 'settings.oauth.modal.clientName': 'Nombre de la aplicación',
+ 'settings.oauth.modal.clientNamePlaceholder': 'ej. Claude Web, Mi app MCP',
+ 'settings.oauth.modal.redirectUris': 'URIs de redirección',
+ 'settings.oauth.modal.redirectUrisPlaceholder': 'https://your-app.com/callback\nhttps://your-app.com/auth',
+ 'settings.oauth.modal.redirectUrisHint': 'Un URI por línea. HTTPS obligatorio (localhost exento). Coincidencia exacta.',
+ 'settings.oauth.modal.scopes': 'Ámbitos permitidos',
+ 'settings.oauth.modal.scopesHint': 'list_trips y get_trip_summary siempre están disponibles — sin ámbito requerido. Permiten a la IA descubrir los IDs de viaje necesarios.',
+ 'settings.oauth.modal.selectAll': 'Seleccionar todo',
+ 'settings.oauth.modal.deselectAll': 'Deseleccionar todo',
+ 'settings.oauth.modal.creating': 'Registrando…',
+ 'settings.oauth.modal.create': 'Registrar cliente',
+ 'settings.oauth.modal.createdTitle': 'Cliente registrado',
+ 'settings.oauth.modal.createdWarning': 'El secreto del cliente solo se muestra una vez. Cópielo ahora — no se puede recuperar.',
+ 'settings.oauth.toast.createError': 'Error al registrar el cliente OAuth',
+ 'settings.oauth.toast.deleted': 'Cliente OAuth eliminado',
+ 'settings.oauth.toast.deleteError': 'Error al eliminar el cliente OAuth',
+ 'settings.oauth.toast.revoked': 'Sesión revocada',
+ 'settings.oauth.toast.revokeError': 'Error al revocar la sesión',
+ 'settings.oauth.toast.rotateError': 'Error al renovar el secreto del cliente',
'settings.account': 'Cuenta',
'settings.about': 'Acerca de',
'settings.about.reportBug': 'Reportar un error',
@@ -397,7 +437,7 @@ const es: Record = {
'admin.tabs.users': 'Usuarios',
'admin.tabs.categories': 'Categorías',
'admin.tabs.backup': 'Copia de seguridad',
- 'admin.tabs.audit': 'Audit',
+ 'admin.tabs.audit': 'Auditoría',
'admin.stats.users': 'Usuarios',
'admin.stats.trips': 'Viajes',
'admin.stats.places': 'Lugares',
@@ -525,9 +565,10 @@ const es: Record = {
'admin.weather.locationHint': 'El tiempo se basa en el primer lugar con coordenadas de cada día. Si no hay ningún lugar asignado a un día, se usa como referencia cualquier lugar de la lista.',
// MCP Tokens
- 'admin.tabs.mcpTokens': 'Tokens MCP',
- 'admin.mcpTokens.title': 'Tokens MCP',
- 'admin.mcpTokens.subtitle': 'Gestionar tokens de API de todos los usuarios',
+ 'admin.tabs.mcpTokens': 'Acceso MCP',
+ 'admin.mcpTokens.title': 'Acceso MCP',
+ 'admin.mcpTokens.subtitle': 'Gestionar sesiones OAuth y tokens de API de todos los usuarios',
+ 'admin.mcpTokens.sectionTitle': 'Tokens de API',
'admin.mcpTokens.owner': 'Propietario',
'admin.mcpTokens.tokenName': 'Nombre del token',
'admin.mcpTokens.created': 'Creado',
@@ -539,6 +580,17 @@ const es: Record = {
'admin.mcpTokens.deleteSuccess': 'Token eliminado',
'admin.mcpTokens.deleteError': 'No se pudo eliminar el token',
'admin.mcpTokens.loadError': 'No se pudieron cargar los tokens',
+ 'admin.oauthSessions.sectionTitle': 'Sesiones OAuth',
+ 'admin.oauthSessions.clientName': 'Cliente',
+ 'admin.oauthSessions.owner': 'Propietario',
+ 'admin.oauthSessions.scopes': 'Permisos',
+ 'admin.oauthSessions.created': 'Creado',
+ 'admin.oauthSessions.empty': 'No hay sesiones OAuth activas',
+ 'admin.oauthSessions.revokeTitle': 'Revocar sesión',
+ 'admin.oauthSessions.revokeMessage': 'Esto revocará la sesión OAuth inmediatamente. El cliente perderá el acceso MCP.',
+ 'admin.oauthSessions.revokeSuccess': 'Sesión revocada',
+ 'admin.oauthSessions.revokeError': 'No se pudo revocar la sesión',
+ 'admin.oauthSessions.loadError': 'No se pudieron cargar las sesiones OAuth',
// GitHub
'admin.tabs.github': 'GitHub',
@@ -668,7 +720,7 @@ const es: Record = {
'vacay.fuseInfo4': 'Ajustes como festivos y festivos de empresa se comparten.',
'vacay.fuseInfo5': 'La fusión puede disolverse en cualquier momento por cualquiera de las partes. Tus entradas se conservarán.',
'vacay.addCalendar': 'Añadir calendario',
- 'vacay.calendarColor': 'Color',
+ 'vacay.calendarColor': 'Color del calendario',
'vacay.calendarLabel': 'Etiqueta',
'vacay.noCalendars': 'Sin calendarios',
@@ -881,7 +933,7 @@ const es: Record = {
'reservations.type.car': 'Coche de alquiler',
'reservations.type.cruise': 'Crucero',
'reservations.type.event': 'Evento',
- 'reservations.type.tour': 'Tour',
+ 'reservations.type.tour': 'Excursión',
'reservations.type.other': 'Otro',
'reservations.confirm.delete': '¿Seguro que quieres eliminar la reserva "{name}"?',
'reservations.confirm.deleteTitle': '¿Eliminar reserva?',
@@ -965,6 +1017,7 @@ const es: Record = {
'budget.totalBudget': 'Presupuesto total',
'budget.byCategory': 'Por categoría',
'budget.editTooltip': 'Haz clic para editar',
+ 'budget.linkedToReservation': 'Vinculado a una reserva — edite el nombre allí',
'budget.confirm.deleteCategory': '¿Seguro que quieres eliminar la categoría "{name}" con {count} entradas?',
'budget.deleteCategory': 'Eliminar categoría',
'budget.perPerson': 'Por persona',
@@ -1041,6 +1094,9 @@ const es: Record = {
'packing.template': 'Plantilla',
'packing.templateApplied': '{count} artículos añadidos desde plantilla',
'packing.templateError': 'Error al aplicar plantilla',
+ 'packing.saveAsTemplate': 'Guardar como plantilla',
+ 'packing.templateName': 'Nombre de la plantilla',
+ 'packing.templateSaved': 'Lista de equipaje guardada como plantilla',
'packing.assignUser': 'Asignar usuario',
'packing.noMembers': 'Sin miembros',
'packing.bags': 'Equipaje',
@@ -1321,8 +1377,8 @@ const es: Record = {
'day.hotelDayRange': 'Aplicar a los días',
'day.noPlacesForHotel': 'Añade primero lugares al viaje',
'day.allDays': 'Todos',
- 'day.checkIn': 'Check-in',
- 'day.checkOut': 'Check-out',
+ 'day.checkIn': 'Registro de entrada',
+ 'day.checkOut': 'Registro de salida',
'day.confirmation': 'Confirmación',
'day.editAccommodation': 'Editar alojamiento',
'day.reservations': 'Reservas',
@@ -1331,6 +1387,7 @@ const es: Record = {
'memories.title': 'Fotos',
'memories.notConnected': 'Immich no conectado',
'memories.notConnectedHint': 'Conecta tu instancia de Immich en Ajustes para ver tus fotos de viaje aquí.',
+ 'memories.notConnectedMultipleHint': 'Conecta alguno de estos proveedores de fotos: {provider_names} en Configuración para poder añadir fotos a este viaje.',
'memories.noDates': 'Añade fechas a tu viaje para cargar fotos.',
'memories.noPhotos': 'No se encontraron fotos',
'memories.noPhotosHint': 'No se encontraron fotos en Immich para el rango de fechas de este viaje.',
@@ -1341,26 +1398,35 @@ const es: Record = {
'memories.reviewTitle': 'Revisar tus fotos',
'memories.reviewHint': 'Haz clic en las fotos para excluirlas de compartir.',
'memories.shareCount': 'Compartir {count} fotos',
- 'memories.immichUrl': 'URL del servidor Immich',
- 'memories.immichApiKey': 'Clave API',
+ 'memories.providerUrl': 'URL del servidor',
+ 'memories.providerApiKey': 'Clave API',
+ 'memories.providerUsername': 'Nombre de usuario',
+ 'memories.providerPassword': 'Contraseña',
+ 'memories.providerOTP': 'Código MFA (si está habilitado)',
+ 'memories.skipSSLVerification': 'Omitir verificación del certificado SSL',
+ 'memories.providerUrlHintSynology': 'Incluye la ruta de la aplicación Photos en la URL, p.ej. https://nas:5001/photo',
'memories.testConnection': 'Probar conexión',
'memories.testFirst': 'Probar conexión primero',
'memories.connected': 'Conectado',
'memories.disconnected': 'No conectado',
'memories.connectionSuccess': 'Conectado a Immich',
'memories.connectionError': 'No se pudo conectar a Immich',
- 'memories.saved': 'Configuración de Immich guardada',
+ 'memories.saved': 'Configuración de {provider_name} guardada',
+ 'memories.providerDisconnectedBanner': 'Se perdió la conexión con {provider_name}. Vuelve a conectar en Configuración para ver las fotos.',
+ 'memories.saveError': 'No se pudieron guardar los ajustes de {provider_name}',
'memories.oldest': 'Más antiguas',
'memories.newest': 'Más recientes',
'memories.allLocations': 'Todas las ubicaciones',
'memories.addPhotos': 'Añadir fotos',
'memories.linkAlbum': 'Vincular álbum',
'memories.selectAlbum': 'Seleccionar álbum de Immich',
+ 'memories.selectAlbumMultiple': 'Seleccionar álbum',
'memories.noAlbums': 'No se encontraron álbumes',
'memories.syncAlbum': 'Sincronizar álbum',
'memories.unlinkAlbum': 'Desvincular',
'memories.photos': 'fotos',
'memories.selectPhotos': 'Seleccionar fotos de Immich',
+ 'memories.selectPhotosMultiple': 'Seleccionar fotos',
'memories.selectHint': 'Toca las fotos para seleccionarlas.',
'memories.selected': 'seleccionado(s)',
'memories.addSelected': 'Añadir {count} fotos',
@@ -1475,8 +1541,8 @@ const es: Record = {
'reservations.meta.trainNumber': 'N° de tren',
'reservations.meta.platform': 'Andén',
'reservations.meta.seat': 'Asiento',
- 'reservations.meta.checkIn': 'Check-in',
- 'reservations.meta.checkOut': 'Check-out',
+ 'reservations.meta.checkIn': 'Registro de entrada',
+ 'reservations.meta.checkOut': 'Registro de salida',
'reservations.meta.linkAccommodation': 'Alojamiento',
'reservations.meta.pickAccommodation': 'Vincular con alojamiento',
'reservations.meta.noAccommodation': 'Ninguno',
@@ -1570,6 +1636,8 @@ const es: Record = {
'notifications.markUnread': 'Marcar como no leída',
'notifications.delete': 'Eliminar',
'notifications.system': 'Sistema',
+ 'notifications.synologySessionCleared.title': 'Synology Photos desconectado',
+ 'notifications.synologySessionCleared.text': 'Tu servidor o cuenta ha cambiado — ve a Configuración para probar la conexión de nuevo.',
'memories.error.loadAlbums': 'Error al cargar los álbumes',
'memories.error.linkAlbum': 'Error al vincular el álbum',
'memories.error.unlinkAlbum': 'Error al desvincular el álbum',
@@ -1925,6 +1993,69 @@ const es: Record = {
'dayplan.mobile.createNew': 'Crear nuevo lugar',
'admin.addons.catalog.journey.name': 'Travesía',
'admin.addons.catalog.journey.description': 'Seguimiento de viajes y diario de viajero con registros de ubicación, fotos e historias diarias',
+ // OAuth scope groups
+ 'oauth.scope.group.trips': 'Viajes',
+ 'oauth.scope.group.places': 'Lugares',
+ 'oauth.scope.group.atlas': 'Atlas',
+ 'oauth.scope.group.packing': 'Equipaje',
+ 'oauth.scope.group.todos': 'Tareas',
+ 'oauth.scope.group.budget': 'Presupuesto',
+ 'oauth.scope.group.reservations': 'Reservas',
+ 'oauth.scope.group.collab': 'Colaboración',
+ 'oauth.scope.group.notifications': 'Notificaciones',
+ 'oauth.scope.group.vacay': 'Vacaciones',
+ 'oauth.scope.group.geo': 'Geo',
+ 'oauth.scope.group.weather': 'Clima',
+
+ // OAuth scope labels & descriptions
+ 'oauth.scope.trips:read.label': 'Ver viajes e itinerarios',
+ 'oauth.scope.trips:read.description': 'Leer viajes, días, notas y miembros',
+ 'oauth.scope.trips:write.label': 'Editar viajes e itinerarios',
+ 'oauth.scope.trips:write.description': 'Crear y actualizar viajes, días, notas y gestionar miembros',
+ 'oauth.scope.trips:delete.label': 'Eliminar viajes',
+ 'oauth.scope.trips:delete.description': 'Eliminar viajes permanentemente — esta acción es irreversible',
+ 'oauth.scope.trips:share.label': 'Gestionar enlaces de compartir',
+ 'oauth.scope.trips:share.description': 'Crear, actualizar y revocar enlaces públicos de viaje',
+ 'oauth.scope.places:read.label': 'Ver lugares y datos del mapa',
+ 'oauth.scope.places:read.description': 'Leer lugares, asignaciones de días, etiquetas y categorías',
+ 'oauth.scope.places:write.label': 'Gestionar lugares',
+ 'oauth.scope.places:write.description': 'Crear, actualizar y eliminar lugares, asignaciones y etiquetas',
+ 'oauth.scope.atlas:read.label': 'Ver Atlas',
+ 'oauth.scope.atlas:read.description': 'Leer países visitados, regiones y lista de deseos',
+ 'oauth.scope.atlas:write.label': 'Gestionar Atlas',
+ 'oauth.scope.atlas:write.description': 'Marcar países y regiones como visitados, gestionar lista de deseos',
+ 'oauth.scope.packing:read.label': 'Ver listas de equipaje',
+ 'oauth.scope.packing:read.description': 'Leer artículos, maletas y responsables de categoría',
+ 'oauth.scope.packing:write.label': 'Gestionar listas de equipaje',
+ 'oauth.scope.packing:write.description': 'Agregar, actualizar, eliminar, marcar y reordenar artículos y maletas',
+ 'oauth.scope.todos:read.label': 'Ver listas de tareas',
+ 'oauth.scope.todos:read.description': 'Leer tareas del viaje y responsables de categoría',
+ 'oauth.scope.todos:write.label': 'Gestionar listas de tareas',
+ 'oauth.scope.todos:write.description': 'Crear, actualizar, marcar, eliminar y reordenar tareas',
+ 'oauth.scope.budget:read.label': 'Ver presupuesto',
+ 'oauth.scope.budget:read.description': 'Leer partidas de presupuesto y desglose de gastos',
+ 'oauth.scope.budget:write.label': 'Gestionar presupuesto',
+ 'oauth.scope.budget:write.description': 'Crear, actualizar y eliminar partidas de presupuesto',
+ 'oauth.scope.reservations:read.label': 'Ver reservas',
+ 'oauth.scope.reservations:read.description': 'Leer reservas y detalles de alojamiento',
+ 'oauth.scope.reservations:write.label': 'Gestionar reservas',
+ 'oauth.scope.reservations:write.description': 'Crear, actualizar, eliminar y reordenar reservas',
+ 'oauth.scope.collab:read.label': 'Ver colaboración',
+ 'oauth.scope.collab:read.description': 'Leer notas colaborativas, encuestas y mensajes',
+ 'oauth.scope.collab:write.label': 'Gestionar colaboración',
+ 'oauth.scope.collab:write.description': 'Crear, actualizar y eliminar notas, encuestas y mensajes',
+ 'oauth.scope.notifications:read.label': 'Ver notificaciones',
+ 'oauth.scope.notifications:read.description': 'Leer notificaciones y conteos no leídos',
+ 'oauth.scope.notifications:write.label': 'Gestionar notificaciones',
+ 'oauth.scope.notifications:write.description': 'Marcar notificaciones como leídas y responderlas',
+ 'oauth.scope.vacay:read.label': 'Ver planes de vacaciones',
+ 'oauth.scope.vacay:read.description': 'Leer datos de planificación, entradas y estadísticas de vacaciones',
+ 'oauth.scope.vacay:write.label': 'Gestionar planes de vacaciones',
+ 'oauth.scope.vacay:write.description': 'Crear y gestionar entradas de vacaciones, festivos y planes de equipo',
+ 'oauth.scope.geo:read.label': 'Mapas y geocodificación',
+ 'oauth.scope.geo:read.description': 'Buscar lugares, resolver URLs de mapa y geocodificar coordenadas',
+ 'oauth.scope.weather:read.label': 'Previsiones meteorológicas',
+ 'oauth.scope.weather:read.description': 'Obtener previsiones meteorológicas para lugares y fechas del viaje',
}
export default es
diff --git a/client/src/i18n/translations/fr.ts b/client/src/i18n/translations/fr.ts
index 91708d28..b2d9e934 100644
--- a/client/src/i18n/translations/fr.ts
+++ b/client/src/i18n/translations/fr.ts
@@ -179,9 +179,6 @@ const fr: Record = {
'admin.notifications.none': 'Désactivé',
'admin.notifications.email': 'E-mail (SMTP)',
'admin.notifications.webhook': 'Webhook',
- 'admin.notifications.events': 'Événements de notification',
- 'admin.notifications.eventsHint': 'Choisissez quels événements déclenchent des notifications pour tous les utilisateurs.',
- 'admin.notifications.configureFirst': 'Configurez d\'abord les paramètres SMTP ou webhook ci-dessous, puis activez les événements.',
'admin.notifications.save': 'Enregistrer les paramètres de notification',
'admin.notifications.saved': 'Paramètres de notification enregistrés',
'admin.notifications.testWebhook': 'Envoyer un webhook de test',
@@ -228,6 +225,7 @@ const fr: Record = {
'settings.mcp.endpoint': 'Point de terminaison MCP',
'settings.mcp.clientConfig': 'Configuration du client',
'settings.mcp.clientConfigHint': 'Remplacez par un token API de la liste ci-dessous. Le chemin vers npx devra peut-être être ajusté selon votre système (ex. C:\\PROGRA~1\\nodejs\\npx.cmd sous Windows).',
+ 'settings.mcp.clientConfigHintOAuth': 'Remplacez et par les identifiants affichés dans le client OAuth 2.1 créé ci-dessus. mcp-remote ouvrira votre navigateur pour finaliser l\'autorisation lors de la première connexion. Le chemin vers npx devra peut-être être ajusté selon votre système (ex. C:\\PROGRA~1\\nodejs\\npx.cmd sous Windows).',
'settings.mcp.copy': 'Copier',
'settings.mcp.copied': 'Copié !',
'settings.mcp.apiTokens': 'Tokens API',
@@ -249,6 +247,48 @@ const fr: Record = {
'settings.mcp.toast.createError': 'Impossible de créer le token',
'settings.mcp.toast.deleted': 'Token supprimé',
'settings.mcp.toast.deleteError': 'Impossible de supprimer le token',
+ 'settings.mcp.apiTokensDeprecated': 'Les tokens API sont dépréciés et seront supprimés dans une prochaine version. Veuillez utiliser les clients OAuth 2.1 à la place.',
+ 'settings.oauth.clients': 'Clients OAuth 2.1',
+ 'settings.oauth.clientsHint': 'Enregistrez des clients OAuth 2.1 pour permettre à des applications MCP tierces (Claude Web, Cursor, etc.) de se connecter sans tokens statiques.',
+ 'settings.oauth.createClient': 'Nouveau client',
+ 'settings.oauth.noClients': 'Aucun client OAuth enregistré.',
+ 'settings.oauth.clientId': 'ID client',
+ 'settings.oauth.clientSecret': 'Secret client',
+ 'settings.oauth.deleteClient': 'Supprimer le client',
+ 'settings.oauth.deleteClientMessage': 'Ce client et toutes les sessions actives seront définitivement supprimés. Toute application l\'utilisant perdra immédiatement l\'accès.',
+ 'settings.oauth.rotateSecret': 'Renouveler le secret',
+ 'settings.oauth.rotateSecretMessage': 'Un nouveau secret client sera généré et toutes les sessions existantes seront immédiatement invalidées. Mettez à jour votre application avant de fermer cette fenêtre.',
+ 'settings.oauth.rotateSecretConfirm': 'Renouveler',
+ 'settings.oauth.rotateSecretConfirming': 'Renouvellement…',
+ 'settings.oauth.rotateSecretDoneTitle': 'Nouveau secret généré',
+ 'settings.oauth.rotateSecretDoneWarning': 'Ce secret n\'est affiché qu\'une seule fois. Copiez-le maintenant et mettez à jour votre application — toutes les sessions précédentes ont été invalidées.',
+ 'settings.oauth.activeSessions': 'Sessions OAuth actives',
+ 'settings.oauth.sessionScopes': 'Portées',
+ 'settings.oauth.sessionExpires': 'Expire',
+ 'settings.oauth.revoke': 'Révoquer',
+ 'settings.oauth.revokeSession': 'Révoquer la session',
+ 'settings.oauth.revokeSessionMessage': 'Cela révoquera immédiatement l\'accès pour cette session OAuth.',
+ 'settings.oauth.modal.createTitle': 'Enregistrer un client OAuth',
+ 'settings.oauth.modal.presets': 'Préréglages rapides',
+ 'settings.oauth.modal.clientName': 'Nom de l\'application',
+ 'settings.oauth.modal.clientNamePlaceholder': 'ex. Claude Web, Mon app MCP',
+ 'settings.oauth.modal.redirectUris': 'URIs de redirection',
+ 'settings.oauth.modal.redirectUrisPlaceholder': 'https://your-app.com/callback\nhttps://your-app.com/auth',
+ 'settings.oauth.modal.redirectUrisHint': 'Une URI par ligne. HTTPS requis (localhost exempté). Correspondance exacte.',
+ 'settings.oauth.modal.scopes': 'Portées autorisées',
+ 'settings.oauth.modal.scopesHint': 'list_trips et get_trip_summary sont toujours disponibles — aucune portée requise. Ils permettent à l\'IA de découvrir les IDs de voyage nécessaires.',
+ 'settings.oauth.modal.selectAll': 'Tout sélectionner',
+ 'settings.oauth.modal.deselectAll': 'Tout désélectionner',
+ 'settings.oauth.modal.creating': 'Enregistrement…',
+ 'settings.oauth.modal.create': 'Enregistrer le client',
+ 'settings.oauth.modal.createdTitle': 'Client enregistré',
+ 'settings.oauth.modal.createdWarning': 'Le secret client n\'est affiché qu\'une seule fois. Copiez-le maintenant — il ne peut pas être récupéré.',
+ 'settings.oauth.toast.createError': 'Impossible d\'enregistrer le client OAuth',
+ 'settings.oauth.toast.deleted': 'Client OAuth supprimé',
+ 'settings.oauth.toast.deleteError': 'Impossible de supprimer le client OAuth',
+ 'settings.oauth.toast.revoked': 'Session révoquée',
+ 'settings.oauth.toast.revokeError': 'Impossible de révoquer la session',
+ 'settings.oauth.toast.rotateError': 'Impossible de renouveler le secret client',
'settings.account': 'Compte',
'settings.about': 'À propos',
'settings.about.reportBug': 'Signaler un bug',
@@ -560,9 +600,10 @@ const fr: Record = {
'admin.audit.col.details': 'Détails',
// MCP Tokens
- 'admin.tabs.mcpTokens': 'Tokens MCP',
- 'admin.mcpTokens.title': 'Tokens MCP',
- 'admin.mcpTokens.subtitle': 'Gérer les tokens API de tous les utilisateurs',
+ 'admin.tabs.mcpTokens': 'Accès MCP',
+ 'admin.mcpTokens.title': 'Accès MCP',
+ 'admin.mcpTokens.subtitle': 'Gérer les sessions OAuth et les tokens API de tous les utilisateurs',
+ 'admin.mcpTokens.sectionTitle': 'Tokens API',
'admin.mcpTokens.owner': 'Propriétaire',
'admin.mcpTokens.tokenName': 'Nom du token',
'admin.mcpTokens.created': 'Créé',
@@ -574,6 +615,17 @@ const fr: Record = {
'admin.mcpTokens.deleteSuccess': 'Token supprimé',
'admin.mcpTokens.deleteError': 'Impossible de supprimer le token',
'admin.mcpTokens.loadError': 'Impossible de charger les tokens',
+ 'admin.oauthSessions.sectionTitle': 'Sessions OAuth',
+ 'admin.oauthSessions.clientName': 'Client',
+ 'admin.oauthSessions.owner': 'Propriétaire',
+ 'admin.oauthSessions.scopes': 'Portées',
+ 'admin.oauthSessions.created': 'Créé',
+ 'admin.oauthSessions.empty': 'Aucune session OAuth active',
+ 'admin.oauthSessions.revokeTitle': 'Révoquer la session',
+ 'admin.oauthSessions.revokeMessage': 'Cette session OAuth sera révoquée immédiatement. Le client perdra l\'accès MCP.',
+ 'admin.oauthSessions.revokeSuccess': 'Session révoquée',
+ 'admin.oauthSessions.revokeError': 'Impossible de révoquer la session',
+ 'admin.oauthSessions.loadError': 'Impossible de charger les sessions OAuth',
// GitHub
'admin.tabs.github': 'GitHub',
@@ -1005,6 +1057,7 @@ const fr: Record = {
'budget.totalBudget': 'Budget total',
'budget.byCategory': 'Par catégorie',
'budget.editTooltip': 'Cliquez pour modifier',
+ 'budget.linkedToReservation': 'Lié à une réservation — modifiez le nom depuis celle-ci',
'budget.confirm.deleteCategory': 'Voulez-vous vraiment supprimer la catégorie « {name} » avec {count} entrées ?',
'budget.deleteCategory': 'Supprimer la catégorie',
'budget.perPerson': 'Par personne',
@@ -1103,6 +1156,9 @@ const fr: Record = {
'packing.template': 'Modèle',
'packing.templateApplied': '{count} articles ajoutés depuis le modèle',
'packing.templateError': 'Erreur lors de l\'application du modèle',
+ 'packing.saveAsTemplate': 'Enregistrer comme modèle',
+ 'packing.templateName': 'Nom du modèle',
+ 'packing.templateSaved': 'Liste de voyage enregistrée comme modèle',
'packing.assignUser': 'Assigner un utilisateur',
'packing.noMembers': 'Aucun membre',
'packing.bags': 'Bagages',
@@ -1378,6 +1434,7 @@ const fr: Record = {
'memories.title': 'Photos',
'memories.notConnected': 'Immich non connecté',
'memories.notConnectedHint': 'Connectez votre instance Immich dans les paramètres pour voir vos photos de voyage ici.',
+ 'memories.notConnectedMultipleHint': 'Connectez un de ces fournisseurs de photos : {provider_names} dans les Paramètres pour pouvoir ajouter des photos à ce voyage.',
'memories.noDates': 'Ajoutez des dates à votre voyage pour charger les photos.',
'memories.noPhotos': 'Aucune photo trouvée',
'memories.noPhotosHint': 'Aucune photo trouvée dans Immich pour la période de ce voyage.',
@@ -1388,26 +1445,35 @@ const fr: Record = {
'memories.reviewTitle': 'Vérifier vos photos',
'memories.reviewHint': 'Cliquez sur les photos pour les exclure du partage.',
'memories.shareCount': 'Partager {count} photos',
- 'memories.immichUrl': 'URL du serveur Immich',
- 'memories.immichApiKey': 'Clé API',
+ 'memories.providerUrl': 'URL du serveur',
+ 'memories.providerApiKey': 'Clé API',
+ 'memories.providerUsername': 'Nom d\'utilisateur',
+ 'memories.providerPassword': 'Mot de passe',
+ 'memories.providerOTP': 'Code MFA (si activé)',
+ 'memories.skipSSLVerification': 'Ignorer la vérification du certificat SSL',
+ 'memories.providerUrlHintSynology': 'Incluez le chemin de l\'application Photos dans l\'URL, ex. https://nas:5001/photo',
'memories.testConnection': 'Tester la connexion',
'memories.testFirst': 'Testez la connexion avant de sauvegarder',
'memories.connected': 'Connecté',
'memories.disconnected': 'Non connecté',
'memories.connectionSuccess': 'Connecté à Immich',
'memories.connectionError': 'Impossible de se connecter à Immich',
- 'memories.saved': 'Paramètres Immich enregistrés',
+ 'memories.saved': 'Paramètres {provider_name} enregistrés',
+ 'memories.providerDisconnectedBanner': 'Votre connexion {provider_name} est perdue. Reconnectez-vous dans les Paramètres pour voir les photos.',
+ 'memories.saveError': 'Impossible d\'enregistrer les paramètres de {provider_name}',
'memories.oldest': 'Plus anciennes',
'memories.newest': 'Plus récentes',
'memories.allLocations': 'Tous les lieux',
'memories.addPhotos': 'Ajouter des photos',
'memories.linkAlbum': 'Lier un album',
'memories.selectAlbum': 'Choisir un album Immich',
+ 'memories.selectAlbumMultiple': 'Sélectionner un album',
'memories.noAlbums': 'Aucun album trouvé',
'memories.syncAlbum': 'Synchroniser',
'memories.unlinkAlbum': 'Délier',
'memories.photos': 'photos',
'memories.selectPhotos': 'Sélectionner des photos depuis Immich',
+ 'memories.selectPhotosMultiple': 'Sélectionner des photos',
'memories.selectHint': 'Appuyez sur les photos pour les sélectionner.',
'memories.selected': 'sélectionné(s)',
'memories.addSelected': 'Ajouter {count} photos',
@@ -1564,6 +1630,8 @@ const fr: Record = {
'notifications.markUnread': 'Marquer comme non lu',
'notifications.delete': 'Supprimer',
'notifications.system': 'Système',
+ 'notifications.synologySessionCleared.title': 'Synology Photos déconnecté',
+ 'notifications.synologySessionCleared.text': 'Votre serveur ou compte a changé — allez dans Paramètres pour tester à nouveau votre connexion.',
'memories.error.loadAlbums': 'Impossible de charger les albums',
'memories.error.linkAlbum': 'Impossible de lier l\'album',
'memories.error.unlinkAlbum': 'Impossible de dissocier l\'album',
@@ -1919,6 +1987,69 @@ const fr: Record = {
'dayplan.mobile.createNew': 'Créer un nouveau lieu',
'admin.addons.catalog.journey.name': 'Journal de voyage',
'admin.addons.catalog.journey.description': 'Suivi de voyages et journal avec check-ins, photos et récits quotidiens',
+ // OAuth scope groups
+ 'oauth.scope.group.trips': 'Voyages',
+ 'oauth.scope.group.places': 'Lieux',
+ 'oauth.scope.group.atlas': 'Atlas',
+ 'oauth.scope.group.packing': 'Bagages',
+ 'oauth.scope.group.todos': 'Tâches',
+ 'oauth.scope.group.budget': 'Budget',
+ 'oauth.scope.group.reservations': 'Réservations',
+ 'oauth.scope.group.collab': 'Collaboration',
+ 'oauth.scope.group.notifications': 'Notifications',
+ 'oauth.scope.group.vacay': 'Congés',
+ 'oauth.scope.group.geo': 'Géo',
+ 'oauth.scope.group.weather': 'Météo',
+
+ // OAuth scope labels & descriptions
+ 'oauth.scope.trips:read.label': 'Voir les voyages et itinéraires',
+ 'oauth.scope.trips:read.description': 'Lire les voyages, jours, notes et membres',
+ 'oauth.scope.trips:write.label': 'Modifier les voyages et itinéraires',
+ 'oauth.scope.trips:write.description': 'Créer et mettre à jour les voyages, jours, notes et gérer les membres',
+ 'oauth.scope.trips:delete.label': 'Supprimer des voyages',
+ 'oauth.scope.trips:delete.description': 'Supprimer définitivement des voyages entiers — cette action est irréversible',
+ 'oauth.scope.trips:share.label': 'Gérer les liens de partage',
+ 'oauth.scope.trips:share.description': 'Créer, modifier et révoquer des liens de partage publics',
+ 'oauth.scope.places:read.label': 'Voir les lieux et données cartographiques',
+ 'oauth.scope.places:read.description': 'Lire les lieux, affectations de jours, étiquettes et catégories',
+ 'oauth.scope.places:write.label': 'Gérer les lieux',
+ 'oauth.scope.places:write.description': 'Créer, modifier et supprimer des lieux, affectations et étiquettes',
+ 'oauth.scope.atlas:read.label': 'Voir l\'Atlas',
+ 'oauth.scope.atlas:read.description': 'Lire les pays visités, régions et liste de souhaits',
+ 'oauth.scope.atlas:write.label': 'Gérer l\'Atlas',
+ 'oauth.scope.atlas:write.description': 'Marquer des pays et régions visités, gérer la liste de souhaits',
+ 'oauth.scope.packing:read.label': 'Voir les listes de bagages',
+ 'oauth.scope.packing:read.description': 'Lire les articles, sacs et assignations de catégories',
+ 'oauth.scope.packing:write.label': 'Gérer les listes de bagages',
+ 'oauth.scope.packing:write.description': 'Ajouter, modifier, supprimer, cocher et réordonner les articles et sacs',
+ 'oauth.scope.todos:read.label': 'Voir les listes de tâches',
+ 'oauth.scope.todos:read.description': 'Lire les tâches et assignations de catégories',
+ 'oauth.scope.todos:write.label': 'Gérer les listes de tâches',
+ 'oauth.scope.todos:write.description': 'Créer, modifier, cocher, supprimer et réordonner les tâches',
+ 'oauth.scope.budget:read.label': 'Voir le budget',
+ 'oauth.scope.budget:read.description': 'Lire les dépenses et la répartition du budget',
+ 'oauth.scope.budget:write.label': 'Gérer le budget',
+ 'oauth.scope.budget:write.description': 'Créer, modifier et supprimer des dépenses',
+ 'oauth.scope.reservations:read.label': 'Voir les réservations',
+ 'oauth.scope.reservations:read.description': 'Lire les réservations et détails d\'hébergement',
+ 'oauth.scope.reservations:write.label': 'Gérer les réservations',
+ 'oauth.scope.reservations:write.description': 'Créer, modifier, supprimer et réordonner les réservations',
+ 'oauth.scope.collab:read.label': 'Voir la collaboration',
+ 'oauth.scope.collab:read.description': 'Lire les notes, sondages et messages collaboratifs',
+ 'oauth.scope.collab:write.label': 'Gérer la collaboration',
+ 'oauth.scope.collab:write.description': 'Créer, modifier et supprimer des notes, sondages et messages',
+ 'oauth.scope.notifications:read.label': 'Voir les notifications',
+ 'oauth.scope.notifications:read.description': 'Lire les notifications et le nombre de non-lus',
+ 'oauth.scope.notifications:write.label': 'Gérer les notifications',
+ 'oauth.scope.notifications:write.description': 'Marquer les notifications comme lues et y répondre',
+ 'oauth.scope.vacay:read.label': 'Voir les plans de congés',
+ 'oauth.scope.vacay:read.description': 'Lire les données, entrées et statistiques de congés',
+ 'oauth.scope.vacay:write.label': 'Gérer les plans de congés',
+ 'oauth.scope.vacay:write.description': 'Créer et gérer les entrées de congés, jours fériés et plans d\'équipe',
+ 'oauth.scope.geo:read.label': 'Cartes et géocodage',
+ 'oauth.scope.geo:read.description': 'Chercher des lieux, résoudre des URL cartographiques et géocoder des coordonnées',
+ 'oauth.scope.weather:read.label': 'Prévisions météo',
+ 'oauth.scope.weather:read.description': 'Obtenir les prévisions météo pour les lieux et dates de voyage',
}
export default fr
diff --git a/client/src/i18n/translations/hu.ts b/client/src/i18n/translations/hu.ts
index 42c6d460..9efc6a91 100644
--- a/client/src/i18n/translations/hu.ts
+++ b/client/src/i18n/translations/hu.ts
@@ -180,6 +180,7 @@ const hu: Record = {
'settings.mcp.endpoint': 'MCP végpont',
'settings.mcp.clientConfig': 'Kliens konfiguráció',
'settings.mcp.clientConfigHint': 'Cserélje ki a részt egy API tokenre az alábbi listából. Az npx elérési útját szükség lehet módosítani a rendszeréhez (pl. C:\\PROGRA~1\\nodejs\\npx.cmd Windows-on).',
+ 'settings.mcp.clientConfigHintOAuth': 'Cserélje ki a és részeket a fent létrehozott OAuth 2.1 kliens adataival. Az mcp-remote megnyitja a böngészőt az első csatlakozáskor az engedélyezés elvégzéséhez. Az npx elérési útját szükség lehet módosítani a rendszeréhez (pl. C:\\PROGRA~1\\nodejs\\npx.cmd Windows-on).',
'settings.mcp.copy': 'Másolás',
'settings.mcp.copied': 'Másolva!',
'settings.mcp.apiTokens': 'API tokenek',
@@ -201,6 +202,48 @@ const hu: Record = {
'settings.mcp.toast.createError': 'Nem sikerült létrehozni a tokent',
'settings.mcp.toast.deleted': 'Token törölve',
'settings.mcp.toast.deleteError': 'Nem sikerült törölni a tokent',
+ 'settings.mcp.apiTokensDeprecated': 'Az API tokenek elavultak és egy jövőbeli verzióban eltávolításra kerülnek. Kérjük, használjon helyettük OAuth 2.1 klienseket.',
+ 'settings.oauth.clients': 'OAuth 2.1 kliensek',
+ 'settings.oauth.clientsHint': 'Regisztráljon OAuth 2.1 klienseket, hogy a harmadik féltől származó MCP alkalmazások (Claude Web, Cursor stb.) statikus tokenek nélkül csatlakozhassanak.',
+ 'settings.oauth.createClient': 'Új kliens',
+ 'settings.oauth.noClients': 'Nincs regisztrált OAuth kliens.',
+ 'settings.oauth.clientId': 'Kliens azonosító',
+ 'settings.oauth.clientSecret': 'Kliens titok',
+ 'settings.oauth.deleteClient': 'Kliens törlése',
+ 'settings.oauth.deleteClientMessage': 'Ez a kliens és az összes aktív munkamenet véglegesen törlésre kerül. Minden alkalmazás, amely ezt használja, azonnal elveszíti a hozzáférést.',
+ 'settings.oauth.rotateSecret': 'Titok megújítása',
+ 'settings.oauth.rotateSecretMessage': 'Új kliens titok kerül generálásra és az összes meglévő munkamenet azonnal érvénytelenné válik. Frissítse alkalmazását a párbeszéd bezárása előtt.',
+ 'settings.oauth.rotateSecretConfirm': 'Megújítás',
+ 'settings.oauth.rotateSecretConfirming': 'Megújítás…',
+ 'settings.oauth.rotateSecretDoneTitle': 'Új titok generálva',
+ 'settings.oauth.rotateSecretDoneWarning': 'Ez a titok csak egyszer jelenik meg. Másolja most és frissítse alkalmazását — az összes korábbi munkamenet érvénytelenné vált.',
+ 'settings.oauth.activeSessions': 'Aktív OAuth munkamenetek',
+ 'settings.oauth.sessionScopes': 'Jogosultságok',
+ 'settings.oauth.sessionExpires': 'Lejár',
+ 'settings.oauth.revoke': 'Visszavonás',
+ 'settings.oauth.revokeSession': 'Munkamenet visszavonása',
+ 'settings.oauth.revokeSessionMessage': 'Ez azonnal visszavonja a hozzáférést ehhez az OAuth munkamenethez.',
+ 'settings.oauth.modal.createTitle': 'OAuth kliens regisztrálása',
+ 'settings.oauth.modal.presets': 'Gyors beállítások',
+ 'settings.oauth.modal.clientName': 'Alkalmazás neve',
+ 'settings.oauth.modal.clientNamePlaceholder': 'pl. Claude Web, Az én MCP appom',
+ 'settings.oauth.modal.redirectUris': 'Átirányítási URI-k',
+ 'settings.oauth.modal.redirectUrisPlaceholder': 'https://your-app.com/callback\nhttps://your-app.com/auth',
+ 'settings.oauth.modal.redirectUrisHint': 'Soronként egy URI. HTTPS szükséges (localhost kivételével). Pontos egyezés szükséges.',
+ 'settings.oauth.modal.scopes': 'Engedélyezett jogosultságok',
+ 'settings.oauth.modal.scopesHint': 'A list_trips és get_trip_summary mindig elérhető — jogosultság nélkül. Segítenek az AI-nak megtalálni az utazás azonosítókat.',
+ 'settings.oauth.modal.selectAll': 'Összes kijelölése',
+ 'settings.oauth.modal.deselectAll': 'Összes kijelölés törlése',
+ 'settings.oauth.modal.creating': 'Regisztrálás…',
+ 'settings.oauth.modal.create': 'Kliens regisztrálása',
+ 'settings.oauth.modal.createdTitle': 'Kliens regisztrálva',
+ 'settings.oauth.modal.createdWarning': 'A kliens titok csak egyszer jelenik meg. Másolja most — nem állítható helyre.',
+ 'settings.oauth.toast.createError': 'Az OAuth kliens regisztrálása sikertelen',
+ 'settings.oauth.toast.deleted': 'OAuth kliens törölve',
+ 'settings.oauth.toast.deleteError': 'Az OAuth kliens törlése sikertelen',
+ 'settings.oauth.toast.revoked': 'Munkamenet visszavonva',
+ 'settings.oauth.toast.revokeError': 'A munkamenet visszavonása sikertelen',
+ 'settings.oauth.toast.rotateError': 'A kliens titok megújítása sikertelen',
'settings.account': 'Fiók',
'settings.about': 'Névjegy',
'settings.about.reportBug': 'Hiba bejelentése',
@@ -274,9 +317,6 @@ const hu: Record = {
'admin.notifications.none': 'Kikapcsolva',
'admin.notifications.email': 'E-mail (SMTP)',
'admin.notifications.webhook': 'Webhook',
- 'admin.notifications.events': 'Értesítési események',
- 'admin.notifications.eventsHint': 'Válaszd ki, mely események indítsanak értesítéseket minden felhasználó számára.',
- 'admin.notifications.configureFirst': 'Először konfiguráld az SMTP vagy webhook beállításokat lent, majd engedélyezd az eseményeket.',
'admin.notifications.save': 'Értesítési beállítások mentése',
'admin.notifications.saved': 'Értesítési beállítások mentve',
'admin.notifications.testWebhook': 'Teszt webhook küldése',
@@ -561,9 +601,10 @@ const hu: Record = {
'admin.audit.col.details': 'Részletek',
// MCP Tokens
- 'admin.tabs.mcpTokens': 'MCP tokenek',
- 'admin.mcpTokens.title': 'MCP tokenek',
- 'admin.mcpTokens.subtitle': 'Összes felhasználó API tokeneinek kezelése',
+ 'admin.tabs.mcpTokens': 'MCP hozzáférés',
+ 'admin.mcpTokens.title': 'MCP hozzáférés',
+ 'admin.mcpTokens.subtitle': 'OAuth munkamenetek és API tokenek kezelése az összes felhasználó számára',
+ 'admin.mcpTokens.sectionTitle': 'API tokenek',
'admin.mcpTokens.owner': 'Tulajdonos',
'admin.mcpTokens.tokenName': 'Token neve',
'admin.mcpTokens.created': 'Létrehozva',
@@ -575,6 +616,17 @@ const hu: Record = {
'admin.mcpTokens.deleteSuccess': 'Token törölve',
'admin.mcpTokens.deleteError': 'Nem sikerült törölni a tokent',
'admin.mcpTokens.loadError': 'Nem sikerült betölteni a tokeneket',
+ 'admin.oauthSessions.sectionTitle': 'OAuth munkamenetek',
+ 'admin.oauthSessions.clientName': 'Kliens',
+ 'admin.oauthSessions.owner': 'Tulajdonos',
+ 'admin.oauthSessions.scopes': 'Jogosultságok',
+ 'admin.oauthSessions.created': 'Létrehozva',
+ 'admin.oauthSessions.empty': 'Nincsenek aktív OAuth munkamenetek',
+ 'admin.oauthSessions.revokeTitle': 'Munkamenet visszavonása',
+ 'admin.oauthSessions.revokeMessage': 'Ez az OAuth munkamenet azonnal visszavonásra kerül. A kliens elveszíti az MCP hozzáférést.',
+ 'admin.oauthSessions.revokeSuccess': 'Munkamenet visszavonva',
+ 'admin.oauthSessions.revokeError': 'Nem sikerült visszavonni a munkamenetet',
+ 'admin.oauthSessions.loadError': 'Nem sikerült betölteni az OAuth munkameneteket',
// GitHub
'admin.tabs.github': 'GitHub',
@@ -1006,6 +1058,7 @@ const hu: Record = {
'budget.totalBudget': 'Teljes költségvetés',
'budget.byCategory': 'Kategóriánként',
'budget.editTooltip': 'Kattints a szerkesztéshez',
+ 'budget.linkedToReservation': 'Foglaláshoz kapcsolva — ott szerkessze a nevet',
'budget.confirm.deleteCategory': 'Biztosan törölni szeretnéd a(z) "{name}" kategóriát {count} bejegyzéssel?',
'budget.deleteCategory': 'Kategória törlése',
'budget.perPerson': 'Személyenként',
@@ -1106,6 +1159,9 @@ const hu: Record = {
'packing.template': 'Sablon',
'packing.templateApplied': '{count} tétel hozzáadva a sablonból',
'packing.templateError': 'Nem sikerült alkalmazni a sablont',
+ 'packing.saveAsTemplate': 'Mentés sablonként',
+ 'packing.templateName': 'Sablon neve',
+ 'packing.templateSaved': 'Csomaglista elmentve sablonként',
'packing.bags': 'Táskák',
'packing.noBag': 'Nincs hozzárendelve',
'packing.totalWeight': 'Összsúly',
@@ -1449,6 +1505,7 @@ const hu: Record = {
'memories.title': 'Fotók',
'memories.notConnected': 'Immich nincs csatlakoztatva',
'memories.notConnectedHint': 'Csatlakoztasd az Immich példányodat a Beállításokban, hogy itt lásd az utazási fotóidat.',
+ 'memories.notConnectedMultipleHint': 'A fényképek hozzáadásához csatlakoztasson egyet a következő fényképszolgáltatók közül a Beállításokban: {provider_names}.',
'memories.noDates': 'Adj hozzá dátumokat az utazáshoz a fotók betöltéséhez.',
'memories.noPhotos': 'Nem találhatók fotók',
'memories.noPhotosHint': 'Nem találhatók fotók az Immichben erre az utazási időszakra.',
@@ -1459,23 +1516,32 @@ const hu: Record = {
'memories.reviewTitle': 'Nézd át a fotóidat',
'memories.reviewHint': 'Kattints a fotókra a megosztásból való kizáráshoz.',
'memories.shareCount': '{count} fotó megosztása',
- 'memories.immichUrl': 'Immich szerver URL',
- 'memories.immichApiKey': 'API kulcs',
+ 'memories.providerUrl': 'Szerver URL',
+ 'memories.providerApiKey': 'API kulcs',
+ 'memories.providerUsername': 'Felhasználónév',
+ 'memories.providerPassword': 'Jelszó',
+ 'memories.providerOTP': 'MFA kód (ha engedélyezve van)',
+ 'memories.skipSSLVerification': 'SSL tanúsítvány ellenőrzésének kihagyása',
+ 'memories.providerUrlHintSynology': 'Adja meg a Photos alkalmazás elérési útját az URL-ben, pl. https://nas:5001/photo',
'memories.testConnection': 'Kapcsolat tesztelése',
'memories.testFirst': 'Először teszteld a kapcsolatot',
'memories.connected': 'Csatlakoztatva',
'memories.disconnected': 'Nincs csatlakoztatva',
'memories.connectionSuccess': 'Csatlakozva az Immichhez',
'memories.connectionError': 'Nem sikerült csatlakozni az Immichhez',
- 'memories.saved': 'Immich beállítások mentve',
+ 'memories.saved': '{provider_name} beállítások mentve',
+ 'memories.providerDisconnectedBanner': 'A {provider_name} kapcsolat megszakadt. Csatlakozzon újra a Beállításokban a fényképek megtekintéséhez.',
+ 'memories.saveError': 'Nem sikerült menteni a(z) {provider_name} beállításait',
'memories.addPhotos': 'Fotók hozzáadása',
'memories.linkAlbum': 'Album csatolása',
'memories.selectAlbum': 'Immich album kiválasztása',
+ 'memories.selectAlbumMultiple': 'Album kiválasztása',
'memories.noAlbums': 'Nem található album',
'memories.syncAlbum': 'Album szinkronizálása',
'memories.unlinkAlbum': 'Leválasztás',
'memories.photos': 'fotó',
'memories.selectPhotos': 'Fotók kiválasztása az Immichből',
+ 'memories.selectPhotosMultiple': 'Fényképek kiválasztása',
'memories.selectHint': 'Koppints a fotókra a kijelölésükhöz.',
'memories.selected': 'kijelölve',
'memories.addSelected': '{count} fotó hozzáadása',
@@ -1565,6 +1631,8 @@ const hu: Record = {
'notifications.markUnread': 'Olvasatlannak jelölés',
'notifications.delete': 'Törlés',
'notifications.system': 'Rendszer',
+ 'notifications.synologySessionCleared.title': 'Synology Photos leválasztva',
+ 'notifications.synologySessionCleared.text': 'A szerver vagy a fiók megváltozott — lépjen a Beállításokba a kapcsolat újrateszteléséhez.',
'memories.error.loadAlbums': 'Az albumok betöltése sikertelen',
'memories.error.linkAlbum': 'Az album csatolása sikertelen',
'memories.error.unlinkAlbum': 'Az album leválasztása sikertelen',
@@ -1920,6 +1988,69 @@ const hu: Record = {
'dayplan.mobile.createNew': 'Új helyszín létrehozása',
'admin.addons.catalog.journey.name': 'Útinaplók',
'admin.addons.catalog.journey.description': 'Utazáskövetés és útinapló bejelentkezésekkel, fotókkal és napi történetekkel',
+ // OAuth scope groups
+ 'oauth.scope.group.trips': 'Utazások',
+ 'oauth.scope.group.places': 'Helyek',
+ 'oauth.scope.group.atlas': 'Atlas',
+ 'oauth.scope.group.packing': 'Csomagolás',
+ 'oauth.scope.group.todos': 'Feladatok',
+ 'oauth.scope.group.budget': 'Költségvetés',
+ 'oauth.scope.group.reservations': 'Foglalások',
+ 'oauth.scope.group.collab': 'Együttműködés',
+ 'oauth.scope.group.notifications': 'Értesítések',
+ 'oauth.scope.group.vacay': 'Szabadság',
+ 'oauth.scope.group.geo': 'Geo',
+ 'oauth.scope.group.weather': 'Időjárás',
+
+ // OAuth scope labels & descriptions
+ 'oauth.scope.trips:read.label': 'Utazások és útvonalak megtekintése',
+ 'oauth.scope.trips:read.description': 'Utazások, napok, napi feljegyzések és tagok olvasása',
+ 'oauth.scope.trips:write.label': 'Utazások és útvonalak szerkesztése',
+ 'oauth.scope.trips:write.description': 'Utazások, napok és feljegyzések létrehozása, frissítése és tagok kezelése',
+ 'oauth.scope.trips:delete.label': 'Utazások törlése',
+ 'oauth.scope.trips:delete.description': 'Teljes utazások végleges törlése — ez a művelet visszafordíthatatlan',
+ 'oauth.scope.trips:share.label': 'Megosztási linkek kezelése',
+ 'oauth.scope.trips:share.description': 'Nyilvános megosztási linkek létrehozása, frissítése és visszavonása',
+ 'oauth.scope.places:read.label': 'Helyek és térképadatok megtekintése',
+ 'oauth.scope.places:read.description': 'Helyek, napi hozzárendelések, címkék és kategóriák olvasása',
+ 'oauth.scope.places:write.label': 'Helyek kezelése',
+ 'oauth.scope.places:write.description': 'Helyek, hozzárendelések és címkék létrehozása, frissítése és törlése',
+ 'oauth.scope.atlas:read.label': 'Atlas megtekintése',
+ 'oauth.scope.atlas:read.description': 'Meglátogatott országok, régiók és bakancslisták olvasása',
+ 'oauth.scope.atlas:write.label': 'Atlas kezelése',
+ 'oauth.scope.atlas:write.description': 'Országok és régiók meglátogatottként jelölése, bakancslisták kezelése',
+ 'oauth.scope.packing:read.label': 'Csomaglisták megtekintése',
+ 'oauth.scope.packing:read.description': 'Csomagolási tételek, táskák és kategória-hozzárendelések olvasása',
+ 'oauth.scope.packing:write.label': 'Csomaglisták kezelése',
+ 'oauth.scope.packing:write.description': 'Csomagolási tételek és táskák hozzáadása, frissítése, törlése, jelölése és átrendezése',
+ 'oauth.scope.todos:read.label': 'Feladatlisták megtekintése',
+ 'oauth.scope.todos:read.description': 'Utazás feladatai és kategória-hozzárendelések olvasása',
+ 'oauth.scope.todos:write.label': 'Feladatlisták kezelése',
+ 'oauth.scope.todos:write.description': 'Feladatok létrehozása, frissítése, jelölése, törlése és átrendezése',
+ 'oauth.scope.budget:read.label': 'Költségvetés megtekintése',
+ 'oauth.scope.budget:read.description': 'Költségvetési tételek és kiadások részletezésének olvasása',
+ 'oauth.scope.budget:write.label': 'Költségvetés kezelése',
+ 'oauth.scope.budget:write.description': 'Költségvetési tételek létrehozása, frissítése és törlése',
+ 'oauth.scope.reservations:read.label': 'Foglalások megtekintése',
+ 'oauth.scope.reservations:read.description': 'Foglalások és szállásadatok olvasása',
+ 'oauth.scope.reservations:write.label': 'Foglalások kezelése',
+ 'oauth.scope.reservations:write.description': 'Foglalások létrehozása, frissítése, törlése és átrendezése',
+ 'oauth.scope.collab:read.label': 'Együttműködés megtekintése',
+ 'oauth.scope.collab:read.description': 'Együttműködési feljegyzések, szavazások és üzenetek olvasása',
+ 'oauth.scope.collab:write.label': 'Együttműködés kezelése',
+ 'oauth.scope.collab:write.description': 'Együttműködési feljegyzések, szavazások és üzenetek létrehozása, frissítése és törlése',
+ 'oauth.scope.notifications:read.label': 'Értesítések megtekintése',
+ 'oauth.scope.notifications:read.description': 'Alkalmazáson belüli értesítések és olvasatlan számok olvasása',
+ 'oauth.scope.notifications:write.label': 'Értesítések kezelése',
+ 'oauth.scope.notifications:write.description': 'Értesítések olvasottként jelölése és válaszadás rájuk',
+ 'oauth.scope.vacay:read.label': 'Szabadságtervek megtekintése',
+ 'oauth.scope.vacay:read.description': 'Szabadságtervezési adatok, bejegyzések és statisztikák olvasása',
+ 'oauth.scope.vacay:write.label': 'Szabadságtervek kezelése',
+ 'oauth.scope.vacay:write.description': 'Szabadságbejegyzések, ünnepnapok és csapattervek létrehozása és kezelése',
+ 'oauth.scope.geo:read.label': 'Térképek és geokódolás',
+ 'oauth.scope.geo:read.description': 'Helyek keresése, térkép URL-ek feloldása és koordináták fordított geokódolása',
+ 'oauth.scope.weather:read.label': 'Időjárás-előrejelzések',
+ 'oauth.scope.weather:read.description': 'Időjárás-előrejelzések lekérése az utazási helyszínekre és dátumokra',
}
export default hu
diff --git a/client/src/i18n/translations/it.ts b/client/src/i18n/translations/it.ts
index 541d69d0..62bca2f9 100644
--- a/client/src/i18n/translations/it.ts
+++ b/client/src/i18n/translations/it.ts
@@ -180,6 +180,7 @@ const it: Record = {
'settings.mcp.endpoint': 'Endpoint MCP',
'settings.mcp.clientConfig': 'Configurazione client',
'settings.mcp.clientConfigHint': 'Sostituisci con un token API dalla lista sottostante. Il percorso di npx potrebbe dover essere adattato per il tuo sistema (es. C:\\PROGRA~1\\nodejs\\npx.cmd su Windows).',
+ 'settings.mcp.clientConfigHintOAuth': 'Replace and with the credentials shown in the OAuth 2.1 client you created above. mcp-remote will open your browser to complete the authorization the first time you connect. The path to npx may need to be adjusted for your system (e.g. C:\PROGRA~1\nodejs\npx.cmd on Windows).',
'settings.mcp.copy': 'Copia',
'settings.mcp.copied': 'Copiato!',
'settings.mcp.apiTokens': 'Token API',
@@ -201,6 +202,48 @@ const it: Record = {
'settings.mcp.toast.createError': 'Impossibile creare il token',
'settings.mcp.toast.deleted': 'Token eliminato',
'settings.mcp.toast.deleteError': 'Impossibile eliminare il token',
+ 'settings.mcp.apiTokensDeprecated': 'I token API sono deprecati e verranno rimossi in una versione futura. Utilizza invece i client OAuth 2.1.',
+ 'settings.oauth.clients': 'Client OAuth 2.1',
+ 'settings.oauth.clientsHint': 'Registra client OAuth 2.1 per consentire alle applicazioni MCP di terze parti (Claude Web, Cursor, ecc.) di connettersi senza token statici.',
+ 'settings.oauth.createClient': 'Nuovo client',
+ 'settings.oauth.noClients': 'Nessun client OAuth registrato.',
+ 'settings.oauth.clientId': 'ID client',
+ 'settings.oauth.clientSecret': 'Segreto client',
+ 'settings.oauth.deleteClient': 'Elimina client',
+ 'settings.oauth.deleteClientMessage': 'Questo client e tutte le sessioni attive verranno eliminati definitivamente. Qualsiasi applicazione che lo utilizza perderà immediatamente l\'accesso.',
+ 'settings.oauth.rotateSecret': 'Rinnova segreto',
+ 'settings.oauth.rotateSecretMessage': 'Verrà generato un nuovo segreto client e tutte le sessioni esistenti verranno invalidate immediatamente. Aggiorna la tua applicazione prima di chiudere questa finestra.',
+ 'settings.oauth.rotateSecretConfirm': 'Rinnova',
+ 'settings.oauth.rotateSecretConfirming': 'Rinnovo in corso…',
+ 'settings.oauth.rotateSecretDoneTitle': 'Nuovo segreto generato',
+ 'settings.oauth.rotateSecretDoneWarning': 'Questo segreto viene mostrato una sola volta. Copialo ora e aggiorna la tua applicazione — tutte le sessioni precedenti sono state invalidate.',
+ 'settings.oauth.activeSessions': 'Sessioni OAuth attive',
+ 'settings.oauth.sessionScopes': 'Ambiti',
+ 'settings.oauth.sessionExpires': 'Scade',
+ 'settings.oauth.revoke': 'Revoca',
+ 'settings.oauth.revokeSession': 'Revoca sessione',
+ 'settings.oauth.revokeSessionMessage': 'Questo revocherà immediatamente l\'accesso per questa sessione OAuth.',
+ 'settings.oauth.modal.createTitle': 'Registra client OAuth',
+ 'settings.oauth.modal.presets': 'Preimpostazioni rapide',
+ 'settings.oauth.modal.clientName': 'Nome applicazione',
+ 'settings.oauth.modal.clientNamePlaceholder': 'es. Claude Web, La mia app MCP',
+ 'settings.oauth.modal.redirectUris': 'URI di reindirizzamento',
+ 'settings.oauth.modal.redirectUrisPlaceholder': 'https://your-app.com/callback\nhttps://your-app.com/auth',
+ 'settings.oauth.modal.redirectUrisHint': 'Un URI per riga. HTTPS richiesto (localhost esente). Corrispondenza esatta richiesta.',
+ 'settings.oauth.modal.scopes': 'Ambiti consentiti',
+ 'settings.oauth.modal.scopesHint': 'list_trips e get_trip_summary sono sempre disponibili — nessun ambito richiesto. Permettono all\'IA di scoprire gli ID viaggio necessari.',
+ 'settings.oauth.modal.selectAll': 'Seleziona tutto',
+ 'settings.oauth.modal.deselectAll': 'Deseleziona tutto',
+ 'settings.oauth.modal.creating': 'Registrazione…',
+ 'settings.oauth.modal.create': 'Registra client',
+ 'settings.oauth.modal.createdTitle': 'Client registrato',
+ 'settings.oauth.modal.createdWarning': 'Il segreto client viene mostrato una sola volta. Copialo ora — non può essere recuperato.',
+ 'settings.oauth.toast.createError': 'Impossibile registrare il client OAuth',
+ 'settings.oauth.toast.deleted': 'Client OAuth eliminato',
+ 'settings.oauth.toast.deleteError': 'Impossibile eliminare il client OAuth',
+ 'settings.oauth.toast.revoked': 'Sessione revocata',
+ 'settings.oauth.toast.revokeError': 'Impossibile revocare la sessione',
+ 'settings.oauth.toast.rotateError': 'Impossibile rinnovare il segreto client',
'settings.account': 'Account',
'settings.about': 'Informazioni',
'settings.about.reportBug': 'Segnala un bug',
@@ -211,7 +254,7 @@ const it: Record = {
'settings.about.description': 'TREK è un pianificatore di viaggi self-hosted che ti aiuta a organizzare i tuoi viaggi dalla prima idea all\'ultimo ricordo. Pianificazione giornaliera, budget, liste bagagli, foto e molto altro — tutto in un unico posto, sul tuo server.',
'settings.about.madeWith': 'Fatto con',
'settings.about.madeBy': 'da Maurice e una crescente comunità open-source.',
- 'settings.username': 'Username',
+ 'settings.username': 'Nome utente',
'settings.email': 'Email',
'settings.role': 'Ruolo',
'settings.roleAdmin': 'Amministratore',
@@ -274,9 +317,6 @@ const it: Record = {
'admin.notifications.none': 'Disattivato',
'admin.notifications.email': 'E-mail (SMTP)',
'admin.notifications.webhook': 'Webhook',
- 'admin.notifications.events': 'Eventi di notifica',
- 'admin.notifications.eventsHint': 'Scegli quali eventi attivano le notifiche per tutti gli utenti.',
- 'admin.notifications.configureFirst': 'Configura prima le impostazioni SMTP o webhook qui sotto, poi abilita gli eventi.',
'admin.notifications.save': 'Salva impostazioni notifiche',
'admin.notifications.saved': 'Impostazioni notifiche salvate',
'admin.notifications.testWebhook': 'Invia webhook di test',
@@ -354,7 +394,7 @@ const it: Record = {
'login.hasAccount': 'Hai già un account?',
'login.register': 'Registrati',
'login.emailPlaceholder': 'tua@email.com',
- 'login.username': 'Username',
+ 'login.username': 'Nome utente',
'login.oidc.registrationDisabled': 'La registrazione è disabilitata. Contatta il tuo amministratore.',
'login.oidc.noEmail': 'Nessuna email ricevuta dal provider.',
'login.oidc.tokenFailed': 'Autenticazione fallita.',
@@ -561,9 +601,10 @@ const it: Record = {
'admin.audit.col.details': 'Dettagli',
// MCP Tokens
- 'admin.tabs.mcpTokens': 'Token MCP',
- 'admin.mcpTokens.title': 'Token MCP',
- 'admin.mcpTokens.subtitle': 'Gestisci i token API di tutti gli utenti',
+ 'admin.tabs.mcpTokens': 'Accesso MCP',
+ 'admin.mcpTokens.title': 'Accesso MCP',
+ 'admin.mcpTokens.subtitle': 'Gestisci le sessioni OAuth e i token API di tutti gli utenti',
+ 'admin.mcpTokens.sectionTitle': 'Token API',
'admin.mcpTokens.owner': 'Proprietario',
'admin.mcpTokens.tokenName': 'Nome token',
'admin.mcpTokens.created': 'Creato',
@@ -575,6 +616,17 @@ const it: Record = {
'admin.mcpTokens.deleteSuccess': 'Token eliminato',
'admin.mcpTokens.deleteError': 'Impossibile eliminare il token',
'admin.mcpTokens.loadError': 'Impossibile caricare i token',
+ 'admin.oauthSessions.sectionTitle': 'Sessioni OAuth',
+ 'admin.oauthSessions.clientName': 'Client',
+ 'admin.oauthSessions.owner': 'Proprietario',
+ 'admin.oauthSessions.scopes': 'Ambiti',
+ 'admin.oauthSessions.created': 'Creato',
+ 'admin.oauthSessions.empty': 'Nessuna sessione OAuth attiva',
+ 'admin.oauthSessions.revokeTitle': 'Revoca sessione',
+ 'admin.oauthSessions.revokeMessage': 'Questa sessione OAuth verrà revocata immediatamente. Il client perderà l\'accesso MCP.',
+ 'admin.oauthSessions.revokeSuccess': 'Sessione revocata',
+ 'admin.oauthSessions.revokeError': 'Impossibile revocare la sessione',
+ 'admin.oauthSessions.loadError': 'Impossibile caricare le sessioni OAuth',
// GitHub
'admin.tabs.github': 'GitHub',
@@ -1006,6 +1058,7 @@ const it: Record = {
'budget.totalBudget': 'Budget totale',
'budget.byCategory': 'Per categoria',
'budget.editTooltip': 'Clicca per modificare',
+ 'budget.linkedToReservation': 'Collegato a una prenotazione — modifica il nome lì',
'budget.confirm.deleteCategory': 'Sei sicuro di voler eliminare la categoria "{name}" con {count} voci?',
'budget.deleteCategory': 'Elimina categoria',
'budget.perPerson': 'Per persona',
@@ -1106,6 +1159,9 @@ const it: Record = {
'packing.template': 'Modello',
'packing.templateApplied': '{count} elementi aggiunti dal modello',
'packing.templateError': 'Impossibile applicare il modello',
+ 'packing.saveAsTemplate': 'Salva come modello',
+ 'packing.templateName': 'Nome modello',
+ 'packing.templateSaved': 'Lista bagagli salvata come modello',
'packing.bags': 'Valigie',
'packing.noBag': 'Non assegnato',
'packing.totalWeight': 'Peso totale',
@@ -1379,6 +1435,7 @@ const it: Record = {
'memories.title': 'Foto',
'memories.notConnected': 'Immich non connesso',
'memories.notConnectedHint': 'Connetti la tua istanza Immich nelle Impostazioni per vedere qui le foto del tuo viaggio.',
+ 'memories.notConnectedMultipleHint': 'Collega uno di questi provider di foto: {provider_names} nelle Impostazioni per poter aggiungere foto a questo viaggio.',
'memories.noDates': 'Aggiungi le date al tuo viaggio per caricare le foto.',
'memories.noPhotos': 'Nessuna foto trovata',
'memories.noPhotosHint': 'Nessuna foto trovata in Immich per l\'intervallo di date di questo viaggio.',
@@ -1389,23 +1446,32 @@ const it: Record = {
'memories.reviewTitle': 'Rivedi le tue foto',
'memories.reviewHint': 'Clicca sulle foto per escluderle dalla condivisione.',
'memories.shareCount': 'Condividi {count} foto',
- 'memories.immichUrl': 'URL Server Immich',
- 'memories.immichApiKey': 'Chiave API',
+ 'memories.providerUrl': 'URL del server',
+ 'memories.providerApiKey': 'Chiave API',
+ 'memories.providerUsername': 'Nome utente',
+ 'memories.providerPassword': 'Password',
+ 'memories.providerOTP': 'Codice MFA (se abilitato)',
+ 'memories.skipSSLVerification': 'Ignora la verifica del certificato SSL',
+ 'memories.providerUrlHintSynology': 'Includi il percorso dell\'app Foto nell\'URL, es. https://nas:5001/photo',
'memories.testConnection': 'Test connessione',
'memories.testFirst': 'Testa prima la connessione',
'memories.connected': 'Connesso',
'memories.disconnected': 'Non connesso',
'memories.connectionSuccess': 'Connesso a Immich',
'memories.connectionError': 'Impossibile connettersi a Immich',
- 'memories.saved': 'Impostazioni Immich salvate',
+ 'memories.saved': 'Impostazioni {provider_name} salvate',
+ 'memories.providerDisconnectedBanner': 'La connessione a {provider_name} è persa. Riconnetti nelle Impostazioni per visualizzare le foto.',
+ 'memories.saveError': 'Impossibile salvare le impostazioni di {provider_name}',
'memories.addPhotos': 'Aggiungi foto',
'memories.linkAlbum': 'Collega album',
'memories.selectAlbum': 'Seleziona album Immich',
+ 'memories.selectAlbumMultiple': 'Seleziona album',
'memories.noAlbums': 'Nessun album trovato',
'memories.syncAlbum': 'Sincronizza album',
'memories.unlinkAlbum': 'Scollega',
'memories.photos': 'foto',
'memories.selectPhotos': 'Seleziona foto da Immich',
+ 'memories.selectPhotosMultiple': 'Seleziona foto',
'memories.selectHint': 'Tocca le foto per selezionarle.',
'memories.selected': 'selezionate',
'memories.addSelected': 'Aggiungi {count} foto',
@@ -1567,6 +1633,8 @@ const it: Record = {
'notifications.markUnread': 'Segna come non letto',
'notifications.delete': 'Elimina',
'notifications.system': 'Sistema',
+ 'notifications.synologySessionCleared.title': 'Synology Photos disconnesso',
+ 'notifications.synologySessionCleared.text': 'Il server o l\'account è cambiato — vai alle Impostazioni per testare nuovamente la connessione.',
'memories.error.loadAlbums': 'Caricamento album non riuscito',
'memories.error.linkAlbum': 'Collegamento album non riuscito',
'memories.error.unlinkAlbum': 'Scollegamento album non riuscito',
@@ -1648,7 +1716,7 @@ const it: Record = {
'admin.notifications.adminWebhookPanel.testFailed': 'Invio webhook di test fallito',
'admin.notifications.adminWebhookPanel.alwaysOnHint': 'Il webhook admin si attiva automaticamente quando è configurato un URL',
'admin.notifications.adminNotificationsHint': 'Configura quali canali consegnano le notifiche admin (es. avvisi di versione). Il webhook si attiva automaticamente se è impostato un URL webhook admin.',
- 'admin.tabs.notifications': 'Notifications',
+ 'admin.tabs.notifications': 'Notifiche',
'notifications.versionAvailable.title': 'Aggiornamento disponibile',
'notifications.versionAvailable.text': 'TREK {version} è ora disponibile.',
'notifications.versionAvailable.button': 'Visualizza dettagli',
@@ -1920,6 +1988,69 @@ const it: Record = {
'dayplan.mobile.createNew': 'Crea nuovo luogo',
'admin.addons.catalog.journey.name': 'Diario di viaggio',
'admin.addons.catalog.journey.description': 'Tracciamento viaggi e diario con check-in, foto e storie quotidiane',
+ // OAuth scope groups
+ 'oauth.scope.group.trips': 'Viaggi',
+ 'oauth.scope.group.places': 'Luoghi',
+ 'oauth.scope.group.atlas': 'Atlas',
+ 'oauth.scope.group.packing': 'Bagagli',
+ 'oauth.scope.group.todos': 'Attività',
+ 'oauth.scope.group.budget': 'Budget',
+ 'oauth.scope.group.reservations': 'Prenotazioni',
+ 'oauth.scope.group.collab': 'Collaborazione',
+ 'oauth.scope.group.notifications': 'Notifiche',
+ 'oauth.scope.group.vacay': 'Ferie',
+ 'oauth.scope.group.geo': 'Geo',
+ 'oauth.scope.group.weather': 'Meteo',
+
+ // OAuth scope labels & descriptions
+ 'oauth.scope.trips:read.label': 'Visualizza viaggi e itinerari',
+ 'oauth.scope.trips:read.description': 'Leggi viaggi, giorni, note giornaliere e membri',
+ 'oauth.scope.trips:write.label': 'Modifica viaggi e itinerari',
+ 'oauth.scope.trips:write.description': 'Crea e aggiorna viaggi, giorni, note e gestisci membri',
+ 'oauth.scope.trips:delete.label': 'Elimina viaggi',
+ 'oauth.scope.trips:delete.description': 'Elimina definitivamente interi viaggi — questa azione è irreversibile',
+ 'oauth.scope.trips:share.label': 'Gestisci link di condivisione',
+ 'oauth.scope.trips:share.description': 'Crea, aggiorna e revoca link di condivisione pubblici per i viaggi',
+ 'oauth.scope.places:read.label': 'Visualizza luoghi e dati mappa',
+ 'oauth.scope.places:read.description': 'Leggi luoghi, assegnazioni giornaliere, tag e categorie',
+ 'oauth.scope.places:write.label': 'Gestisci luoghi',
+ 'oauth.scope.places:write.description': 'Crea, aggiorna ed elimina luoghi, assegnazioni e tag',
+ 'oauth.scope.atlas:read.label': 'Visualizza Atlas',
+ 'oauth.scope.atlas:read.description': 'Leggi paesi visitati, regioni e lista dei desideri',
+ 'oauth.scope.atlas:write.label': 'Gestisci Atlas',
+ 'oauth.scope.atlas:write.description': 'Segna paesi e regioni come visitati, gestisci la lista dei desideri',
+ 'oauth.scope.packing:read.label': 'Visualizza liste bagagli',
+ 'oauth.scope.packing:read.description': 'Leggi articoli, borse e assegnatari di categoria',
+ 'oauth.scope.packing:write.label': 'Gestisci liste bagagli',
+ 'oauth.scope.packing:write.description': 'Aggiungi, aggiorna, elimina, spunta e riordina articoli e borse',
+ 'oauth.scope.todos:read.label': 'Visualizza liste attività',
+ 'oauth.scope.todos:read.description': 'Leggi attività del viaggio e assegnatari di categoria',
+ 'oauth.scope.todos:write.label': 'Gestisci liste attività',
+ 'oauth.scope.todos:write.description': 'Crea, aggiorna, spunta, elimina e riordina attività',
+ 'oauth.scope.budget:read.label': 'Visualizza budget',
+ 'oauth.scope.budget:read.description': 'Leggi voci di budget e ripartizione delle spese',
+ 'oauth.scope.budget:write.label': 'Gestisci budget',
+ 'oauth.scope.budget:write.description': 'Crea, aggiorna ed elimina voci di budget',
+ 'oauth.scope.reservations:read.label': 'Visualizza prenotazioni',
+ 'oauth.scope.reservations:read.description': 'Leggi prenotazioni e dettagli alloggio',
+ 'oauth.scope.reservations:write.label': 'Gestisci prenotazioni',
+ 'oauth.scope.reservations:write.description': 'Crea, aggiorna, elimina e riordina prenotazioni',
+ 'oauth.scope.collab:read.label': 'Visualizza collaborazione',
+ 'oauth.scope.collab:read.description': 'Leggi note collaborative, sondaggi e messaggi',
+ 'oauth.scope.collab:write.label': 'Gestisci collaborazione',
+ 'oauth.scope.collab:write.description': 'Crea, aggiorna ed elimina note collaborative, sondaggi e messaggi',
+ 'oauth.scope.notifications:read.label': 'Visualizza notifiche',
+ 'oauth.scope.notifications:read.description': 'Leggi notifiche in-app e conteggi non letti',
+ 'oauth.scope.notifications:write.label': 'Gestisci notifiche',
+ 'oauth.scope.notifications:write.description': 'Segna notifiche come lette e rispondi',
+ 'oauth.scope.vacay:read.label': 'Visualizza piani ferie',
+ 'oauth.scope.vacay:read.description': 'Leggi dati di pianificazione ferie, voci e statistiche',
+ 'oauth.scope.vacay:write.label': 'Gestisci piani ferie',
+ 'oauth.scope.vacay:write.description': 'Crea e gestisci voci ferie, festività e piani del team',
+ 'oauth.scope.geo:read.label': 'Mappe e geocodifica',
+ 'oauth.scope.geo:read.description': 'Cerca luoghi, risolvi URL mappa e geocodifica inversa coordinate',
+ 'oauth.scope.weather:read.label': 'Previsioni meteo',
+ 'oauth.scope.weather:read.description': 'Ottieni previsioni meteo per luoghi e date del viaggio',
}
export default it
diff --git a/client/src/i18n/translations/nl.ts b/client/src/i18n/translations/nl.ts
index 9b7a4d68..79532979 100644
--- a/client/src/i18n/translations/nl.ts
+++ b/client/src/i18n/translations/nl.ts
@@ -179,9 +179,6 @@ const nl: Record = {
'admin.notifications.none': 'Uitgeschakeld',
'admin.notifications.email': 'E-mail (SMTP)',
'admin.notifications.webhook': 'Webhook',
- 'admin.notifications.events': 'Meldingsgebeurtenissen',
- 'admin.notifications.eventsHint': 'Kies welke gebeurtenissen meldingen activeren voor alle gebruikers.',
- 'admin.notifications.configureFirst': 'Configureer eerst de SMTP- of webhook-instellingen hieronder en schakel dan de events in.',
'admin.notifications.save': 'Meldingsinstellingen opslaan',
'admin.notifications.saved': 'Meldingsinstellingen opgeslagen',
'admin.notifications.testWebhook': 'Testwebhook verzenden',
@@ -228,6 +225,7 @@ const nl: Record = {
'settings.mcp.endpoint': 'MCP-eindpunt',
'settings.mcp.clientConfig': 'Clientconfiguratie',
'settings.mcp.clientConfigHint': 'Vervang door een API-token uit de onderstaande lijst. Het pad naar npx moet mogelijk worden aangepast voor jouw systeem (bijv. C:\\PROGRA~1\\nodejs\\npx.cmd op Windows).',
+ 'settings.mcp.clientConfigHintOAuth': 'Replace and with the credentials shown in the OAuth 2.1 client you created above. mcp-remote will open your browser to complete the authorization the first time you connect. The path to npx may need to be adjusted for your system (e.g. C:\PROGRA~1\nodejs\npx.cmd on Windows).',
'settings.mcp.copy': 'Kopiëren',
'settings.mcp.copied': 'Gekopieerd!',
'settings.mcp.apiTokens': 'API-tokens',
@@ -249,6 +247,48 @@ const nl: Record = {
'settings.mcp.toast.createError': 'Token aanmaken mislukt',
'settings.mcp.toast.deleted': 'Token verwijderd',
'settings.mcp.toast.deleteError': 'Token verwijderen mislukt',
+ 'settings.mcp.apiTokensDeprecated': 'API-tokens zijn verouderd en worden in een toekomstige versie verwijderd. Gebruik OAuth 2.1-clients in plaats daarvan.',
+ 'settings.oauth.clients': 'OAuth 2.1-clients',
+ 'settings.oauth.clientsHint': 'Registreer OAuth 2.1-clients zodat externe MCP-toepassingen (Claude Web, Cursor, enz.) verbinding kunnen maken zonder statische tokens.',
+ 'settings.oauth.createClient': 'Nieuwe client',
+ 'settings.oauth.noClients': 'Geen OAuth-clients geregistreerd.',
+ 'settings.oauth.clientId': 'Client-ID',
+ 'settings.oauth.clientSecret': 'Clientgeheim',
+ 'settings.oauth.deleteClient': 'Client verwijderen',
+ 'settings.oauth.deleteClientMessage': 'Deze client en alle actieve sessies worden permanent verwijderd. Elke toepassing die deze client gebruikt, verliest onmiddellijk de toegang.',
+ 'settings.oauth.rotateSecret': 'Geheim vernieuwen',
+ 'settings.oauth.rotateSecretMessage': 'Er wordt een nieuw clientgeheim gegenereerd en alle bestaande sessies worden direct ongeldig. Werk uw toepassing bij voordat u dit venster sluit.',
+ 'settings.oauth.rotateSecretConfirm': 'Vernieuwen',
+ 'settings.oauth.rotateSecretConfirming': 'Vernieuwen…',
+ 'settings.oauth.rotateSecretDoneTitle': 'Nieuw geheim gegenereerd',
+ 'settings.oauth.rotateSecretDoneWarning': 'Dit geheim wordt slechts eenmalig getoond. Kopieer het nu en werk uw toepassing bij — alle vorige sessies zijn ongeldig gemaakt.',
+ 'settings.oauth.activeSessions': 'Actieve OAuth-sessies',
+ 'settings.oauth.sessionScopes': 'Rechten',
+ 'settings.oauth.sessionExpires': 'Verloopt',
+ 'settings.oauth.revoke': 'Intrekken',
+ 'settings.oauth.revokeSession': 'Sessie intrekken',
+ 'settings.oauth.revokeSessionMessage': 'Dit trekt onmiddellijk de toegang voor deze OAuth-sessie in.',
+ 'settings.oauth.modal.createTitle': 'OAuth-client registreren',
+ 'settings.oauth.modal.presets': 'Snelle instellingen',
+ 'settings.oauth.modal.clientName': 'Toepassingsnaam',
+ 'settings.oauth.modal.clientNamePlaceholder': 'bijv. Claude Web, Mijn MCP-app',
+ 'settings.oauth.modal.redirectUris': 'Redirect-URI\'s',
+ 'settings.oauth.modal.redirectUrisPlaceholder': 'https://your-app.com/callback\nhttps://your-app.com/auth',
+ 'settings.oauth.modal.redirectUrisHint': 'Eén URI per regel. HTTPS vereist (localhost uitgezonderd). Exacte overeenkomst vereist.',
+ 'settings.oauth.modal.scopes': 'Toegestane rechten',
+ 'settings.oauth.modal.scopesHint': 'list_trips en get_trip_summary zijn altijd beschikbaar — geen recht vereist. Ze helpen de AI trip-ID\'s te ontdekken.',
+ 'settings.oauth.modal.selectAll': 'Alles selecteren',
+ 'settings.oauth.modal.deselectAll': 'Alles deselecteren',
+ 'settings.oauth.modal.creating': 'Registreren…',
+ 'settings.oauth.modal.create': 'Client registreren',
+ 'settings.oauth.modal.createdTitle': 'Client geregistreerd',
+ 'settings.oauth.modal.createdWarning': 'Het clientgeheim wordt slechts eenmalig getoond. Kopieer het nu — het kan niet worden hersteld.',
+ 'settings.oauth.toast.createError': 'OAuth-client kon niet worden geregistreerd',
+ 'settings.oauth.toast.deleted': 'OAuth-client verwijderd',
+ 'settings.oauth.toast.deleteError': 'OAuth-client kon niet worden verwijderd',
+ 'settings.oauth.toast.revoked': 'Sessie ingetrokken',
+ 'settings.oauth.toast.revokeError': 'Sessie kon niet worden ingetrokken',
+ 'settings.oauth.toast.rotateError': 'Clientgeheim kon niet worden vernieuwd',
'settings.account': 'Account',
'settings.about': 'Over',
'settings.about.reportBug': 'Bug melden',
@@ -515,11 +555,11 @@ const nl: Record = {
'admin.addons.catalog.budget.description': 'Houd uitgaven bij en plan je reisbudget',
'admin.addons.catalog.documents.name': 'Documenten',
'admin.addons.catalog.documents.description': 'Bewaar en beheer reisdocumenten',
- 'admin.addons.catalog.vacay.name': 'Vacay',
+ 'admin.addons.catalog.vacay.name': 'Vakantie',
'admin.addons.catalog.vacay.description': 'Persoonlijke vakantieplanner met kalenderweergave',
'admin.addons.catalog.atlas.name': 'Atlas',
'admin.addons.catalog.atlas.description': 'Wereldkaart met bezochte landen en reisstatistieken',
- 'admin.addons.catalog.collab.name': 'Collab',
+ 'admin.addons.catalog.collab.name': 'Samenwerking',
'admin.addons.catalog.collab.description': 'Realtime notities, polls en chat voor het plannen van reizen',
'admin.addons.subtitleBefore': 'Schakel functies in of uit om je ',
'admin.addons.subtitleAfter': '-ervaring aan te passen.',
@@ -547,9 +587,10 @@ const nl: Record = {
'admin.weather.locationHint': 'Het weer is gebaseerd op de eerste plaats met coördinaten op elke dag. Als er geen plaats aan een dag is toegewezen, wordt een plaats uit de lijst als referentie gebruikt.',
// MCP Tokens
- 'admin.tabs.mcpTokens': 'MCP-tokens',
- 'admin.mcpTokens.title': 'MCP-tokens',
- 'admin.mcpTokens.subtitle': 'API-tokens van alle gebruikers beheren',
+ 'admin.tabs.mcpTokens': 'MCP-toegang',
+ 'admin.mcpTokens.title': 'MCP-toegang',
+ 'admin.mcpTokens.subtitle': 'OAuth-sessies en API-tokens van alle gebruikers beheren',
+ 'admin.mcpTokens.sectionTitle': 'API-tokens',
'admin.mcpTokens.owner': 'Eigenaar',
'admin.mcpTokens.tokenName': 'Tokennaam',
'admin.mcpTokens.created': 'Aangemaakt',
@@ -561,6 +602,17 @@ const nl: Record = {
'admin.mcpTokens.deleteSuccess': 'Token verwijderd',
'admin.mcpTokens.deleteError': 'Token kon niet worden verwijderd',
'admin.mcpTokens.loadError': 'Tokens konden niet worden geladen',
+ 'admin.oauthSessions.sectionTitle': 'OAuth-sessies',
+ 'admin.oauthSessions.clientName': 'Client',
+ 'admin.oauthSessions.owner': 'Eigenaar',
+ 'admin.oauthSessions.scopes': 'Rechten',
+ 'admin.oauthSessions.created': 'Aangemaakt',
+ 'admin.oauthSessions.empty': 'Geen actieve OAuth-sessies',
+ 'admin.oauthSessions.revokeTitle': 'Sessie intrekken',
+ 'admin.oauthSessions.revokeMessage': 'Deze OAuth-sessie wordt onmiddellijk ingetrokken. De client verliest MCP-toegang.',
+ 'admin.oauthSessions.revokeSuccess': 'Sessie ingetrokken',
+ 'admin.oauthSessions.revokeError': 'Sessie kon niet worden ingetrokken',
+ 'admin.oauthSessions.loadError': 'OAuth-sessies konden niet worden geladen',
// GitHub
'admin.tabs.github': 'GitHub',
@@ -731,7 +783,7 @@ const nl: Record = {
'atlas.placeVisited': 'Bezochte plaats',
'atlas.placesVisited': 'Bezochte plaatsen',
'atlas.statsTab': 'Statistieken',
- 'atlas.bucketTab': 'Bucket List',
+ 'atlas.bucketTab': 'Bucketlist',
'atlas.addBucket': 'Toevoegen aan bucket list',
'atlas.bucketNamePlaceholder': 'Plaats of bestemming...',
'atlas.bucketNotesPlaceholder': 'Notities (optioneel)',
@@ -842,7 +894,7 @@ const nl: Record = {
'places.noCategory': 'Geen categorie',
'places.categoryNamePlaceholder': 'Categorienaam',
'places.formTime': 'Tijd',
- 'places.startTime': 'Start',
+ 'places.startTime': 'Starttijd',
'places.endTime': 'Einde',
'places.endTimeBeforeStart': 'Eindtijd is vóór de starttijd',
'places.timeCollision': 'Tijdoverlap met:',
@@ -858,7 +910,7 @@ const nl: Record = {
'places.nameRequired': 'Voer een naam in',
'places.saveError': 'Opslaan mislukt',
// Place Inspector
- 'inspector.opened': 'Open',
+ 'inspector.opened': 'Openingstijden',
'inspector.closed': 'Gesloten',
'inspector.openingHours': 'Openingstijden',
'inspector.showHours': 'Openingstijden tonen',
@@ -904,8 +956,8 @@ const nl: Record = {
'reservations.meta.trainNumber': 'Treinnr.',
'reservations.meta.platform': 'Perron',
'reservations.meta.seat': 'Stoel',
- 'reservations.meta.checkIn': 'Check-in',
- 'reservations.meta.checkOut': 'Check-out',
+ 'reservations.meta.checkIn': 'Inchecken',
+ 'reservations.meta.checkOut': 'Uitchecken',
'reservations.meta.linkAccommodation': 'Accommodatie',
'reservations.meta.pickAccommodation': 'Koppel aan accommodatie',
'reservations.meta.noAccommodation': 'Geen',
@@ -1005,11 +1057,12 @@ const nl: Record = {
'budget.totalBudget': 'Totaal budget',
'budget.byCategory': 'Per categorie',
'budget.editTooltip': 'Klik om te bewerken',
+ 'budget.linkedToReservation': 'Gekoppeld aan een reservering — bewerk de naam daar',
'budget.confirm.deleteCategory': 'Weet je zeker dat je de categorie "{name}" met {count} invoeren wilt verwijderen?',
'budget.deleteCategory': 'Categorie verwijderen',
'budget.perPerson': 'Per persoon',
'budget.paid': 'Betaald',
- 'budget.open': 'Open',
+ 'budget.open': 'Openstaand',
'budget.noMembers': 'Geen leden toegewezen',
'budget.settlement': 'Afrekening',
'budget.settlementInfo': 'Klik op de avatar van een lid bij een budgetpost om deze groen te markeren — dit betekent dat diegene heeft betaald. De afrekening toont vervolgens wie wie hoeveel verschuldigd is.',
@@ -1086,7 +1139,7 @@ const nl: Record = {
'packing.addPlaceholder': 'Nieuw item toevoegen...',
'packing.categoryPlaceholder': 'Categorie...',
'packing.filterAll': 'Alle',
- 'packing.filterOpen': 'Open',
+ 'packing.filterOpen': 'Openstaand',
'packing.filterDone': 'Klaar',
'packing.emptyTitle': 'Paklijst is leeg',
'packing.emptyHint': 'Voeg items toe of gebruik de suggesties',
@@ -1095,6 +1148,7 @@ const nl: Record = {
'packing.menuCheckAll': 'Alles aanvinken',
'packing.menuUncheckAll': 'Alles uitvinken',
'packing.menuDeleteCat': 'Categorie verwijderen',
+ 'packing.assignUser': 'Gebruiker toewijzen',
'packing.addItem': 'Item toevoegen',
'packing.addItemPlaceholder': 'Itemnaam...',
'packing.addCategory': 'Categorie toevoegen',
@@ -1103,7 +1157,9 @@ const nl: Record = {
'packing.template': 'Sjabloon',
'packing.templateApplied': '{count} items toegevoegd vanuit sjabloon',
'packing.templateError': 'Fout bij toepassen van sjabloon',
- 'packing.assignUser': 'Gebruiker toewijzen',
+ 'packing.saveAsTemplate': 'Opslaan als sjabloon',
+ 'packing.templateName': 'Sjabloonnaam',
+ 'packing.templateSaved': 'Paklijst opgeslagen als sjabloon',
'packing.noMembers': 'Geen leden',
'packing.bags': 'Bagage',
'packing.noBag': 'Niet toegewezen',
@@ -1368,8 +1424,8 @@ const nl: Record = {
'day.hotelDayRange': 'Toepassen op dagen',
'day.noPlacesForHotel': 'Voeg eerst plaatsen toe aan je reis',
'day.allDays': 'Alle',
- 'day.checkIn': 'Check-in',
- 'day.checkOut': 'Check-out',
+ 'day.checkIn': 'Inchecken',
+ 'day.checkOut': 'Uitchecken',
'day.confirmation': 'Bevestiging',
'day.editAccommodation': 'Accommodatie bewerken',
'day.reservations': 'Reserveringen',
@@ -1378,6 +1434,7 @@ const nl: Record = {
'memories.title': 'Foto\'s',
'memories.notConnected': 'Immich niet verbonden',
'memories.notConnectedHint': 'Verbind je Immich-instantie in Instellingen om je reisfoto\'s hier te zien.',
+ 'memories.notConnectedMultipleHint': 'Verbind een van deze fotoproviders: {provider_names} in Instellingen om foto\'s aan dit reisplan toe te voegen.',
'memories.noDates': 'Voeg data toe aan je reis om foto\'s te laden.',
'memories.noPhotos': 'Geen foto\'s gevonden',
'memories.noPhotosHint': 'Geen foto\'s gevonden in Immich voor de datumreeks van deze reis.',
@@ -1388,26 +1445,35 @@ const nl: Record = {
'memories.reviewTitle': 'Je foto\'s bekijken',
'memories.reviewHint': 'Klik op foto\'s om ze uit te sluiten van delen.',
'memories.shareCount': '{count} foto\'s delen',
- 'memories.immichUrl': 'Immich Server URL',
- 'memories.immichApiKey': 'API-sleutel',
+ 'memories.providerUrl': 'Server-URL',
+ 'memories.providerApiKey': 'API-sleutel',
+ 'memories.providerUsername': 'Gebruikersnaam',
+ 'memories.providerPassword': 'Wachtwoord',
+ 'memories.providerOTP': 'MFA-code (indien ingeschakeld)',
+ 'memories.skipSSLVerification': 'SSL-certificaatverificatie overslaan',
+ 'memories.providerUrlHintSynology': 'Voeg het pad van de Photos-app toe aan de URL, bijv. https://nas:5001/photo',
'memories.testConnection': 'Verbinding testen',
'memories.testFirst': 'Test eerst de verbinding',
'memories.connected': 'Verbonden',
'memories.disconnected': 'Niet verbonden',
'memories.connectionSuccess': 'Verbonden met Immich',
'memories.connectionError': 'Kon niet verbinden met Immich',
- 'memories.saved': 'Immich-instellingen opgeslagen',
+ 'memories.saved': '{provider_name}-instellingen opgeslagen',
+ 'memories.providerDisconnectedBanner': 'Je {provider_name}-verbinding is verbroken. Maak opnieuw verbinding in Instellingen om foto\'s te bekijken.',
+ 'memories.saveError': '{provider_name}-instellingen konden niet worden opgeslagen',
'memories.oldest': 'Oudste eerst',
'memories.newest': 'Nieuwste eerst',
'memories.allLocations': 'Alle locaties',
'memories.addPhotos': 'Foto\'s toevoegen',
'memories.linkAlbum': 'Album koppelen',
'memories.selectAlbum': 'Immich-album selecteren',
+ 'memories.selectAlbumMultiple': 'Album selecteren',
'memories.noAlbums': 'Geen albums gevonden',
'memories.syncAlbum': 'Album synchroniseren',
'memories.unlinkAlbum': 'Ontkoppelen',
'memories.photos': 'fotos',
'memories.selectPhotos': 'Selecteer foto\'s uit Immich',
+ 'memories.selectPhotosMultiple': 'Foto\'s selecteren',
'memories.selectHint': 'Tik op foto\'s om ze te selecteren.',
'memories.selected': 'geselecteerd',
'memories.addSelected': '{count} foto\'s toevoegen',
@@ -1564,6 +1630,8 @@ const nl: Record = {
'notifications.markUnread': 'Markeren als ongelezen',
'notifications.delete': 'Verwijderen',
'notifications.system': 'Systeem',
+ 'notifications.synologySessionCleared.title': 'Synology Photos verbroken',
+ 'notifications.synologySessionCleared.text': 'Je server of account is gewijzigd — ga naar Instellingen om je verbinding opnieuw te testen.',
'memories.error.loadAlbums': 'Albums laden mislukt',
'memories.error.linkAlbum': 'Album koppelen mislukt',
'memories.error.unlinkAlbum': 'Album ontkoppelen mislukt',
@@ -1593,7 +1661,7 @@ const nl: Record = {
'todo.subtab.todo': 'Taken',
'todo.completed': 'voltooid',
'todo.filter.all': 'Alles',
- 'todo.filter.open': 'Open',
+ 'todo.filter.open': 'Openstaand',
'todo.filter.done': 'Klaar',
'todo.uncategorized': 'Zonder categorie',
'todo.namePlaceholder': 'Taaknaam',
@@ -1919,6 +1987,69 @@ const nl: Record = {
'dayplan.mobile.createNew': 'Nieuwe plaats aanmaken',
'admin.addons.catalog.journey.name': 'Reisverslag',
'admin.addons.catalog.journey.description': 'Reistracking & reisdagboek met check-ins, foto\'s en dagelijkse verhalen',
+ // OAuth scope groups
+ 'oauth.scope.group.trips': 'Reizen',
+ 'oauth.scope.group.places': 'Plaatsen',
+ 'oauth.scope.group.atlas': 'Atlas',
+ 'oauth.scope.group.packing': 'Paklijst',
+ 'oauth.scope.group.todos': 'Taken',
+ 'oauth.scope.group.budget': 'Budget',
+ 'oauth.scope.group.reservations': 'Reserveringen',
+ 'oauth.scope.group.collab': 'Samenwerking',
+ 'oauth.scope.group.notifications': 'Meldingen',
+ 'oauth.scope.group.vacay': 'Vakantie',
+ 'oauth.scope.group.geo': 'Geo',
+ 'oauth.scope.group.weather': 'Weer',
+
+ // OAuth scope labels & descriptions
+ 'oauth.scope.trips:read.label': 'Reizen en reisplannen bekijken',
+ 'oauth.scope.trips:read.description': 'Reizen, dagen, notities en leden lezen',
+ 'oauth.scope.trips:write.label': 'Reizen en reisplannen bewerken',
+ 'oauth.scope.trips:write.description': 'Reizen, dagen en notities aanmaken, bijwerken en leden beheren',
+ 'oauth.scope.trips:delete.label': 'Reizen verwijderen',
+ 'oauth.scope.trips:delete.description': 'Hele reizen permanent verwijderen — deze actie is onomkeerbaar',
+ 'oauth.scope.trips:share.label': 'Deellinks beheren',
+ 'oauth.scope.trips:share.description': 'Publieke deellinks aanmaken, bijwerken en intrekken',
+ 'oauth.scope.places:read.label': 'Plaatsen en kaartgegevens bekijken',
+ 'oauth.scope.places:read.description': 'Plaatsen, dagtoewijzingen, tags en categorieën lezen',
+ 'oauth.scope.places:write.label': 'Plaatsen beheren',
+ 'oauth.scope.places:write.description': 'Plaatsen, toewijzingen en tags aanmaken, bijwerken en verwijderen',
+ 'oauth.scope.atlas:read.label': 'Atlas bekijken',
+ 'oauth.scope.atlas:read.description': 'Bezochte landen, regio\'s en bucketlist lezen',
+ 'oauth.scope.atlas:write.label': 'Atlas beheren',
+ 'oauth.scope.atlas:write.description': 'Landen en regio\'s markeren als bezocht, bucketlist beheren',
+ 'oauth.scope.packing:read.label': 'Paklijsten bekijken',
+ 'oauth.scope.packing:read.description': 'Pakartikelen, tassen en categorietoewijzingen lezen',
+ 'oauth.scope.packing:write.label': 'Paklijsten beheren',
+ 'oauth.scope.packing:write.description': 'Pakartikelen en tassen toevoegen, bijwerken, verwijderen, omschakelen en herordenen',
+ 'oauth.scope.todos:read.label': 'Takenlijsten bekijken',
+ 'oauth.scope.todos:read.description': 'Reistaakitems en categorietoewijzingen lezen',
+ 'oauth.scope.todos:write.label': 'Takenlijsten beheren',
+ 'oauth.scope.todos:write.description': 'Taakitems aanmaken, bijwerken, omschakelen, verwijderen en herordenen',
+ 'oauth.scope.budget:read.label': 'Budget bekijken',
+ 'oauth.scope.budget:read.description': 'Budgetitems en kostenspecificatie lezen',
+ 'oauth.scope.budget:write.label': 'Budget beheren',
+ 'oauth.scope.budget:write.description': 'Budgetitems aanmaken, bijwerken en verwijderen',
+ 'oauth.scope.reservations:read.label': 'Reserveringen bekijken',
+ 'oauth.scope.reservations:read.description': 'Reserveringen en accommodatiedetails lezen',
+ 'oauth.scope.reservations:write.label': 'Reserveringen beheren',
+ 'oauth.scope.reservations:write.description': 'Reserveringen aanmaken, bijwerken, verwijderen en herordenen',
+ 'oauth.scope.collab:read.label': 'Samenwerking bekijken',
+ 'oauth.scope.collab:read.description': 'Samenwerkingsnotities, polls en berichten lezen',
+ 'oauth.scope.collab:write.label': 'Samenwerking beheren',
+ 'oauth.scope.collab:write.description': 'Samenwerkingsnotities, polls en berichten aanmaken, bijwerken en verwijderen',
+ 'oauth.scope.notifications:read.label': 'Meldingen bekijken',
+ 'oauth.scope.notifications:read.description': 'In-app meldingen en ongelezen aantallen lezen',
+ 'oauth.scope.notifications:write.label': 'Meldingen beheren',
+ 'oauth.scope.notifications:write.description': 'Meldingen als gelezen markeren en erop reageren',
+ 'oauth.scope.vacay:read.label': 'Vakantieplannen bekijken',
+ 'oauth.scope.vacay:read.description': 'Vakantieplanningsgegevens, invoeren en statistieken lezen',
+ 'oauth.scope.vacay:write.label': 'Vakantieplannen beheren',
+ 'oauth.scope.vacay:write.description': 'Vakantie-invoeren, feestdagen en teamplannen aanmaken en beheren',
+ 'oauth.scope.geo:read.label': 'Kaarten & geocodering',
+ 'oauth.scope.geo:read.description': 'Locaties zoeken, kaart-URL\'s oplossen en coördinaten omgekeerd geocoderen',
+ 'oauth.scope.weather:read.label': 'Weersverwachtingen',
+ 'oauth.scope.weather:read.description': 'Weersverwachtingen ophalen voor reislocaties en -datums',
}
export default nl
diff --git a/client/src/i18n/translations/pl.ts b/client/src/i18n/translations/pl.ts
index afe4e971..f85e28e0 100644
--- a/client/src/i18n/translations/pl.ts
+++ b/client/src/i18n/translations/pl.ts
@@ -29,7 +29,7 @@ const pl: Record = {
'common.change': 'Zmień',
'common.uploading': 'Przesyłanie...',
'common.backToPlanning': 'Powrót do planowania',
- 'common.reset': 'Reset',
+ 'common.reset': 'Resetuj',
// Navbar
'nav.trip': 'Podróż',
@@ -198,6 +198,7 @@ const pl: Record = {
'settings.mcp.endpoint': 'Endpoint MCP',
'settings.mcp.clientConfig': 'Konfiguracja klienta',
'settings.mcp.clientConfigHint': 'Zastąp tokenem API z listy poniżej. Ścieżka do npx może wymagać dostosowania do Twojego systemu (np. C:\\PROGRA~1\\nodejs\\npx.cmd w systemie Windows).',
+ 'settings.mcp.clientConfigHintOAuth': 'Zastąp i danymi uwierzytelniającymi z klienta OAuth 2.1 utworzonego powyżej. mcp-remote otworzy przeglądarkę, aby dokończyć autoryzację przy pierwszym połączeniu. Ścieżka do npx może wymagać dostosowania do Twojego systemu (np. C:\\PROGRA~1\\nodejs\\npx.cmd w systemie Windows).',
'settings.mcp.copy': 'Kopiuj',
'settings.mcp.copied': 'Skopiowano!',
'settings.mcp.apiTokens': 'Tokeny API',
@@ -219,6 +220,48 @@ const pl: Record = {
'settings.mcp.toast.createError': 'Nie udało się utworzyć tokenu',
'settings.mcp.toast.deleted': 'Token został usunięty',
'settings.mcp.toast.deleteError': 'Nie udało się usunąć tokenu',
+ 'settings.mcp.apiTokensDeprecated': 'Tokeny API są przestarzałe i zostaną usunięte w przyszłej wersji. Użyj zamiast tego klientów OAuth 2.1.',
+ 'settings.oauth.clients': 'Klienci OAuth 2.1',
+ 'settings.oauth.clientsHint': 'Zarejestruj klientów OAuth 2.1, aby zewnętrzne aplikacje MCP (Claude Web, Cursor itp.) mogły się łączyć bez statycznych tokenów.',
+ 'settings.oauth.createClient': 'Nowy klient',
+ 'settings.oauth.noClients': 'Brak zarejestrowanych klientów OAuth.',
+ 'settings.oauth.clientId': 'ID klienta',
+ 'settings.oauth.clientSecret': 'Sekret klienta',
+ 'settings.oauth.deleteClient': 'Usuń klienta',
+ 'settings.oauth.deleteClientMessage': 'Ten klient i wszystkie aktywne sesje zostaną trwale usunięte. Każda aplikacja, która go używa, natychmiast utraci dostęp.',
+ 'settings.oauth.rotateSecret': 'Odnów sekret',
+ 'settings.oauth.rotateSecretMessage': 'Zostanie wygenerowany nowy sekret klienta, a wszystkie istniejące sesje zostaną natychmiast unieważnione. Zaktualizuj aplikację przed zamknięciem tego okna.',
+ 'settings.oauth.rotateSecretConfirm': 'Odnów',
+ 'settings.oauth.rotateSecretConfirming': 'Odnawianie…',
+ 'settings.oauth.rotateSecretDoneTitle': 'Wygenerowano nowy sekret',
+ 'settings.oauth.rotateSecretDoneWarning': 'Ten sekret jest wyświetlany tylko raz. Skopiuj go teraz i zaktualizuj aplikację — wszystkie poprzednie sesje zostały unieważnione.',
+ 'settings.oauth.activeSessions': 'Aktywne sesje OAuth',
+ 'settings.oauth.sessionScopes': 'Uprawnienia',
+ 'settings.oauth.sessionExpires': 'Wygasa',
+ 'settings.oauth.revoke': 'Unieważnij',
+ 'settings.oauth.revokeSession': 'Unieważnij sesję',
+ 'settings.oauth.revokeSessionMessage': 'Spowoduje to natychmiastowe unieważnienie dostępu dla tej sesji OAuth.',
+ 'settings.oauth.modal.createTitle': 'Zarejestruj klienta OAuth',
+ 'settings.oauth.modal.presets': 'Szybkie ustawienia',
+ 'settings.oauth.modal.clientName': 'Nazwa aplikacji',
+ 'settings.oauth.modal.clientNamePlaceholder': 'np. Claude Web, Moja aplikacja MCP',
+ 'settings.oauth.modal.redirectUris': 'URI przekierowania',
+ 'settings.oauth.modal.redirectUrisPlaceholder': 'https://your-app.com/callback\nhttps://your-app.com/auth',
+ 'settings.oauth.modal.redirectUrisHint': 'Jeden URI na linię. Wymagane HTTPS (localhost zwolniony). Wymagana dokładna zgodność.',
+ 'settings.oauth.modal.scopes': 'Dozwolone uprawnienia',
+ 'settings.oauth.modal.scopesHint': 'list_trips i get_trip_summary są zawsze dostępne — bez wymaganych uprawnień. Umożliwiają AI odkrycie potrzebnych ID podróży.',
+ 'settings.oauth.modal.selectAll': 'Zaznacz wszystko',
+ 'settings.oauth.modal.deselectAll': 'Odznacz wszystko',
+ 'settings.oauth.modal.creating': 'Rejestrowanie…',
+ 'settings.oauth.modal.create': 'Zarejestruj klienta',
+ 'settings.oauth.modal.createdTitle': 'Klient zarejestrowany',
+ 'settings.oauth.modal.createdWarning': 'Sekret klienta jest wyświetlany tylko raz. Skopiuj go teraz — nie można go odzyskać.',
+ 'settings.oauth.toast.createError': 'Nie udało się zarejestrować klienta OAuth',
+ 'settings.oauth.toast.deleted': 'Klient OAuth usunięty',
+ 'settings.oauth.toast.deleteError': 'Nie udało się usunąć klienta OAuth',
+ 'settings.oauth.toast.revoked': 'Sesja unieważniona',
+ 'settings.oauth.toast.revokeError': 'Nie udało się unieważnić sesji',
+ 'settings.oauth.toast.rotateError': 'Nie udało się odnowić sekretu klienta',
'settings.account': 'Konto',
'settings.about': 'O aplikacji',
'settings.about.reportBug': 'Zgłoś błąd',
@@ -428,7 +471,7 @@ const pl: Record = {
'admin.recommended': 'Polecane',
'admin.weatherKey': 'Klucz OpenWeatherMap API',
'admin.weatherKeyHint': 'Do danych pogodowych. Uzyskaj go bezpłatnie na openweathermap.org',
- 'admin.validateKey': 'Test',
+ 'admin.validateKey': 'Testuj',
'admin.keyValid': 'Połączono',
'admin.keyInvalid': 'Niepoprawny',
'admin.keySaved': 'Klucze API zostały zapisane',
@@ -484,7 +527,7 @@ const pl: Record = {
'admin.addons.catalog.vacay.description': 'Osobisty planer urlopu z widokiem kalendarza',
'admin.addons.catalog.atlas.name': 'Atlas',
'admin.addons.catalog.atlas.description': 'Mapa świata z odwiedzonymi krajami i statystykami podróży',
- 'admin.addons.catalog.collab.name': 'Collab',
+ 'admin.addons.catalog.collab.name': 'Współpraca',
'admin.addons.catalog.collab.description': 'Notatki w czasie rzeczywistym, ankiety i czat do planowania podróży',
'admin.addons.catalog.memories.name': 'Zdjęcia (Immich)',
'admin.addons.catalog.memories.description': 'Udostępniaj zdjęcia z podróży za pośrednictwem swojej instancji Immich',
@@ -516,9 +559,10 @@ const pl: Record = {
'admin.weather.locationHint': 'Pogoda jest określana na podstawie pierwszego miejsca z przypisanymi współrzędnymi w danym dniu. Jeśli do dnia nie przypisano żadnego miejsca, jako punkt odniesienia używane jest dowolne miejsce z listy.',
// GitHub
- 'admin.tabs.mcpTokens': 'Tokeny MCP',
- 'admin.mcpTokens.title': 'Tokeny MCP',
- 'admin.mcpTokens.subtitle': 'Zarządzaj tokenami API dla wszystkich użytkowników',
+ 'admin.tabs.mcpTokens': 'Dostęp MCP',
+ 'admin.mcpTokens.title': 'Dostęp MCP',
+ 'admin.mcpTokens.subtitle': 'Zarządzaj sesjami OAuth i tokenami API dla wszystkich użytkowników',
+ 'admin.mcpTokens.sectionTitle': 'Tokeny API',
'admin.mcpTokens.owner': 'Właściciel',
'admin.mcpTokens.tokenName': 'Nazwa tokenu',
'admin.mcpTokens.created': 'Utworzono',
@@ -530,6 +574,17 @@ const pl: Record = {
'admin.mcpTokens.deleteSuccess': 'Token został usunięty',
'admin.mcpTokens.deleteError': 'Nie udało się usunąć tokenu',
'admin.mcpTokens.loadError': 'Nie udało się załadować tokenów',
+ 'admin.oauthSessions.sectionTitle': 'Sesje OAuth',
+ 'admin.oauthSessions.clientName': 'Klient',
+ 'admin.oauthSessions.owner': 'Właściciel',
+ 'admin.oauthSessions.scopes': 'Uprawnienia',
+ 'admin.oauthSessions.created': 'Utworzono',
+ 'admin.oauthSessions.empty': 'Brak aktywnych sesji OAuth',
+ 'admin.oauthSessions.revokeTitle': 'Unieważnij sesję',
+ 'admin.oauthSessions.revokeMessage': 'Ta sesja OAuth zostanie natychmiast unieważniona. Klient straci dostęp do MCP.',
+ 'admin.oauthSessions.revokeSuccess': 'Sesja unieważniona',
+ 'admin.oauthSessions.revokeError': 'Nie udało się unieważnić sesji',
+ 'admin.oauthSessions.loadError': 'Nie udało się załadować sesji OAuth',
'admin.tabs.github': 'GitHub',
'admin.audit.subtitle': 'Zdarzenia związane z bezpieczeństwem i administracją (kopie zapasowe, użytkownicy, MFA, ustawienia).',
@@ -597,7 +652,7 @@ const pl: Record = {
'vacay.legend': 'Legenda',
'vacay.publicHoliday': 'Święto państwowe',
'vacay.companyHoliday': 'Urlop firmowy',
- 'vacay.weekend': 'Weekend',
+ 'vacay.weekend': 'Weekendowy',
'vacay.modeVacation': 'Urlop',
'vacay.modeCompany': 'Urlop firmowy',
'vacay.entitlement': 'Wymiar',
@@ -695,7 +750,7 @@ const pl: Record = {
'atlas.lastTrip': 'Ostatnia podróż',
'atlas.nextTrip': 'Następna podróż',
'atlas.daysLeft': 'dni do wyjazdu',
- 'atlas.streak': 'Streak',
+ 'atlas.streak': 'Seria',
'atlas.years': 'lata',
'atlas.yearInRow': 'rok z rzędu',
'atlas.yearsInRow': 'lat z rzędu',
@@ -961,6 +1016,7 @@ const pl: Record = {
'budget.totalBudget': 'Całkowity budżet',
'budget.byCategory': 'Według kategorii',
'budget.editTooltip': 'Kliknij, aby edytować',
+ 'budget.linkedToReservation': 'Powiązano z rezerwacją — edytuj nazwę tam',
'budget.confirm.deleteCategory': 'Czy na pewno chcesz usunąć kategorię "{name}" z {count} wpisami?',
'budget.deleteCategory': 'Usuń kategorię',
'budget.perPerson': 'Za osobę',
@@ -1061,6 +1117,9 @@ const pl: Record = {
'packing.template': 'Szablon',
'packing.templateApplied': '{count} przedmiotów dodanych z szablonu',
'packing.templateError': 'Nie udało się zastosować szablonu',
+ 'packing.saveAsTemplate': 'Zapisz jako szablon',
+ 'packing.templateName': 'Nazwa szablonu',
+ 'packing.templateSaved': 'Lista pakowania zapisana jako szablon',
'packing.bags': 'Torby',
'packing.noBag': 'Nieprzypisane',
'packing.totalWeight': 'Waga całkowita',
@@ -1334,6 +1393,7 @@ const pl: Record = {
'memories.title': 'Zdjęcia',
'memories.notConnected': 'Immich nie jest połączony',
'memories.notConnectedHint': 'Połącz swoją instancję Immich w ustawieniach, aby przeglądać tutaj swoje zdjęcia z podróży.',
+ 'memories.notConnectedMultipleHint': 'Połącz jednego z tych dostawców zdjęć: {provider_names} w Ustawieniach, aby móc dodawać zdjęcia do tej podróży.',
'memories.noDates': 'Dodaj daty do swojej podróży, aby załadować zdjęcia.',
'memories.noPhotos': 'Nie znaleziono zdjęć',
'memories.noPhotosHint': 'Nie znaleziono zdjęć w Immich dla tego zakresu dat podróży.',
@@ -1344,16 +1404,24 @@ const pl: Record = {
'memories.reviewTitle': 'Przejrzyj swoje zdjęcia',
'memories.reviewHint': 'Kliknij w zdjęcia, aby wykluczyć je z udostępnienia.',
'memories.shareCount': 'Udostępnij {count} zdjęć',
- 'memories.immichUrl': 'URL serwera Immich',
- 'memories.immichApiKey': 'Klucz API',
+ 'memories.providerUrl': 'URL serwera',
+ 'memories.providerApiKey': 'Klucz API',
+ 'memories.providerUsername': 'Nazwa użytkownika',
+ 'memories.providerPassword': 'Hasło',
+ 'memories.providerOTP': 'Kod MFA (jeśli włączony)',
+ 'memories.skipSSLVerification': 'Pomiń weryfikację certyfikatu SSL',
+ 'memories.providerUrlHintSynology': 'Uwzględnij ścieżkę aplikacji Photos w URL, np. https://nas:5001/photo',
'memories.testConnection': 'Test',
'memories.connected': 'Połączono',
'memories.disconnected': 'Nie połączono',
'memories.connectionSuccess': 'Połączono z Immich',
'memories.connectionError': 'Nie udało się połączyć z Immich',
- 'memories.saved': 'Ustawienia Immich zostały zapisane',
+ 'memories.saved': 'Ustawienia {provider_name} zostały zapisane',
+ 'memories.providerDisconnectedBanner': 'Połączenie z {provider_name} zostało utracone. Połącz ponownie w Ustawieniach, aby wyświetlać zdjęcia.',
+ 'memories.saveError': 'Nie można zapisać ustawień {provider_name}',
'memories.addPhotos': 'Dodaj zdjęcia',
'memories.selectPhotos': 'Wybierz zdjęcia z Immich',
+ 'memories.selectPhotosMultiple': 'Wybierz zdjęcia',
'memories.selectHint': 'Dotknij zdjęć, aby je zaznaczyć.',
'memories.selected': 'wybranych',
'memories.addSelected': 'Dodaj {count} zdjęć',
@@ -1454,11 +1522,8 @@ const pl: Record = {
'admin.notifications.title': 'Powiadomienia',
'admin.notifications.hint': 'Wybierz jeden kanał powiadomień.',
'admin.notifications.none': 'Wyłączone',
- 'admin.notifications.email': 'Email (SMTP)',
+ 'admin.notifications.email': 'E-mail (SMTP)',
'admin.notifications.webhook': 'Webhook',
- 'admin.notifications.events': 'Zdarzenia powiadomień',
- 'admin.notifications.eventsHint': 'Wybierz zdarzenia wyzwalające powiadomienia.',
- 'admin.notifications.configureFirst': 'Najpierw skonfiguruj ustawienia SMTP lub webhook.',
'admin.notifications.save': 'Zapisz ustawienia powiadomień',
'admin.notifications.saved': 'Ustawienia powiadomień zapisane',
'admin.notifications.testWebhook': 'Wyślij testowy webhook',
@@ -1483,7 +1548,7 @@ const pl: Record = {
'settings.webhookUrl.hint': 'Wprowadź adres URL webhooka Discord, Slack lub własnego, aby otrzymywać powiadomienia.',
'settings.webhookUrl.save': 'Zapisz',
'settings.webhookUrl.saved': 'URL webhooka zapisany',
- 'settings.webhookUrl.test': 'Test',
+ 'settings.webhookUrl.test': 'Testuj',
'settings.webhookUrl.testSuccess': 'Testowy webhook wysłany pomyślnie',
'settings.webhookUrl.testFailed': 'Wysyłanie testowego webhooka nie powiodło się',
'settings.notificationPreferences.inapp': 'In-App',
@@ -1507,6 +1572,7 @@ const pl: Record = {
'memories.testFirst': 'Najpierw przetestuj połączenie',
'memories.linkAlbum': 'Połącz album',
'memories.selectAlbum': 'Wybierz album Immich',
+ 'memories.selectAlbumMultiple': 'Wybierz album',
'memories.noAlbums': 'Nie znaleziono albumów',
'memories.syncAlbum': 'Synchronizuj album',
'memories.unlinkAlbum': 'Odłącz album',
@@ -1592,6 +1658,8 @@ const pl: Record = {
'notifications.markUnread': 'Oznacz jako nieprzeczytane',
'notifications.delete': 'Usuń',
'notifications.system': 'System',
+ 'notifications.synologySessionCleared.title': 'Synology Photos rozłączone',
+ 'notifications.synologySessionCleared.text': 'Twój serwer lub konto zostało zmienione — przejdź do Ustawień, aby ponownie przetestować połączenie.',
'notifications.versionAvailable.title': 'Dostępna aktualizacja',
'notifications.versionAvailable.text': 'TREK {version} jest już dostępny.',
'notifications.versionAvailable.button': 'Zobacz szczegóły',
@@ -1912,6 +1980,69 @@ const pl: Record = {
'dayplan.mobile.createNew': 'Utwórz nowe miejsce',
'admin.addons.catalog.journey.name': 'Dziennik podróży',
'admin.addons.catalog.journey.description': 'Śledzenie podróży i dziennik z zameldowaniami, zdjęciami i codziennymi historiami',
+ // OAuth scope groups
+ 'oauth.scope.group.trips': 'Podróże',
+ 'oauth.scope.group.places': 'Miejsca',
+ 'oauth.scope.group.atlas': 'Atlas',
+ 'oauth.scope.group.packing': 'Pakowanie',
+ 'oauth.scope.group.todos': 'Zadania',
+ 'oauth.scope.group.budget': 'Budżet',
+ 'oauth.scope.group.reservations': 'Rezerwacje',
+ 'oauth.scope.group.collab': 'Współpraca',
+ 'oauth.scope.group.notifications': 'Powiadomienia',
+ 'oauth.scope.group.vacay': 'Urlop',
+ 'oauth.scope.group.geo': 'Geo',
+ 'oauth.scope.group.weather': 'Pogoda',
+
+ // OAuth scope labels & descriptions
+ 'oauth.scope.trips:read.label': 'Przeglądaj podróże i itineraria',
+ 'oauth.scope.trips:read.description': 'Odczytuj podróże, dni, notatki i członków',
+ 'oauth.scope.trips:write.label': 'Edytuj podróże i itineraria',
+ 'oauth.scope.trips:write.description': 'Twórz i aktualizuj podróże, dni, notatki oraz zarządzaj członkami',
+ 'oauth.scope.trips:delete.label': 'Usuń podróże',
+ 'oauth.scope.trips:delete.description': 'Trwale usuń całe podróże — ta akcja jest nieodwracalna',
+ 'oauth.scope.trips:share.label': 'Zarządzaj linkami udostępniania',
+ 'oauth.scope.trips:share.description': 'Twórz, aktualizuj i unieważniaj publiczne linki udostępniania',
+ 'oauth.scope.places:read.label': 'Przeglądaj miejsca i dane mapy',
+ 'oauth.scope.places:read.description': 'Odczytuj miejsca, przypisania dni, tagi i kategorie',
+ 'oauth.scope.places:write.label': 'Zarządzaj miejscami',
+ 'oauth.scope.places:write.description': 'Twórz, aktualizuj i usuń miejsca, przypisania i tagi',
+ 'oauth.scope.atlas:read.label': 'Przeglądaj Atlas',
+ 'oauth.scope.atlas:read.description': 'Odczytuj odwiedzone kraje, regiony i listę marzeń',
+ 'oauth.scope.atlas:write.label': 'Zarządzaj Atlasem',
+ 'oauth.scope.atlas:write.description': 'Oznaczaj kraje i regiony jako odwiedzone, zarządzaj listą marzeń',
+ 'oauth.scope.packing:read.label': 'Przeglądaj listy pakowania',
+ 'oauth.scope.packing:read.description': 'Odczytuj przedmioty, torby i przypisania kategorii',
+ 'oauth.scope.packing:write.label': 'Zarządzaj listami pakowania',
+ 'oauth.scope.packing:write.description': 'Dodawaj, aktualizuj, usuwaj, zaznaczaj i porządkuj przedmioty i torby',
+ 'oauth.scope.todos:read.label': 'Przeglądaj listy zadań',
+ 'oauth.scope.todos:read.description': 'Odczytuj zadania podróży i przypisania kategorii',
+ 'oauth.scope.todos:write.label': 'Zarządzaj listami zadań',
+ 'oauth.scope.todos:write.description': 'Twórz, aktualizuj, zaznaczaj, usuwaj i porządkuj zadania',
+ 'oauth.scope.budget:read.label': 'Przeglądaj budżet',
+ 'oauth.scope.budget:read.description': 'Odczytuj pozycje budżetu i zestawienie wydatków',
+ 'oauth.scope.budget:write.label': 'Zarządzaj budżetem',
+ 'oauth.scope.budget:write.description': 'Twórz, aktualizuj i usuń pozycje budżetu',
+ 'oauth.scope.reservations:read.label': 'Przeglądaj rezerwacje',
+ 'oauth.scope.reservations:read.description': 'Odczytuj rezerwacje i szczegóły zakwaterowania',
+ 'oauth.scope.reservations:write.label': 'Zarządzaj rezerwacjami',
+ 'oauth.scope.reservations:write.description': 'Twórz, aktualizuj, usuwaj i porządkuj rezerwacje',
+ 'oauth.scope.collab:read.label': 'Przeglądaj współpracę',
+ 'oauth.scope.collab:read.description': 'Odczytuj notatki, ankiety i wiadomości',
+ 'oauth.scope.collab:write.label': 'Zarządzaj współpracą',
+ 'oauth.scope.collab:write.description': 'Twórz, aktualizuj i usuń notatki, ankiety i wiadomości',
+ 'oauth.scope.notifications:read.label': 'Przeglądaj powiadomienia',
+ 'oauth.scope.notifications:read.description': 'Odczytuj powiadomienia i liczby nieprzeczytanych',
+ 'oauth.scope.notifications:write.label': 'Zarządzaj powiadomieniami',
+ 'oauth.scope.notifications:write.description': 'Oznaczaj powiadomienia jako przeczytane i odpowiadaj na nie',
+ 'oauth.scope.vacay:read.label': 'Przeglądaj plany urlopowe',
+ 'oauth.scope.vacay:read.description': 'Odczytuj dane planowania urlopu, wpisy i statystyki',
+ 'oauth.scope.vacay:write.label': 'Zarządzaj planami urlopowymi',
+ 'oauth.scope.vacay:write.description': 'Twórz i zarządzaj wpisami urlopowymi, świętami i planami zespołu',
+ 'oauth.scope.geo:read.label': 'Mapy i geokodowanie',
+ 'oauth.scope.geo:read.description': 'Wyszukuj miejsca, rozwiązuj adresy URL map i odwrotnie geokoduj współrzędne',
+ 'oauth.scope.weather:read.label': 'Prognozy pogody',
+ 'oauth.scope.weather:read.description': 'Pobieraj prognozy pogody dla miejsc i dat podróży',
}
export default pl
diff --git a/client/src/i18n/translations/ru.ts b/client/src/i18n/translations/ru.ts
index 2ce65842..1db338aa 100644
--- a/client/src/i18n/translations/ru.ts
+++ b/client/src/i18n/translations/ru.ts
@@ -179,9 +179,6 @@ const ru: Record = {
'admin.notifications.none': 'Отключено',
'admin.notifications.email': 'Эл. почта (SMTP)',
'admin.notifications.webhook': 'Webhook',
- 'admin.notifications.events': 'События уведомлений',
- 'admin.notifications.eventsHint': 'Выберите, какие события вызывают уведомления для всех пользователей.',
- 'admin.notifications.configureFirst': 'Сначала настройте SMTP или webhook ниже, затем включите события.',
'admin.notifications.save': 'Сохранить настройки уведомлений',
'admin.notifications.saved': 'Настройки уведомлений сохранены',
'admin.notifications.testWebhook': 'Отправить тестовый вебхук',
@@ -228,6 +225,7 @@ const ru: Record = {
'settings.mcp.endpoint': 'MCP-эндпоинт',
'settings.mcp.clientConfig': 'Конфигурация клиента',
'settings.mcp.clientConfigHint': 'Замените на API-токен из списка ниже. Путь к npx может потребовать настройки для вашей системы (например, C:\\PROGRA~1\\nodejs\\npx.cmd в Windows).',
+ 'settings.mcp.clientConfigHintOAuth': 'Замените и на учётные данные из созданного выше клиента OAuth 2.1. При первом подключении mcp-remote откроет браузер для завершения авторизации. Путь к npx может потребовать настройки для вашей системы (например, C:\\PROGRA~1\\nodejs\\npx.cmd в Windows).',
'settings.mcp.copy': 'Копировать',
'settings.mcp.copied': 'Скопировано!',
'settings.mcp.apiTokens': 'API-токены',
@@ -249,6 +247,48 @@ const ru: Record = {
'settings.mcp.toast.createError': 'Не удалось создать токен',
'settings.mcp.toast.deleted': 'Токен удалён',
'settings.mcp.toast.deleteError': 'Не удалось удалить токен',
+ 'settings.mcp.apiTokensDeprecated': 'API-токены устарели и будут удалены в будущей версии. Пожалуйста, используйте клиенты OAuth 2.1.',
+ 'settings.oauth.clients': 'Клиенты OAuth 2.1',
+ 'settings.oauth.clientsHint': 'Зарегистрируйте клиенты OAuth 2.1, чтобы сторонние MCP-приложения (Claude Web, Cursor и др.) могли подключаться без статических токенов.',
+ 'settings.oauth.createClient': 'Новый клиент',
+ 'settings.oauth.noClients': 'Нет зарегистрированных клиентов OAuth.',
+ 'settings.oauth.clientId': 'ID клиента',
+ 'settings.oauth.clientSecret': 'Секрет клиента',
+ 'settings.oauth.deleteClient': 'Удалить клиента',
+ 'settings.oauth.deleteClientMessage': 'Этот клиент и все активные сессии будут удалены навсегда. Любое приложение, использующее его, немедленно потеряет доступ.',
+ 'settings.oauth.rotateSecret': 'Обновить секрет',
+ 'settings.oauth.rotateSecretMessage': 'Будет сгенерирован новый секрет клиента, а все существующие сессии будут немедленно аннулированы. Обновите приложение перед закрытием этого диалога.',
+ 'settings.oauth.rotateSecretConfirm': 'Обновить',
+ 'settings.oauth.rotateSecretConfirming': 'Обновление…',
+ 'settings.oauth.rotateSecretDoneTitle': 'Новый секрет сгенерирован',
+ 'settings.oauth.rotateSecretDoneWarning': 'Этот секрет отображается только один раз. Скопируйте его сейчас и обновите приложение — все предыдущие сессии были аннулированы.',
+ 'settings.oauth.activeSessions': 'Активные сессии OAuth',
+ 'settings.oauth.sessionScopes': 'Области доступа',
+ 'settings.oauth.sessionExpires': 'Истекает',
+ 'settings.oauth.revoke': 'Отозвать',
+ 'settings.oauth.revokeSession': 'Отозвать сессию',
+ 'settings.oauth.revokeSessionMessage': 'Это немедленно отзовёт доступ для данной сессии OAuth.',
+ 'settings.oauth.modal.createTitle': 'Зарегистрировать клиент OAuth',
+ 'settings.oauth.modal.presets': 'Быстрые настройки',
+ 'settings.oauth.modal.clientName': 'Название приложения',
+ 'settings.oauth.modal.clientNamePlaceholder': 'напр. Claude Web, Моё MCP-приложение',
+ 'settings.oauth.modal.redirectUris': 'URI перенаправления',
+ 'settings.oauth.modal.redirectUrisPlaceholder': 'https://your-app.com/callback\nhttps://your-app.com/auth',
+ 'settings.oauth.modal.redirectUrisHint': 'Один URI на строку. Требуется HTTPS (localhost исключён). Требуется точное совпадение.',
+ 'settings.oauth.modal.scopes': 'Разрешённые области доступа',
+ 'settings.oauth.modal.scopesHint': 'list_trips и get_trip_summary всегда доступны — область не требуется. Они помогают ИИ находить нужные ID поездок.',
+ 'settings.oauth.modal.selectAll': 'Выбрать все',
+ 'settings.oauth.modal.deselectAll': 'Снять выбор',
+ 'settings.oauth.modal.creating': 'Регистрация…',
+ 'settings.oauth.modal.create': 'Зарегистрировать клиента',
+ 'settings.oauth.modal.createdTitle': 'Клиент зарегистрирован',
+ 'settings.oauth.modal.createdWarning': 'Секрет клиента отображается только один раз. Скопируйте его сейчас — его нельзя будет восстановить.',
+ 'settings.oauth.toast.createError': 'Не удалось зарегистрировать клиент OAuth',
+ 'settings.oauth.toast.deleted': 'Клиент OAuth удалён',
+ 'settings.oauth.toast.deleteError': 'Не удалось удалить клиент OAuth',
+ 'settings.oauth.toast.revoked': 'Сессия отозвана',
+ 'settings.oauth.toast.revokeError': 'Не удалось отозвать сессию',
+ 'settings.oauth.toast.rotateError': 'Не удалось обновить секрет клиента',
'settings.account': 'Аккаунт',
'settings.about': 'О приложении',
'settings.about.reportBug': 'Сообщить об ошибке',
@@ -547,9 +587,10 @@ const ru: Record = {
'admin.weather.locationHint': 'Погода основана на первом месте с координатами в каждом дне. Если ни одно место не назначено на день, в качестве ориентира используется любое место из списка.',
// MCP Tokens
- 'admin.tabs.mcpTokens': 'MCP-токены',
- 'admin.mcpTokens.title': 'MCP-токены',
- 'admin.mcpTokens.subtitle': 'Управление API-токенами всех пользователей',
+ 'admin.tabs.mcpTokens': 'MCP-доступ',
+ 'admin.mcpTokens.title': 'MCP-доступ',
+ 'admin.mcpTokens.subtitle': 'Управление OAuth-сессиями и API-токенами всех пользователей',
+ 'admin.mcpTokens.sectionTitle': 'API-токены',
'admin.mcpTokens.owner': 'Владелец',
'admin.mcpTokens.tokenName': 'Название токена',
'admin.mcpTokens.created': 'Создан',
@@ -561,6 +602,17 @@ const ru: Record = {
'admin.mcpTokens.deleteSuccess': 'Токен удалён',
'admin.mcpTokens.deleteError': 'Не удалось удалить токен',
'admin.mcpTokens.loadError': 'Не удалось загрузить токены',
+ 'admin.oauthSessions.sectionTitle': 'OAuth-сессии',
+ 'admin.oauthSessions.clientName': 'Клиент',
+ 'admin.oauthSessions.owner': 'Владелец',
+ 'admin.oauthSessions.scopes': 'Права доступа',
+ 'admin.oauthSessions.created': 'Создано',
+ 'admin.oauthSessions.empty': 'Нет активных OAuth-сессий',
+ 'admin.oauthSessions.revokeTitle': 'Отозвать сессию',
+ 'admin.oauthSessions.revokeMessage': 'Эта OAuth-сессия будет немедленно отозвана. Клиент потеряет доступ к MCP.',
+ 'admin.oauthSessions.revokeSuccess': 'Сессия отозвана',
+ 'admin.oauthSessions.revokeError': 'Не удалось отозвать сессию',
+ 'admin.oauthSessions.loadError': 'Не удалось загрузить OAuth-сессии',
// GitHub
'admin.tabs.github': 'GitHub',
@@ -1005,6 +1057,7 @@ const ru: Record = {
'budget.totalBudget': 'Общий бюджет',
'budget.byCategory': 'По категориям',
'budget.editTooltip': 'Нажмите для редактирования',
+ 'budget.linkedToReservation': 'Связано с бронированием — редактируйте название там',
'budget.confirm.deleteCategory': 'Вы уверены, что хотите удалить категорию «{name}» с {count} записями?',
'budget.deleteCategory': 'Удалить категорию',
'budget.perPerson': 'На человека',
@@ -1103,6 +1156,9 @@ const ru: Record = {
'packing.template': 'Шаблон',
'packing.templateApplied': '{count} вещей добавлено из шаблона',
'packing.templateError': 'Ошибка применения шаблона',
+ 'packing.saveAsTemplate': 'Сохранить как шаблон',
+ 'packing.templateName': 'Название шаблона',
+ 'packing.templateSaved': 'Список вещей сохранён как шаблон',
'packing.assignUser': 'Назначить пользователя',
'packing.noMembers': 'Нет участников',
'packing.bags': 'Багаж',
@@ -1378,6 +1434,7 @@ const ru: Record = {
'memories.title': 'Фото',
'memories.notConnected': 'Immich не подключён',
'memories.notConnectedHint': 'Подключите Immich в настройках, чтобы видеть фотографии из поездок.',
+ 'memories.notConnectedMultipleHint': 'Подключите одного из этих фотопровайдеров: {provider_names} в Настройках, чтобы добавлять фотографии к этому путешествию.',
'memories.noDates': 'Добавьте даты поездки для загрузки фотографий.',
'memories.noPhotos': 'Фотографии не найдены',
'memories.noPhotosHint': 'В Immich нет фотографий за период этой поездки.',
@@ -1388,26 +1445,35 @@ const ru: Record = {
'memories.reviewTitle': 'Проверьте ваши фото',
'memories.reviewHint': 'Нажмите на фото, чтобы исключить его из общего доступа.',
'memories.shareCount': 'Поделиться ({count} фото)',
- 'memories.immichUrl': 'URL сервера Immich',
- 'memories.immichApiKey': 'API-ключ',
+ 'memories.providerUrl': 'URL сервера',
+ 'memories.providerApiKey': 'API-ключ',
+ 'memories.providerUsername': 'Имя пользователя',
+ 'memories.providerPassword': 'Пароль',
+ 'memories.providerOTP': 'Код MFA (если включён)',
+ 'memories.skipSSLVerification': 'Пропустить проверку SSL-сертификата',
+ 'memories.providerUrlHintSynology': 'Включите путь приложения Photos в URL, например https://nas:5001/photo',
'memories.testConnection': 'Проверить подключение',
'memories.testFirst': 'Сначала проверьте подключение',
'memories.connected': 'Подключено',
'memories.disconnected': 'Не подключено',
'memories.connectionSuccess': 'Подключение к Immich установлено',
'memories.connectionError': 'Не удалось подключиться к Immich',
- 'memories.saved': 'Настройки Immich сохранены',
+ 'memories.saved': 'Настройки {provider_name} сохранены',
+ 'memories.providerDisconnectedBanner': 'Соединение с {provider_name} потеряно. Переподключитесь в Настройках для просмотра фотографий.',
+ 'memories.saveError': 'Не удалось сохранить настройки {provider_name}',
'memories.oldest': 'Сначала старые',
'memories.newest': 'Сначала новые',
'memories.allLocations': 'Все места',
'memories.addPhotos': 'Добавить фото',
'memories.linkAlbum': 'Привязать альбом',
'memories.selectAlbum': 'Выбрать альбом Immich',
+ 'memories.selectAlbumMultiple': 'Выбрать альбом',
'memories.noAlbums': 'Альбомы не найдены',
'memories.syncAlbum': 'Синхронизировать',
'memories.unlinkAlbum': 'Отвязать',
'memories.photos': 'фото',
'memories.selectPhotos': 'Выбрать фото из Immich',
+ 'memories.selectPhotosMultiple': 'Выбрать фотографии',
'memories.selectHint': 'Нажмите на фото, чтобы выбрать их.',
'memories.selected': 'выбрано',
'memories.addSelected': 'Добавить {count} фото',
@@ -1564,6 +1630,8 @@ const ru: Record = {
'notifications.markUnread': 'Отметить как непрочитанное',
'notifications.delete': 'Удалить',
'notifications.system': 'Система',
+ 'notifications.synologySessionCleared.title': 'Synology Photos отключено',
+ 'notifications.synologySessionCleared.text': 'Ваш сервер или аккаунт изменился — перейдите в Настройки, чтобы проверить соединение снова.',
'memories.error.loadAlbums': 'Не удалось загрузить альбомы',
'memories.error.linkAlbum': 'Не удалось привязать альбом',
'memories.error.unlinkAlbum': 'Не удалось отвязать альбом',
@@ -1919,6 +1987,69 @@ const ru: Record = {
'dayplan.mobile.createNew': 'Создать новое место',
'admin.addons.catalog.journey.name': 'Путешествие',
'admin.addons.catalog.journey.description': 'Отслеживание поездок и дневник путешествий с отметками, фото и ежедневными историями',
+ // OAuth scope groups
+ 'oauth.scope.group.trips': 'Поездки',
+ 'oauth.scope.group.places': 'Места',
+ 'oauth.scope.group.atlas': 'Atlas',
+ 'oauth.scope.group.packing': 'Вещи',
+ 'oauth.scope.group.todos': 'Задачи',
+ 'oauth.scope.group.budget': 'Бюджет',
+ 'oauth.scope.group.reservations': 'Бронирования',
+ 'oauth.scope.group.collab': 'Сотрудничество',
+ 'oauth.scope.group.notifications': 'Уведомления',
+ 'oauth.scope.group.vacay': 'Отпуск',
+ 'oauth.scope.group.geo': 'Geo',
+ 'oauth.scope.group.weather': 'Погода',
+
+ // OAuth scope labels & descriptions
+ 'oauth.scope.trips:read.label': 'Просмотр поездок и маршрутов',
+ 'oauth.scope.trips:read.description': 'Чтение поездок, дней, заметок и участников',
+ 'oauth.scope.trips:write.label': 'Редактирование поездок и маршрутов',
+ 'oauth.scope.trips:write.description': 'Создание и обновление поездок, дней, заметок и управление участниками',
+ 'oauth.scope.trips:delete.label': 'Удаление поездок',
+ 'oauth.scope.trips:delete.description': 'Безвозвратное удаление поездок — это действие необратимо',
+ 'oauth.scope.trips:share.label': 'Управление ссылками на совместный доступ',
+ 'oauth.scope.trips:share.description': 'Создание, обновление и отзыв публичных ссылок на поездки',
+ 'oauth.scope.places:read.label': 'Просмотр мест и данных карты',
+ 'oauth.scope.places:read.description': 'Чтение мест, назначений по дням, тегов и категорий',
+ 'oauth.scope.places:write.label': 'Управление местами',
+ 'oauth.scope.places:write.description': 'Создание, обновление и удаление мест, назначений и тегов',
+ 'oauth.scope.atlas:read.label': 'Просмотр Atlas',
+ 'oauth.scope.atlas:read.description': 'Чтение посещённых стран, регионов и списка желаний',
+ 'oauth.scope.atlas:write.label': 'Управление Atlas',
+ 'oauth.scope.atlas:write.description': 'Отмечать посещённые страны и регионы, управлять списком желаний',
+ 'oauth.scope.packing:read.label': 'Просмотр списков вещей',
+ 'oauth.scope.packing:read.description': 'Чтение вещей, сумок и назначений категорий',
+ 'oauth.scope.packing:write.label': 'Управление списками вещей',
+ 'oauth.scope.packing:write.description': 'Добавление, обновление, удаление, отметка и переупорядочивание вещей и сумок',
+ 'oauth.scope.todos:read.label': 'Просмотр списков задач',
+ 'oauth.scope.todos:read.description': 'Чтение задач поездки и назначений категорий',
+ 'oauth.scope.todos:write.label': 'Управление списками задач',
+ 'oauth.scope.todos:write.description': 'Создание, обновление, отметка, удаление и переупорядочивание задач',
+ 'oauth.scope.budget:read.label': 'Просмотр бюджета',
+ 'oauth.scope.budget:read.description': 'Чтение статей бюджета и разбивки расходов',
+ 'oauth.scope.budget:write.label': 'Управление бюджетом',
+ 'oauth.scope.budget:write.description': 'Создание, обновление и удаление статей бюджета',
+ 'oauth.scope.reservations:read.label': 'Просмотр бронирований',
+ 'oauth.scope.reservations:read.description': 'Чтение бронирований и сведений о проживании',
+ 'oauth.scope.reservations:write.label': 'Управление бронированиями',
+ 'oauth.scope.reservations:write.description': 'Создание, обновление, удаление и переупорядочивание бронирований',
+ 'oauth.scope.collab:read.label': 'Просмотр совместной работы',
+ 'oauth.scope.collab:read.description': 'Чтение совместных заметок, опросов и сообщений',
+ 'oauth.scope.collab:write.label': 'Управление совместной работой',
+ 'oauth.scope.collab:write.description': 'Создание, обновление и удаление заметок, опросов и сообщений',
+ 'oauth.scope.notifications:read.label': 'Просмотр уведомлений',
+ 'oauth.scope.notifications:read.description': 'Чтение уведомлений в приложении и количества непрочитанных',
+ 'oauth.scope.notifications:write.label': 'Управление уведомлениями',
+ 'oauth.scope.notifications:write.description': 'Отмечать уведомления как прочитанные и отвечать на них',
+ 'oauth.scope.vacay:read.label': 'Просмотр планов отпуска',
+ 'oauth.scope.vacay:read.description': 'Чтение данных планирования отпуска, записей и статистики',
+ 'oauth.scope.vacay:write.label': 'Управление планами отпуска',
+ 'oauth.scope.vacay:write.description': 'Создание и управление записями отпуска, праздниками и командными планами',
+ 'oauth.scope.geo:read.label': 'Карты и геокодирование',
+ 'oauth.scope.geo:read.description': 'Поиск мест, разрешение URL карт и обратное геокодирование координат',
+ 'oauth.scope.weather:read.label': 'Прогнозы погоды',
+ 'oauth.scope.weather:read.description': 'Получение прогнозов погоды для мест и дат поездки',
}
export default ru
diff --git a/client/src/i18n/translations/zh.ts b/client/src/i18n/translations/zh.ts
index 3926843f..4a007923 100644
--- a/client/src/i18n/translations/zh.ts
+++ b/client/src/i18n/translations/zh.ts
@@ -179,9 +179,6 @@ const zh: Record = {
'admin.notifications.none': '已禁用',
'admin.notifications.email': '电子邮件 (SMTP)',
'admin.notifications.webhook': 'Webhook',
- 'admin.notifications.events': '通知事件',
- 'admin.notifications.eventsHint': '选择哪些事件为所有用户触发通知。',
- 'admin.notifications.configureFirst': '请先在下方配置 SMTP 或 Webhook,然后启用事件。',
'admin.notifications.save': '保存通知设置',
'admin.notifications.saved': '通知设置已保存',
'admin.notifications.testWebhook': '发送测试 Webhook',
@@ -228,6 +225,7 @@ const zh: Record = {
'settings.mcp.endpoint': 'MCP 端点',
'settings.mcp.clientConfig': '客户端配置',
'settings.mcp.clientConfigHint': '将 替换为下方列表中的 API 令牌。npx 的路径可能需要根据您的系统进行调整(例如 Windows 上为 C:\\PROGRA~1\\nodejs\\npx.cmd)。',
+ 'settings.mcp.clientConfigHintOAuth': '将 和 替换为上方创建的 OAuth 2.1 客户端凭据。首次连接时,mcp-remote 将打开浏览器完成授权。npx 的路径可能需要根据您的系统进行调整(例如 Windows 上为 C:\\PROGRA~1\\nodejs\\npx.cmd)。',
'settings.mcp.copy': '复制',
'settings.mcp.copied': '已复制!',
'settings.mcp.apiTokens': 'API 令牌',
@@ -249,6 +247,48 @@ const zh: Record = {
'settings.mcp.toast.createError': '创建令牌失败',
'settings.mcp.toast.deleted': '令牌已删除',
'settings.mcp.toast.deleteError': '删除令牌失败',
+ 'settings.mcp.apiTokensDeprecated': 'API 令牌已弃用,将在未来版本中移除。请改用 OAuth 2.1 客户端。',
+ 'settings.oauth.clients': 'OAuth 2.1 客户端',
+ 'settings.oauth.clientsHint': '注册 OAuth 2.1 客户端,让第三方 MCP 应用程序(Claude Web、Cursor 等)无需静态令牌即可连接。',
+ 'settings.oauth.createClient': '新建客户端',
+ 'settings.oauth.noClients': '没有已注册的 OAuth 客户端。',
+ 'settings.oauth.clientId': '客户端 ID',
+ 'settings.oauth.clientSecret': '客户端密钥',
+ 'settings.oauth.deleteClient': '删除客户端',
+ 'settings.oauth.deleteClientMessage': '此客户端及所有活跃会话将被永久删除。使用此客户端的任何应用程序将立即失去访问权限。',
+ 'settings.oauth.rotateSecret': '轮换密钥',
+ 'settings.oauth.rotateSecretMessage': '将生成新的客户端密钥,所有现有会话将立即失效。在关闭此对话框之前,请更新您的应用程序。',
+ 'settings.oauth.rotateSecretConfirm': '轮换',
+ 'settings.oauth.rotateSecretConfirming': '轮换中…',
+ 'settings.oauth.rotateSecretDoneTitle': '已生成新密钥',
+ 'settings.oauth.rotateSecretDoneWarning': '此密钥仅显示一次。请立即复制并更新您的应用程序——所有之前的会话已失效。',
+ 'settings.oauth.activeSessions': '活跃的 OAuth 会话',
+ 'settings.oauth.sessionScopes': '权限范围',
+ 'settings.oauth.sessionExpires': '过期时间',
+ 'settings.oauth.revoke': '撤销',
+ 'settings.oauth.revokeSession': '撤销会话',
+ 'settings.oauth.revokeSessionMessage': '这将立即撤销此 OAuth 会话的访问权限。',
+ 'settings.oauth.modal.createTitle': '注册 OAuth 客户端',
+ 'settings.oauth.modal.presets': '快速预设',
+ 'settings.oauth.modal.clientName': '应用程序名称',
+ 'settings.oauth.modal.clientNamePlaceholder': '例如 Claude Web、我的 MCP 应用',
+ 'settings.oauth.modal.redirectUris': '重定向 URI',
+ 'settings.oauth.modal.redirectUrisPlaceholder': 'https://your-app.com/callback\nhttps://your-app.com/auth',
+ 'settings.oauth.modal.redirectUrisHint': '每行一个 URI。需要 HTTPS(localhost 除外)。要求精确匹配。',
+ 'settings.oauth.modal.scopes': '允许的权限范围',
+ 'settings.oauth.modal.scopesHint': 'list_trips 和 get_trip_summary 始终可用——无需权限范围。它们帮助 AI 发现所需的行程 ID。',
+ 'settings.oauth.modal.selectAll': '全选',
+ 'settings.oauth.modal.deselectAll': '取消全选',
+ 'settings.oauth.modal.creating': '注册中…',
+ 'settings.oauth.modal.create': '注册客户端',
+ 'settings.oauth.modal.createdTitle': '客户端已注册',
+ 'settings.oauth.modal.createdWarning': '客户端密钥仅显示一次。请立即复制——无法恢复。',
+ 'settings.oauth.toast.createError': '注册 OAuth 客户端失败',
+ 'settings.oauth.toast.deleted': 'OAuth 客户端已删除',
+ 'settings.oauth.toast.deleteError': '删除 OAuth 客户端失败',
+ 'settings.oauth.toast.revoked': '会话已撤销',
+ 'settings.oauth.toast.revokeError': '撤销会话失败',
+ 'settings.oauth.toast.rotateError': '轮换客户端密钥失败',
'settings.account': '账户',
'settings.about': '关于',
'settings.about.reportBug': '报告错误',
@@ -547,9 +587,10 @@ const zh: Record = {
'admin.weather.locationHint': '天气基于每天中第一个有坐标的地点。如果当天没有分配地点,则使用地点列表中的任意地点作为参考。',
// MCP Tokens
- 'admin.tabs.mcpTokens': 'MCP 令牌',
- 'admin.mcpTokens.title': 'MCP 令牌',
- 'admin.mcpTokens.subtitle': '管理所有用户的 API 令牌',
+ 'admin.tabs.mcpTokens': 'MCP 访问',
+ 'admin.mcpTokens.title': 'MCP 访问',
+ 'admin.mcpTokens.subtitle': '管理所有用户的 OAuth 会话和 API 令牌',
+ 'admin.mcpTokens.sectionTitle': 'API 令牌',
'admin.mcpTokens.owner': '所有者',
'admin.mcpTokens.tokenName': '令牌名称',
'admin.mcpTokens.created': '创建时间',
@@ -561,6 +602,17 @@ const zh: Record = {
'admin.mcpTokens.deleteSuccess': '令牌已删除',
'admin.mcpTokens.deleteError': '删除令牌失败',
'admin.mcpTokens.loadError': '加载令牌失败',
+ 'admin.oauthSessions.sectionTitle': 'OAuth 会话',
+ 'admin.oauthSessions.clientName': '客户端',
+ 'admin.oauthSessions.owner': '所有者',
+ 'admin.oauthSessions.scopes': '权限范围',
+ 'admin.oauthSessions.created': '创建时间',
+ 'admin.oauthSessions.empty': '暂无活跃的 OAuth 会话',
+ 'admin.oauthSessions.revokeTitle': '撤销会话',
+ 'admin.oauthSessions.revokeMessage': '此 OAuth 会话将立即被撤销。客户端将失去 MCP 访问权限。',
+ 'admin.oauthSessions.revokeSuccess': '会话已撤销',
+ 'admin.oauthSessions.revokeError': '撤销会话失败',
+ 'admin.oauthSessions.loadError': '加载 OAuth 会话失败',
// GitHub
'admin.tabs.github': 'GitHub',
@@ -1005,6 +1057,7 @@ const zh: Record = {
'budget.totalBudget': '总预算',
'budget.byCategory': '按分类',
'budget.editTooltip': '点击编辑',
+ 'budget.linkedToReservation': '已关联到预订——请在那里编辑名称',
'budget.confirm.deleteCategory': '确定删除分类「{name}」及其 {count} 个条目?',
'budget.deleteCategory': '删除分类',
'budget.perPerson': '人均',
@@ -1103,6 +1156,9 @@ const zh: Record = {
'packing.template': '模板',
'packing.templateApplied': '已从模板添加 {count} 个物品',
'packing.templateError': '应用模板失败',
+ 'packing.saveAsTemplate': '保存为模板',
+ 'packing.templateName': '模板名称',
+ 'packing.templateSaved': '行李清单已保存为模板',
'packing.assignUser': '分配用户',
'packing.noMembers': '无成员',
'packing.bags': '行李',
@@ -1378,6 +1434,7 @@ const zh: Record = {
'memories.title': '照片',
'memories.notConnected': 'Immich 未连接',
'memories.notConnectedHint': '在设置中连接您的 Immich 实例以在此查看旅行照片。',
+ 'memories.notConnectedMultipleHint': '请在设置中连接以下任一照片提供商:{provider_names},以便向此行程添加照片。',
'memories.noDates': '为旅行添加日期以加载照片。',
'memories.noPhotos': '未找到照片',
'memories.noPhotosHint': 'Immich 中未找到此旅行日期范围内的照片。',
@@ -1388,26 +1445,35 @@ const zh: Record = {
'memories.reviewTitle': '审查您的照片',
'memories.reviewHint': '点击照片以将其从分享中排除。',
'memories.shareCount': '分享 {count} 张照片',
- 'memories.immichUrl': 'Immich 服务器地址',
- 'memories.immichApiKey': 'API 密钥',
+ 'memories.providerUrl': '服务器 URL',
+ 'memories.providerApiKey': 'API 密钥',
+ 'memories.providerUsername': '用户名',
+ 'memories.providerPassword': '密码',
+ 'memories.providerOTP': 'MFA 验证码(如已启用)',
+ 'memories.skipSSLVerification': '跳过 SSL 证书验证',
+ 'memories.providerUrlHintSynology': '在 URL 中包含照片应用路径,例如 https://nas:5001/photo',
'memories.testConnection': '测试连接',
'memories.testFirst': '请先测试连接',
'memories.connected': '已连接',
'memories.disconnected': '未连接',
'memories.connectionSuccess': '已连接到 Immich',
'memories.connectionError': '无法连接到 Immich',
- 'memories.saved': 'Immich 设置已保存',
+ 'memories.saved': '{provider_name} 设置已保存',
+ 'memories.providerDisconnectedBanner': '您与 {provider_name} 的连接已断开。请在设置中重新连接以查看照片。',
+ 'memories.saveError': '无法保存 {provider_name} 设置',
'memories.oldest': '最早优先',
'memories.newest': '最新优先',
'memories.allLocations': '所有地点',
'memories.addPhotos': '添加照片',
'memories.linkAlbum': '关联相册',
'memories.selectAlbum': '选择 Immich 相册',
+ 'memories.selectAlbumMultiple': '选择相册',
'memories.noAlbums': '未找到相册',
'memories.syncAlbum': '同步相册',
'memories.unlinkAlbum': '取消关联',
'memories.photos': '张照片',
'memories.selectPhotos': '从 Immich 选择照片',
+ 'memories.selectPhotosMultiple': '选择照片',
'memories.selectHint': '点击照片以选择。',
'memories.selected': '已选择',
'memories.addSelected': '添加 {count} 张照片',
@@ -1564,6 +1630,8 @@ const zh: Record = {
'notifications.markUnread': '标为未读',
'notifications.delete': '删除',
'notifications.system': '系统',
+ 'notifications.synologySessionCleared.title': 'Synology Photos 已断开连接',
+ 'notifications.synologySessionCleared.text': '您的服务器或账户已更改 — 请前往设置重新测试您的连接。',
'memories.error.loadAlbums': '加载相册失败',
'memories.error.linkAlbum': '关联相册失败',
'memories.error.unlinkAlbum': '取消关联相册失败',
@@ -1919,6 +1987,69 @@ const zh: Record = {
'dayplan.mobile.createNew': '创建新地点',
'admin.addons.catalog.journey.name': '旅程',
'admin.addons.catalog.journey.description': '旅行追踪与旅行日志,包含签到、照片和每日故事',
+ // OAuth scope groups
+ 'oauth.scope.group.trips': '行程',
+ 'oauth.scope.group.places': '地点',
+ 'oauth.scope.group.atlas': 'Atlas',
+ 'oauth.scope.group.packing': '行李',
+ 'oauth.scope.group.todos': '待办事项',
+ 'oauth.scope.group.budget': '预算',
+ 'oauth.scope.group.reservations': '预订',
+ 'oauth.scope.group.collab': '协作',
+ 'oauth.scope.group.notifications': '通知',
+ 'oauth.scope.group.vacay': '假期',
+ 'oauth.scope.group.geo': 'Geo',
+ 'oauth.scope.group.weather': '天气',
+
+ // OAuth scope labels & descriptions
+ 'oauth.scope.trips:read.label': '查看行程和行程计划',
+ 'oauth.scope.trips:read.description': '读取行程、天数、每日笔记和成员',
+ 'oauth.scope.trips:write.label': '编辑行程和行程计划',
+ 'oauth.scope.trips:write.description': '创建和更新行程、天数、笔记并管理成员',
+ 'oauth.scope.trips:delete.label': '删除行程',
+ 'oauth.scope.trips:delete.description': '永久删除整个行程——此操作不可撤销',
+ 'oauth.scope.trips:share.label': '管理分享链接',
+ 'oauth.scope.trips:share.description': '创建、更新和撤销行程的公开分享链接',
+ 'oauth.scope.places:read.label': '查看地点和地图数据',
+ 'oauth.scope.places:read.description': '读取地点、每日分配、标签和分类',
+ 'oauth.scope.places:write.label': '管理地点',
+ 'oauth.scope.places:write.description': '创建、更新和删除地点、分配和标签',
+ 'oauth.scope.atlas:read.label': '查看 Atlas',
+ 'oauth.scope.atlas:read.description': '读取已访问国家、地区和心愿清单',
+ 'oauth.scope.atlas:write.label': '管理 Atlas',
+ 'oauth.scope.atlas:write.description': '标记已访问国家和地区,管理心愿清单',
+ 'oauth.scope.packing:read.label': '查看行李清单',
+ 'oauth.scope.packing:read.description': '读取行李物品、包袋和分类负责人',
+ 'oauth.scope.packing:write.label': '管理行李清单',
+ 'oauth.scope.packing:write.description': '添加、更新、删除、勾选和重新排列行李物品和包袋',
+ 'oauth.scope.todos:read.label': '查看待办清单',
+ 'oauth.scope.todos:read.description': '读取行程待办事项和分类负责人',
+ 'oauth.scope.todos:write.label': '管理待办清单',
+ 'oauth.scope.todos:write.description': '创建、更新、勾选、删除和重新排列待办事项',
+ 'oauth.scope.budget:read.label': '查看预算',
+ 'oauth.scope.budget:read.description': '读取预算条目和费用明细',
+ 'oauth.scope.budget:write.label': '管理预算',
+ 'oauth.scope.budget:write.description': '创建、更新和删除预算条目',
+ 'oauth.scope.reservations:read.label': '查看预订',
+ 'oauth.scope.reservations:read.description': '读取预订和住宿详情',
+ 'oauth.scope.reservations:write.label': '管理预订',
+ 'oauth.scope.reservations:write.description': '创建、更新、删除和重新排列预订',
+ 'oauth.scope.collab:read.label': '查看协作',
+ 'oauth.scope.collab:read.description': '读取协作笔记、投票和消息',
+ 'oauth.scope.collab:write.label': '管理协作',
+ 'oauth.scope.collab:write.description': '创建、更新和删除协作笔记、投票和消息',
+ 'oauth.scope.notifications:read.label': '查看通知',
+ 'oauth.scope.notifications:read.description': '读取应用内通知和未读数量',
+ 'oauth.scope.notifications:write.label': '管理通知',
+ 'oauth.scope.notifications:write.description': '将通知标记为已读并回复',
+ 'oauth.scope.vacay:read.label': '查看假期计划',
+ 'oauth.scope.vacay:read.description': '读取假期计划数据、条目和统计',
+ 'oauth.scope.vacay:write.label': '管理假期计划',
+ 'oauth.scope.vacay:write.description': '创建和管理假期条目、节假日和团队计划',
+ 'oauth.scope.geo:read.label': '地图和地理编码',
+ 'oauth.scope.geo:read.description': '搜索位置、解析地图 URL 和反向地理编码坐标',
+ 'oauth.scope.weather:read.label': '天气预报',
+ 'oauth.scope.weather:read.description': '获取行程地点和日期的天气预报',
}
export default zh
diff --git a/client/src/i18n/translations/zhTw.ts b/client/src/i18n/translations/zhTw.ts
index 9fd3a5ee..58a6138d 100644
--- a/client/src/i18n/translations/zhTw.ts
+++ b/client/src/i18n/translations/zhTw.ts
@@ -113,6 +113,8 @@ const zhTw: Record = {
'dashboard.tripDescriptionPlaceholder': '這次旅行是關於什麼的?',
'dashboard.startDate': '開始日期',
'dashboard.endDate': '結束日期',
+ 'dashboard.dayCount': '天數',
+ 'dashboard.dayCountHint': '未設定旅行日期時,要規劃的天數。',
'dashboard.noDateHint': '未設定日期——將預設建立 7 天。你可以隨時修改。',
'dashboard.coverImage': '封面圖片',
'dashboard.addCoverImage': '新增封面圖片',
@@ -127,6 +129,12 @@ const zhTw: Record = {
// Settings
'settings.title': '設定',
'settings.subtitle': '配置你的個人設定',
+ 'settings.tabs.display': '顯示',
+ 'settings.tabs.map': '地圖',
+ 'settings.tabs.notifications': '通知',
+ 'settings.tabs.integrations': '整合',
+ 'settings.tabs.account': '帳戶',
+ 'settings.tabs.about': '關於',
'settings.map': '地圖',
'settings.mapTemplate': '地圖模板',
'settings.mapTemplatePlaceholder.select': '選擇模板...',
@@ -163,6 +171,19 @@ const zhTw: Record = {
'settings.notifyCollabMessage': '聊天訊息 (Collab)',
'settings.notifyPackingTagged': '行李清單:分配',
'settings.notifyWebhook': 'Webhook 通知',
+ 'settings.notifyVersionAvailable': '有新版本可用',
+ 'settings.notificationPreferences.email': '電子郵件',
+ 'settings.notificationPreferences.webhook': 'Webhook',
+ 'settings.notificationPreferences.inapp': '應用程式內',
+ 'settings.notificationPreferences.noChannels': '未配置通知渠道。請聯絡管理員設定電子郵件或 Webhook 通知。',
+ 'settings.webhookUrl.label': 'Webhook URL',
+ 'settings.webhookUrl.placeholder': 'https://discord.com/api/webhooks/...',
+ 'settings.webhookUrl.hint': '輸入您的 Discord、Slack 或自訂 Webhook URL 以接收通知。',
+ 'settings.webhookUrl.save': '儲存',
+ 'settings.webhookUrl.saved': 'Webhook URL 已儲存',
+ 'settings.webhookUrl.test': '測試',
+ 'settings.webhookUrl.testSuccess': '測試 Webhook 傳送成功',
+ 'settings.webhookUrl.testFailed': '測試 Webhook 傳送失敗',
'settings.notificationsDisabled': '通知尚未配置。請聯絡管理員啟用電子郵件或 Webhook 通知。',
'settings.notificationsActive': '活躍頻道',
'settings.notificationsManagedByAdmin': '通知事件由管理員配置。',
@@ -171,18 +192,26 @@ const zhTw: Record = {
'admin.notifications.none': '已停用',
'admin.notifications.email': '電子郵件 (SMTP)',
'admin.notifications.webhook': 'Webhook',
- 'admin.notifications.events': '通知事件',
- 'admin.notifications.eventsHint': '選擇哪些事件為所有使用者觸發通知。',
- 'admin.notifications.configureFirst': '請先在下方配置 SMTP 或 Webhook,然後啟用事件。',
'admin.notifications.save': '儲存通知設定',
'admin.notifications.saved': '通知設定已儲存',
'admin.notifications.testWebhook': '傳送測試 Webhook',
'admin.notifications.testWebhookSuccess': '測試 Webhook 傳送成功',
'admin.notifications.testWebhookFailed': '測試 Webhook 傳送失敗',
+ 'admin.notifications.emailPanel.title': '電子郵件 (SMTP)',
+ 'admin.notifications.webhookPanel.title': 'Webhook',
+ 'admin.notifications.inappPanel.title': '應用程式內通知',
+ 'admin.notifications.inappPanel.hint': '應用程式內通知始終啟用,無法全域性停用。',
+ 'admin.notifications.adminWebhookPanel.title': '管理員 Webhook',
+ 'admin.notifications.adminWebhookPanel.hint': '此 Webhook 專用於管理員通知(例如版本提醒)。它與每位使用者的 Webhook 分開,設定後始終會觸發。',
+ 'admin.notifications.adminWebhookPanel.saved': '管理員 Webhook URL 已儲存',
+ 'admin.notifications.adminWebhookPanel.testSuccess': '測試 Webhook 傳送成功',
+ 'admin.notifications.adminWebhookPanel.testFailed': '測試 Webhook 傳送失敗',
+ 'admin.notifications.adminWebhookPanel.alwaysOnHint': '配置 URL 後,管理員 Webhook 始終觸發',
+ 'admin.notifications.adminNotificationsHint': '配置哪些渠道傳遞僅管理員通知(例如版本提醒)。',
'admin.smtp.title': '郵件與通知',
'admin.smtp.hint': '用於傳送電子郵件通知的 SMTP 配置。',
'admin.smtp.testButton': '傳送測試郵件',
- 'admin.webhook.hint': '向外部 Webhook 傳送通知(Discord、Slack 等)。',
+ 'admin.webhook.hint': '允許使用者配置自己的 Webhook URL 以接收通知(Discord、Slack 等)。',
'admin.smtp.testSuccess': '測試郵件傳送成功',
'admin.smtp.testFailed': '測試郵件傳送失敗',
'dayplan.icsTooltip': '匯出日曆 (ICS)',
@@ -220,6 +249,7 @@ const zhTw: Record = {
'settings.mcp.endpoint': 'MCP 端點',
'settings.mcp.clientConfig': '客戶端配置',
'settings.mcp.clientConfigHint': '將 替換為下方列表中的 API 令牌。npx 的路徑可能需要根據您的系統進行調整(例如 Windows 上為 C:\\PROGRA~1\\nodejs\\npx.cmd)。',
+ 'settings.mcp.clientConfigHintOAuth': '將 和 替換為上方建立的 OAuth 2.1 客戶端所顯示的憑據。首次連線時,mcp-remote 將開啟瀏覽器完成授權。npx 的路徑可能需要根據您的系統進行調整(例如 Windows 上為 C:\\PROGRA~1\\nodejs\\npx.cmd)。',
'settings.mcp.copy': '複製',
'settings.mcp.copied': '已複製!',
'settings.mcp.apiTokens': 'API 令牌',
@@ -241,8 +271,58 @@ const zhTw: Record = {
'settings.mcp.toast.createError': '建立令牌失敗',
'settings.mcp.toast.deleted': '令牌已刪除',
'settings.mcp.toast.deleteError': '刪除令牌失敗',
+ 'settings.mcp.apiTokensDeprecated': 'API 金鑰已棄用,將於未來版本中移除。請改用 OAuth 2.1 客戶端。',
+ 'settings.oauth.clients': 'OAuth 2.1 客戶端',
+ 'settings.oauth.clientsHint': '註冊 OAuth 2.1 客戶端,讓第三方 MCP 應用程式(Claude Web、Cursor 等)無需靜態金鑰即可連線。',
+ 'settings.oauth.createClient': '新增客戶端',
+ 'settings.oauth.noClients': '尚無已註冊的 OAuth 客戶端。',
+ 'settings.oauth.clientId': '客戶端 ID',
+ 'settings.oauth.clientSecret': '客戶端密鑰',
+ 'settings.oauth.deleteClient': '刪除客戶端',
+ 'settings.oauth.deleteClientMessage': '此客戶端及所有活躍工作階段將被永久刪除。任何使用此客戶端的應用程式將立即失去存取權限。',
+ 'settings.oauth.rotateSecret': '輪換密鑰',
+ 'settings.oauth.rotateSecretMessage': '將產生新的客戶端密鑰,所有現有工作階段將立即失效。請在關閉此對話框前更新您的應用程式。',
+ 'settings.oauth.rotateSecretConfirm': '輪換',
+ 'settings.oauth.rotateSecretConfirming': '輪換中…',
+ 'settings.oauth.rotateSecretDoneTitle': '已產生新密鑰',
+ 'settings.oauth.rotateSecretDoneWarning': '此密鑰僅顯示一次。請立即複製並更新您的應用程式——所有先前的工作階段已失效。',
+ 'settings.oauth.activeSessions': '活躍的 OAuth 工作階段',
+ 'settings.oauth.sessionScopes': '授權範圍',
+ 'settings.oauth.sessionExpires': '到期時間',
+ 'settings.oauth.revoke': '撤銷',
+ 'settings.oauth.revokeSession': '撤銷工作階段',
+ 'settings.oauth.revokeSessionMessage': '這將立即撤銷此 OAuth 工作階段的存取權限。',
+ 'settings.oauth.modal.createTitle': '註冊 OAuth 客戶端',
+ 'settings.oauth.modal.presets': '快速預設',
+ 'settings.oauth.modal.clientName': '應用程式名稱',
+ 'settings.oauth.modal.clientNamePlaceholder': '例如 Claude Web、我的 MCP 應用程式',
+ 'settings.oauth.modal.redirectUris': '重新導向 URI',
+ 'settings.oauth.modal.redirectUrisPlaceholder': 'https://your-app.com/callback\nhttps://your-app.com/auth',
+ 'settings.oauth.modal.redirectUrisHint': '每行一個 URI。需要 HTTPS(localhost 除外)。需要完全符合。',
+ 'settings.oauth.modal.scopes': '允許的授權範圍',
+ 'settings.oauth.modal.scopesHint': 'list_trips 和 get_trip_summary 始終可用——不需要授權範圍。它們可幫助 AI 找到所需的行程 ID。',
+ 'settings.oauth.modal.selectAll': '全選',
+ 'settings.oauth.modal.deselectAll': '取消全選',
+ 'settings.oauth.modal.creating': '註冊中…',
+ 'settings.oauth.modal.create': '註冊客戶端',
+ 'settings.oauth.modal.createdTitle': '客戶端已註冊',
+ 'settings.oauth.modal.createdWarning': '客戶端密鑰僅顯示一次。請立即複製——無法恢復。',
+ 'settings.oauth.toast.createError': '註冊 OAuth 客戶端失敗',
+ 'settings.oauth.toast.deleted': 'OAuth 客戶端已刪除',
+ 'settings.oauth.toast.deleteError': '刪除 OAuth 客戶端失敗',
+ 'settings.oauth.toast.revoked': '工作階段已撤銷',
+ 'settings.oauth.toast.revokeError': '撤銷工作階段失敗',
+ 'settings.oauth.toast.rotateError': '輪換客戶端密鑰失敗',
'settings.account': '賬戶',
'settings.about': '關於',
+ 'settings.about.reportBug': '回報錯誤',
+ 'settings.about.reportBugHint': '發現問題?告訴我們',
+ 'settings.about.featureRequest': '功能建議',
+ 'settings.about.featureRequestHint': '建議新功能',
+ 'settings.about.wikiHint': '文件與指南',
+ 'settings.about.description': 'TREK 是一款自架旅遊規劃器,幫助您從最初構想到最後回憶,整理每次旅行。日程規劃、預算、行李清單、照片及更多功能——全部集中在您自己的伺服器上。',
+ 'settings.about.madeWith': '以',
+ 'settings.about.madeBy': '由 Maurice 及不斷成長的開源社群製作。',
'settings.username': '使用者名稱',
'settings.email': '郵箱',
'settings.role': '角色',
@@ -385,6 +465,7 @@ const zhTw: Record = {
'admin.tabs.categories': '分類',
'admin.tabs.backup': '備份',
'admin.tabs.audit': '審計日誌',
+ 'admin.tabs.notifications': '通知',
'admin.stats.users': '使用者',
'admin.stats.trips': '旅行',
'admin.stats.places': '地點',
@@ -531,9 +612,10 @@ const zhTw: Record = {
'admin.weather.locationHint': '天氣基於每天中第一個有座標的地點。如果當天沒有分配地點,則使用地點列表中的任意地點作為參考。',
// MCP Tokens
- 'admin.tabs.mcpTokens': 'MCP 令牌',
- 'admin.mcpTokens.title': 'MCP 令牌',
- 'admin.mcpTokens.subtitle': '管理所有使用者的 API 令牌',
+ 'admin.tabs.mcpTokens': 'MCP 存取',
+ 'admin.mcpTokens.title': 'MCP 存取',
+ 'admin.mcpTokens.subtitle': '管理所有使用者的 OAuth 工作階段和 API 令牌',
+ 'admin.mcpTokens.sectionTitle': 'API 令牌',
'admin.mcpTokens.owner': '所有者',
'admin.mcpTokens.tokenName': '令牌名稱',
'admin.mcpTokens.created': '建立時間',
@@ -545,6 +627,17 @@ const zhTw: Record = {
'admin.mcpTokens.deleteSuccess': '令牌已刪除',
'admin.mcpTokens.deleteError': '刪除令牌失敗',
'admin.mcpTokens.loadError': '載入令牌失敗',
+ 'admin.oauthSessions.sectionTitle': 'OAuth 工作階段',
+ 'admin.oauthSessions.clientName': '客戶端',
+ 'admin.oauthSessions.owner': '所有者',
+ 'admin.oauthSessions.scopes': '權限範圍',
+ 'admin.oauthSessions.created': '建立時間',
+ 'admin.oauthSessions.empty': '目前沒有活躍的 OAuth 工作階段',
+ 'admin.oauthSessions.revokeTitle': '撤銷工作階段',
+ 'admin.oauthSessions.revokeMessage': '此 OAuth 工作階段將立即被撤銷。客戶端將失去 MCP 存取權限。',
+ 'admin.oauthSessions.revokeSuccess': '工作階段已撤銷',
+ 'admin.oauthSessions.revokeError': '撤銷工作階段失敗',
+ 'admin.oauthSessions.loadError': '載入 OAuth 工作階段失敗',
// GitHub
'admin.tabs.github': 'GitHub',
@@ -724,8 +817,10 @@ const zhTw: Record = {
'atlas.unmark': '移除',
'atlas.confirmMark': '將此國家標記為已訪問?',
'atlas.confirmUnmark': '從已訪問列表中移除此國家?',
+ 'atlas.confirmUnmarkRegion': '從已訪問列表中移除此地區?',
'atlas.markVisited': '標記為已訪問',
'atlas.markVisitedHint': '將此國家新增到已訪問列表',
+ 'atlas.markRegionVisitedHint': '將此地區新增到已訪問列表',
'atlas.addToBucket': '新增到心願單',
'atlas.addPoi': '新增地點',
'atlas.searchCountry': '搜尋國家...',
@@ -739,6 +834,8 @@ const zhTw: Record = {
'trip.tabs.reservationsShort': '預訂',
'trip.tabs.packing': '行李清單',
'trip.tabs.packingShort': '行李',
+ 'trip.tabs.lists': '清單',
+ 'trip.tabs.listsShort': '清單',
'trip.tabs.budget': '預算',
'trip.tabs.files': '檔案',
'trip.loading': '載入旅行中...',
@@ -933,6 +1030,32 @@ const zhTw: Record = {
'reservations.linkAssignment': '關聯日程分配',
'reservations.pickAssignment': '從計劃中選擇一個分配...',
'reservations.noAssignment': '無關聯(獨立)',
+ 'reservations.price': '價格',
+ 'reservations.budgetCategory': '預算分類',
+ 'reservations.budgetCategoryPlaceholder': '如:交通、住宿',
+ 'reservations.budgetCategoryAuto': '自動(依預訂類型)',
+ 'reservations.budgetHint': '儲存時將自動建立預算條目。',
+ 'reservations.departureDate': '出發日期',
+ 'reservations.arrivalDate': '到達日期',
+ 'reservations.departureTime': '出發時間',
+ 'reservations.arrivalTime': '到達時間',
+ 'reservations.pickupDate': '取車日期',
+ 'reservations.returnDate': '還車日期',
+ 'reservations.pickupTime': '取車時間',
+ 'reservations.returnTime': '還車時間',
+ 'reservations.endDate': '結束日期',
+ 'reservations.meta.departureTimezone': '出發時區',
+ 'reservations.meta.arrivalTimezone': '到達時區',
+ 'reservations.span.departure': '出發',
+ 'reservations.span.arrival': '到達',
+ 'reservations.span.inTransit': '途中',
+ 'reservations.span.pickup': '取車',
+ 'reservations.span.return': '還車',
+ 'reservations.span.active': '進行中',
+ 'reservations.span.start': '開始',
+ 'reservations.span.end': '結束',
+ 'reservations.span.ongoing': '進行中',
+ 'reservations.validation.endBeforeStart': '結束日期/時間必須晚於開始日期/時間',
// Budget
'budget.title': '預算',
@@ -959,6 +1082,7 @@ const zhTw: Record = {
'budget.totalBudget': '總預算',
'budget.byCategory': '按分類',
'budget.editTooltip': '點選編輯',
+ 'budget.linkedToReservation': '已連結至預訂——請在那裡編輯名稱',
'budget.confirm.deleteCategory': '確定刪除分類「{name}」及其 {count} 個條目?',
'budget.deleteCategory': '刪除分類',
'budget.perPerson': '人均',
@@ -1049,6 +1173,7 @@ const zhTw: Record