diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 83b6b01f3..fbcd9d5c1 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -33,6 +33,7 @@ body: options: # The below comment is used to insert a new version with on-release.yml #NEXT_VERSION# + - 1.68.0 - 1.67.0 - 1.66.1 - 1.66.0 diff --git a/.github/workflows/deployment-docker-image.yml b/.github/workflows/deployment-docker-image.yml index 359ca09d7..d80cb5e1f 100644 --- a/.github/workflows/deployment-docker-image.yml +++ b/.github/workflows/deployment-docker-image.yml @@ -66,7 +66,7 @@ jobs: - uses: actions/setup-node@v6 if: env.SKIP_RELEASE == 'false' with: - node-version: 24.17.0 + node-version: 24.18.0 package-manager-cache: false - run: npm i -g pnpm if: env.SKIP_RELEASE == 'false' diff --git a/.gitignore b/.gitignore index 1bb62e979..c22262f6d 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ # dependencies node_modules +.pnpm-store/ .pnp .pnp.js .idea/ @@ -70,7 +71,10 @@ apps/nextjs/public/images/background.png # personal agent config .opencode +.agents/ +skills-lock.json # docusaurus apps/docs/.docusaurus apps/docs/build +.pnpm-store diff --git a/.mise.toml b/.mise.toml index 0b18e941c..d6bd41fc8 100644 --- a/.mise.toml +++ b/.mise.toml @@ -1,2 +1,2 @@ [tools] -node = "24.17.0" +node = "24.18.0" diff --git a/.nvmrc b/.nvmrc index 1dd37d537..ca5c35005 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -24.17.0 +24.18.0 diff --git a/.vscode/extensions.json b/.vscode/extensions.json index f0cdbe63c..8f1b6afce 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,7 +1,7 @@ { "recommendations": [ "dbaeumer.vscode-eslint", - "esbenp.prettier-vscode", + "oxc.oxc-vscode", "yoavbls.pretty-ts-errors", "million.million-lint", "lokalise.i18n-ally" diff --git a/CHANGES.md b/CHANGES.md new file mode 100644 index 000000000..c8a801420 --- /dev/null +++ b/CHANGES.md @@ -0,0 +1,96 @@ +# Changes + +## Goal + +Remove widget-only services from Homarr's global Integration system. + +The affected services are release providers and Search.ch. They are only used by specific widgets, so keeping them as global integrations added unnecessary setup, UI, database rows, docs pages, and integration package code. + +ArchiveTeam Warrior remains a proper integration but is no longer auto-seeded on first install. + +## Why + +These services do not need shared integration state. + +- Release providers are selected per Releases widget repository. +- Search.ch is only used by the Timetable widget. + +Moving them into widget options makes configuration simpler and removes dead-weight integration code from `packages/integrations`. + +## What Changed + +### Release Providers + +- Added release provider definitions in `packages/definitions/src/release-provider.ts`. +- Removed release providers from `IntegrationKind`. +- Moved release-fetching code out of `packages/integrations` and into `packages/request-handler/src/release-providers.ts`. +- Updated the Releases widget to store provider configuration directly in widget options: + - `provider` + - `identifier` + - `versionFilter` + - `providerUrl` +- Updated the Releases widget editor to use a provider select instead of an integration select. +- Updated Docker import mapping so `ghcr.io` maps to `gitHubContainerRegistry`. +- GitHub Container Registry now uses the public OCI registry API and accepts full image references such as `ghcr.io/homarr-labs/homarr:latest`. + +### Timetable / Search.ch + +- Removed Search.ch as an integration. +- Added a Timetable widget `baseUrl` option, defaulting to `https://search.ch`. +- Moved Search.ch request/parsing logic into `packages/request-handler/src/timetable.ts`. +- Updated the Timetable API to receive `baseUrl` directly from widget options. + +### ArchiveTeam Warrior + +- Kept as a proper integration (not removed). +- Removed `defaultUrl` from the integration definition to prevent auto-seeding on first install. + +### Integration Cleanup + +- Removed creators, exports, docs references, and implementation files for the deleted integrations. +- Removed unused dependencies from `packages/integrations/package.json`. + +### Database Migration + +- Added `packages/db/migrations/custom/0003_remove_widget_only_integrations.ts`. +- The migration moves old integration-linked widget config into widget options, then deletes the old widget-only integration rows. +- Updated the earlier release widget migration to use the new `provider` field instead of `providerIntegrationId`. + +### Docs + +- Updated Releases widget docs to describe providers instead of integrations. +- Updated Timetable docs to remove Search.ch integration setup. +- Removed obsolete integration docs pages for the deleted integration kinds. +- Updated docs sitemap and integration docs mappings. + +### Translations + +- Added English labels for the new widget options: + - `widget.timetable.option.baseUrl.label` + - `widget.releases.option.repositories.providerUrl.label` + +## How It Works Now + +Release provider configuration lives on each Releases widget repository entry. The request handler resolves provider metadata and fetches releases directly from the provider-specific endpoint. GHCR uses anonymous OCI registry access for public packages. + +Timetable passes its widget URL option directly to its API router and request handler. No global integration lookup is needed. + +ArchiveTeam Warrior continues to use the standard integration system, but users must manually add the integration (it is not auto-created on install). + +## Deliberate Non-Goals + +- No provider secret/token support was added. +- No new secret storage model was introduced. +- No compatibility layer was kept in `packages/integrations`. + +Widget options are client-visible, so storing provider credentials there would be wrong. If authenticated provider requests are needed later, they need a separate secret-storage design. + +## Verification So Far + +- Focused typechecks passed for affected packages. +- Full `pnpm turbo typecheck` passed. +- Full `pnpm lint` passed with existing warnings. +- Focused release provider normalization tests passed. +- Widget translation test passed. +- Live GHCR smoke checks passed for `ghcr.io/muchobien/pocketbase:latest` and `ghcr.io/homarr-labs/homarr:latest`. +- `pnpm test packages/integrations/test/pi-hole.spec.ts` passed earlier, but failed on rebased reruns because Pi-hole reported `domainsBeingBlocked` as `-2`/`0` instead of `> 1`. diff --git a/CODEOWNERS b/CODEOWNERS index b3e2214ac..ad494df91 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -1,12 +1,193 @@ # ── CODEOWNERS ─────────────────────────────────────── * @homarr-labs/maintainers -# Specific features / integrations -# Glueten -apps/docs/docs/integrations/glueten @Lok3sh-kumar +# Specific features / integrations +# These owners come from the PR that introduced each integration implementation. +# Specific rules override the global fallback because CODEOWNERS uses the last +# matching pattern; do not include @homarr-labs/maintainers here unless they +# should also be requested for that integration. + +# AdGuard Home - introduced in https://github.com/homarr-labs/homarr/pull/929 by @hillaliy +apps/docs/docs/integrations/adguard-home @hillaliy +packages/integrations/src/adguard-home @hillaliy + +# Anchor - introduced in https://github.com/homarr-labs/homarr/pull/5040 by @ohmzi +apps/docs/docs/integrations/anchor @ohmzi +packages/integrations/src/anchor @ohmzi + +# ArchiveTeam Warrior - introduced in https://github.com/homarr-labs/homarr/pull/5784 by @darbyjack +apps/docs/docs/integrations/archive-team-warrior @darbyjack +packages/integrations/src/archive-team-warrior @darbyjack + +# Audiobookshelf - introduced in https://github.com/homarr-labs/homarr/pull/5910 by @ajnart +apps/docs/docs/integrations/audiobookshelf @ajnart +packages/integrations/src/audiobookshelf @ajnart + +# Beszel - introduced in https://github.com/homarr-labs/homarr/pull/5900 by @ajnart +apps/docs/docs/integrations/beszel @ajnart +packages/integrations/src/beszel @ajnart + +# Codeberg - introduced in https://github.com/homarr-labs/homarr/pull/3253 by @Aandree5 +apps/docs/docs/integrations/codeberg @Aandree5 +packages/integrations/src/codeberg @Aandree5 + +# Coolify - introduced in https://github.com/homarr-labs/homarr/pull/4789 by @Rhiz3K +apps/docs/docs/integrations/coolify @Rhiz3K +packages/integrations/src/coolify @Rhiz3K + +# Dash. - introduced in https://github.com/homarr-labs/homarr/pull/1541 by @manuel-rw +apps/docs/docs/integrations/dash-dot @manuel-rw +packages/integrations/src/dashdot @manuel-rw + +# Docker Hub - introduced in https://github.com/homarr-labs/homarr/pull/3253 by @Aandree5 +apps/docs/docs/integrations/docker-hub @Aandree5 +packages/integrations/src/docker-hub @Aandree5 + +# Emby - introduced in https://github.com/homarr-labs/homarr/pull/2466 by @Meierschlumpf +apps/docs/docs/integrations/emby @Meierschlumpf +packages/integrations/src/emby @Meierschlumpf + +# GitHub - introduced in https://github.com/homarr-labs/homarr/pull/3253 by @Aandree5 +apps/docs/docs/integrations/github @Aandree5 +packages/integrations/src/github @Aandree5 + +# GitHub Container Registry - introduced in https://github.com/homarr-labs/homarr/pull/3727 by @Aandree5 +apps/docs/docs/integrations/github-containerregistry @Aandree5 +packages/integrations/src/github-container-registry @Aandree5 + +# GitLab - introduced in https://github.com/homarr-labs/homarr/pull/3253 by @Aandree5 +apps/docs/docs/integrations/gitlab @Aandree5 +packages/integrations/src/gitlab @Aandree5 + +# Glances - introduced in https://github.com/homarr-labs/homarr/pull/5102 by @manuel-rw +apps/docs/docs/integrations/glances @manuel-rw +packages/integrations/src/glances @manuel-rw + +# Gluetun - introduced in https://github.com/homarr-labs/homarr/pull/5812 by @Lok3sh-kumar +apps/docs/docs/integrations/gluetun @Lok3sh-kumar packages/integrations/src/gluetun @Lok3sh-kumar -# Exempt Renovate‑managed files (no owners) +# Gotify - introduced in https://github.com/homarr-labs/homarr/pull/5847 by @Nerdy-Technician +apps/docs/docs/integrations/gotify @Nerdy-Technician +packages/integrations/src/gotify @Nerdy-Technician + +# Home Assistant - introduced in https://github.com/homarr-labs/homarr/pull/578 by @manuel-rw +apps/docs/docs/integrations/home-assistant @manuel-rw +packages/integrations/src/homeassistant @manuel-rw + +# ICal - introduced in https://github.com/homarr-labs/homarr/pull/3980 by @NicolasNewman +apps/docs/docs/integrations/ical @NicolasNewman +packages/integrations/src/ical @NicolasNewman + +# Immich - introduced in https://github.com/homarr-labs/homarr/pull/5165 by @manuel-rw +apps/docs/docs/integrations/immich @manuel-rw +packages/integrations/src/immich @manuel-rw + +# Jellyfin - introduced in https://github.com/homarr-labs/homarr/pull/672 by @manuel-rw +apps/docs/docs/integrations/jellyfin @manuel-rw +packages/integrations/src/jellyfin @manuel-rw + +# Jellyseerr - introduced in https://github.com/homarr-labs/homarr/pull/774 by @manuel-rw +apps/docs/docs/integrations/jellyseerr @manuel-rw +packages/integrations/src/jellyseerr @manuel-rw + +# LinuxServer.io - introduced in https://github.com/homarr-labs/homarr/pull/3607 by @Aandree5 +apps/docs/docs/integrations/linux-server-io @Aandree5 +packages/integrations/src/linuxserverio @Aandree5 + +# Navidrome - introduced in https://github.com/homarr-labs/homarr/pull/5910 by @ajnart +apps/docs/docs/integrations/navidrome @ajnart +packages/integrations/src/navidrome @ajnart + +# Nextcloud - introduced in https://github.com/homarr-labs/homarr/pull/2501 by @manuel-rw +apps/docs/docs/integrations/nextcloud @manuel-rw +packages/integrations/src/nextcloud @manuel-rw + +# npm - introduced in https://github.com/homarr-labs/homarr/pull/3253 by @Aandree5 +apps/docs/docs/integrations/npm @Aandree5 +packages/integrations/src/npm @Aandree5 + +# ntfy - introduced in https://github.com/homarr-labs/homarr/pull/2900 by @itzTheMeow +apps/docs/docs/integrations/ntfy @ajnart @manuel-rw +packages/integrations/src/ntfy @ajnart @manuel-rw + +# OpenMediaVault - introduced in https://github.com/homarr-labs/homarr/pull/1142 by @hillaliy +apps/docs/docs/integrations/open-media-vault @hillaliy +packages/integrations/src/openmediavault @hillaliy + +# OPNsense - introduced in https://github.com/homarr-labs/homarr/pull/3424 by @Oupsman +apps/docs/docs/integrations/opnsense @Oupsman +packages/integrations/src/opnsense @Oupsman + +# Overseerr - introduced in https://github.com/homarr-labs/homarr/pull/774 by @manuel-rw +apps/docs/docs/integrations/overseerr @manuel-rw +packages/integrations/src/overseerr @manuel-rw + +# Paperless-ngx - introduced in https://github.com/homarr-labs/homarr/pull/5910 by @ajnart +apps/docs/docs/integrations/paperless-ngx @ajnart +packages/integrations/src/paperless-ngx @ajnart + +# PeaNUT - introduced in https://github.com/homarr-labs/homarr/pull/5862 by @afonsojramos +apps/docs/docs/integrations/peanut @afonsojramos +packages/integrations/src/peanut @afonsojramos + +# Pi-hole - introduced in https://github.com/homarr-labs/homarr/pull/521 by @Meierschlumpf +apps/docs/docs/integrations/pi-hole @Meierschlumpf +packages/integrations/src/pi-hole @Meierschlumpf + +# Plex - introduced in https://github.com/homarr-labs/homarr/pull/1342 by @hillaliy +apps/docs/docs/integrations/plex @hillaliy +packages/integrations/src/plex @hillaliy + +# Prowlarr - introduced in https://github.com/homarr-labs/homarr/pull/965 by @hillaliy +apps/docs/docs/integrations/prowlarr @hillaliy +packages/integrations/src/prowlarr @hillaliy + +# Proxmox - introduced in https://github.com/homarr-labs/homarr/pull/1969 by @Meierschlumpf +apps/docs/docs/integrations/proxmox @Meierschlumpf +packages/integrations/src/proxmox @Meierschlumpf + +# Quay - introduced in https://github.com/homarr-labs/homarr/pull/3607 by @Aandree5 +apps/docs/docs/integrations/quay @Aandree5 +packages/integrations/src/quay @Aandree5 + +# search.ch - introduced in https://github.com/homarr-labs/homarr/pull/5180 by @Meierschlumpf +apps/docs/docs/integrations/search-ch @Meierschlumpf +packages/integrations/src/search-ch @Meierschlumpf + +# Speedtest Tracker - introduced in https://github.com/homarr-labs/homarr/pull/5363 by @kylegrieder +apps/docs/docs/integrations/speedtest-tracker @kylegrieder +packages/integrations/src/speedtest-tracker @kylegrieder + +# Technitium DNS - introduced in https://github.com/homarr-labs/homarr/pull/5832 by @lopsch +apps/docs/docs/integrations/technitium-dns @lopsch +packages/integrations/src/technitium @lopsch + +# Tracearr - introduced in https://github.com/homarr-labs/homarr/pull/5060 by @StrandedTurtle +apps/docs/docs/integrations/tracearr @StrandedTurtle +packages/integrations/src/tracearr @StrandedTurtle + +# TrueNAS - introduced in https://github.com/homarr-labs/homarr/pull/3745 by @manuel-rw +apps/docs/docs/integrations/truenas @manuel-rw @afonsojramos +packages/integrations/src/truenas @manuel-rw @afonsojramos + +# Umami - introduced in https://github.com/homarr-labs/homarr/pull/5362 by @febrile42 +apps/docs/docs/integrations/umami @febrile42 +packages/integrations/src/umami @febrile42 + +# UniFi Controller - introduced in https://github.com/homarr-labs/homarr/pull/2236 by @pitschi +apps/docs/docs/integrations/unifi-controller @pitschi +packages/integrations/src/unifi-controller @pitschi + +# Unraid - introduced in https://github.com/homarr-labs/homarr/pull/4439 by @manuel-rw +apps/docs/docs/integrations/unraid @manuel-rw +packages/integrations/src/unraid @manuel-rw + +# Uptime Kuma - introduced in https://github.com/homarr-labs/homarr/pull/5910 by @ajnart +apps/docs/docs/integrations/uptime-kuma @ajnart +packages/integrations/src/uptime-kuma @ajnart + +# Exempt Renovate-managed files (no owners) package.json **/package.json package-lock.json diff --git a/Dockerfile b/Dockerfile index 9c4d41de8..e7b5b679c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM node:24.17.0-alpine AS base +FROM node:24.18.0-alpine AS base FROM base AS builder RUN apk add --no-cache libc6-compat @@ -42,6 +42,7 @@ RUN mkdir -p /var/cache/nginx && \ COPY --from=builder /app/apps/nextjs/next.config.ts . COPY --from=builder /app/apps/nextjs/package.json . +COPY --from=builder /app/pnpm-workspace.yaml ./pnpm-workspace.yaml COPY --from=builder /app/node_modules/better-sqlite3/build/Release/better_sqlite3.node /app/build/better_sqlite3.node diff --git a/apps/docs/docs/getting-started/after-the-installation.mdx b/apps/docs/docs/getting-started/after-the-installation.mdx index 5f923cec8..7ac904bf4 100644 --- a/apps/docs/docs/getting-started/after-the-installation.mdx +++ b/apps/docs/docs/getting-started/after-the-installation.mdx @@ -28,6 +28,7 @@ Read our [glossary](/docs/getting-started/glossary) if you are new to Homarr. Congratulations on installing Homarr. Now it's time to get yourself familiar with the concepts of Homarr and how to use it. ## Onboarding + The onboard will show the first time you start Homarr. This process will help you with your first steps and adding some initial data, such as the administrator account. @@ -45,50 +46,68 @@ You can either import a previous dashboard using the ZIP archive or start from s You may refresh anytime and continue from a different device - the onboarding is saved immediately on the server for your convenience. ### Starting from scratch -Depending on your auth provider configuration you will either first see the admin user creation form or the external group creation form. -If credentials provider was selected, you'll be prompted to create an admin user for Homarr. -This user cannot be deleted and has full control over Homarr and all of it's boards. -You must choose an username and password (both can later be changed). -We enforce strict password requirements, when you enter your password, to protect your instance if you accidentially -expose it to the Internet. -{"Screenshot +Depending on your auth provider configuration you will either first see the admin user creation form or the external group creation form. +If the credentials provider was selected, you'll be prompted to create an admin user for Homarr. +This user cannot be deleted and has full control over Homarr and all of its boards. +You must choose a username and password (both can later be changed). +We enforce strict password requirements when you enter your password to protect your instance if you accidentally +expose it to the internet. + +{"Screenshot If you selected external providers (such as LDAP or OIDC), you'll be prompted to create an admin group. -This group will be used to synchronize users from your provider to Homarr. -When a user is logged in through the provider the groups of him that match groups with any in Homarr will be added to the user. -The name of the group has to match exactly the name of the group in the provider. +This group will be used to synchronize users from your provider to Homarr. +When a user logs in through the provider, any of their groups that match groups in Homarr will be assigned to the user. +The name of the group must match exactly the name of the group in the provider. -{"Screenshot +{"Screenshot Next, you can configure some basic settings for Homarr. We recommend to leave all settings at default for most users. Here you can also disable analytics. Please note that our analytics are completly anonymous and is fully compliant with GDPR and PECR. -{"Screenshot +{"Screenshot ### Setting up integrations After configuring server settings, the onboarding guides you through setting up your integrations. #### Docker service discovery + If Docker is available (socket mounted), Homarr automatically detects running containers and matches them to known integration types. Matching integrations are pre-selected in the integration list, saving you from manually picking each one. #### URL templating + You can define a base URL template to auto-fill integration URLs in bulk. Two modes are supported: + - **Subdomain mode** - e.g. `{app}.example.com` where `{app}` is replaced with the integration name - **Port mode** - e.g. `192.168.1.1:{port}` where `{port}` is replaced with the integration's default port #### API key helper links + For integrations that require API keys (such as Sonarr, Radarr, SABnzbd, or Seerr), a helper link opens the relevant settings page in the target application so you can quickly copy your key. #### Creating apps from Docker containers + You can optionally auto-create bookmark apps for all discovered Docker containers, making them immediately available on your board. :::note -Some niche and registry integrations (GitHub, npm, Docker Hub, GitLab, Codeberg, LinuxServer.io, GHCR, Quay, Search.ch) are hidden from the onboarding selection grid but remain available on the manage integrations page. +Release providers and Search.ch are configured directly in their widgets instead of the integrations page. ::: @@ -97,30 +116,41 @@ The onboarding can no longer be started again. From here you can click on one of the provided links to continue your journey. We recommend you to continue with the section [creating your first board on this page](#creating-your-first-board). -{"Screenshot +{"Screenshot ### Importing a ZIP from version before 1.0.0 + This is recommended when you used Homarr before and want to re-import your data. It is important that you first migrate correctly using [the list of breaking changes for 1.0](/blog/2024/09/23/version-1.0). -Export from old Homarr +Export from old Homarr **How to export and import data** -1. Navigate to your old Homarr, open your management pages, open the tools submenu and open ``Migrate to 1.0``. + +1. Navigate to your old Homarr, open your management pages, open the tools submenu and open `Migrate to 1.0`. 2. Check the contents that you want to export. We recommend you to export all data. 3. Click on the export data button 4. Save the ZIP file, that is being downloaded, on your device. If you selected "integrations" or "users", you also get an encryption / decryption key. -Copy it and do not loose it! -5. Go to your new >1.0.0 Homarr instance and start the onboarding. Choose ``Import from Homarr before 1.0`` + Copy it and do not loose it! +5. Go to your new >1.0.0 Homarr instance and start the onboarding. Choose `Import from Homarr before 1.0` 6. Drag and drop your previously downloaded ZIP archive into the file upload:
7. Next, choose the data that you want to import and how to import it. Since screen sizes were removed in 1.0, we create a new board for every screen size by default. We recommend you to only keep the best layout and adjust it later in 1.0.0 - but you can also import all of them and pick later.
8. If you previously exported integrations or users, you will be asked to decrypt the data safely using your obtained encryption token. Paste into the modal and confirm your action.
9. Next, you can [follow the same setup as when starting from scratch](#starting-from-scratch). After you completed the onboarding, all of your data will be migrated and available. ## Creating your first board + In Homarr, you organize your apps and widgets on boards. Boards can be organized using the interactive drag and drop. -Let's create your first board by navigating here: ``Manage`` > ``Boards`` > ``New board``: +Let's create your first board by navigating here: `Manage` > `Boards` > `New board`: ![](./img/manage-create-board-button.png) A board must always have a name and can either be public or private. @@ -133,12 +163,13 @@ Congratulations - you've just created your first board. You can see boards, that ![](./img/manage-view-board.png) ## Add your first app to Homarr + To add links to your dashboard, you must first add them as an "app". Navigate to this page to add an app: ![](./img/manage-navigate-apps.png) -Next, click on the ``New app`` button. You'll need to provide a name and an icon to create an app. +Next, click on the `New app` button. You'll need to provide a name and an icon to create an app. The name must be at least one character long but you can provide any image URL for the icon. Homarr ships with over 11K icons - so you can just search by entering a search text instead. @@ -149,8 +180,9 @@ After you created the app, you'll see in on the apps page: ![](./img/manage-created-app.png) ## Arrange and organize your board + Let's add your freshly created app, from the previous step, to the board. -To do this, edit your previously created board by clicking on the big ``Open board`` button: +To do this, edit your previously created board by clicking on the big `Open board` button: ![](./img/manage-view-board.png) @@ -165,23 +197,38 @@ Here you have the following options in the header: When entering the edit mode, you'll get some additional buttons to work with: -{"Screenshot +{"Screenshot - **Add item**: Click this button to add new elements to your page. - **Exit edit mode**: Click this btuton to save your changes and exit edit mode. To continue, click the option "New Item": -{"Screenshot +{"Screenshot Next, you can choose what item you'd like to add. We will discuss the other options in the following sections - for now choose "App" to continue. -{"Screenshot +{"Screenshot As soon as you chose the app, the modal will close and you'll get the following item on your board: -Screenshot of the No app element on the board +Screenshot of the No app element on the board This is expected, because you haven't told Homarr what app it should show in this item. To configure this, click on the small 3 dots at the top of the item in the edit mode and choose "Edit item" in the menu. @@ -228,17 +275,18 @@ Be sure to save your changes by exiting edit mode! ![](./img/exit-edit-mode.png) ## Adding your first integration + Now it's time to integrate some basic data into your board. To do this, we first need to create an integration. An integration is a connection between Homarr and a third party application - usually something that you run and deploy yourself. -Navigate to ``Manage`` > ``Integrations`` (for example via your user menu). +Navigate to `Manage` > `Integrations` (for example via your user menu). As an example, we are going to use the public demo instance of **Dash.** - a popular application to monitor CPU / hardware usage. Choose the **Dash.** integration from the list of available integrations: ![](./img/choose-integration.gif) -Next, choose a name (or leave it at default), enter ``https://dash.mauz.dev/`` as the URL in the form and click on ``Test connection and create``. +Next, choose a name (or leave it at default), enter `https://dash.mauz.dev/` as the URL in the form and click on `Test connection and create`. Pressing the save button will perform a test request to the integration, to confirm that the networking setup is working as expected. Sometimes invalid configurations, firewalls and other mistakes can lead to connectivity problems - [follow this guide here](/docs/management/integrations/#troubleshooting-connection) if you experience issues. @@ -254,13 +302,14 @@ the target application or your router. ::: ## Embed integration data using widgets + Let's add your integration, that you have configured in the section above, as a widget to your board. First, navigate back to your board that you want to add it to. Next, enter edit mode again and add another item to your board: ![](./img/manage-board-header-add-item-menu.png) -Instead of choosing an app, let's choose ``System Health Monitoring`` this time. +Instead of choosing an app, let's choose `System Health Monitoring` this time. Click on "Add to board": ![](./img/manage-board-header-choose-system-health.png) @@ -290,10 +339,12 @@ As with every change, don't forget to save your changes using the exit edit mode ![](./img/exit-edit-mode.png) ## Grouping your items + As the last step, let's organize your freshly created items using two different options. You can choose either or both of them on your boards. ### Using dynamic sections + Dynamic sections are a simple, resizable visual group to group items. ![](./img/create-dynamic-section.png) @@ -303,6 +354,7 @@ After adding a dynamic section, you can simply drag and drop items into the sect ![](./img/dynamic-section.gif) ### Using categories + Categories are more advanced and complicated than dynamic sections. To create a category, open the add item modal and choose "new category": @@ -329,22 +381,27 @@ You cannot reduce their horizontal width. After completing the onboarding, Homarr provides interactive guided tours to help you discover key features. ### Management tour + When you first visit the management area (`/manage`), Homarr walks you through the main pages: boards, apps, integrations, and users (admin only). Each page highlights the list view and the create action so you know where to find and add things. ### Board tour + When you first open a board, a tour highlights the board header elements: search, edit mode, settings, board switcher, and user menu. ### Tour controls + - Press **Enter** (or click **Next**) to advance through tour steps - Tours auto-start on first visit and do not repeat once completed - Tours are skipped on mobile screens (below `sm` breakpoint) - You can **reset tours** from the user settings page under **Manage > Users > [Your user] > General** ## Next steps + You're done with the basic setup 🔥. Now it's time to organize and customize even more. We highly recommend you to experiment with different options, styles and configurations. See these curated pages for your next steps: + - Create new apps using the [apps management](/docs/management/apps/) - Customize your board or add more boards using the [boards management](/docs/management/boards/) - Integrate software that you use using the [integrations management](/docs/management/integrations/) diff --git a/apps/docs/docs/integrations/archiveteam-warrior/index.mdx b/apps/docs/docs/integrations/archiveteam-warrior/index.mdx new file mode 100644 index 000000000..27256aee8 --- /dev/null +++ b/apps/docs/docs/integrations/archiveteam-warrior/index.mdx @@ -0,0 +1,35 @@ +--- +title: "ArchiveTeam Warrior" +description: "ArchiveTeam Warrior is an easy-to-run virtual machine that helps preserve websites by using some of your bandwidth and disk space to download and upload content to ArchiveTeam’s archive." +hide_title: true +--- + +import { IntegrationHeader } from "@site/src/components/integrations/header"; +import { AddingIntegration } from "@site/src/components/integrations/adding"; +import { IntegrationSecrets } from "@site/src/components/integrations/secrets"; +import { archiveTeamWarriorIntegration } from "."; + + + +The ArchiveTeam Warrior integration lets Homarr connect to your ArchiveTeam Warrior instance so you can manage and monitor your archiving tasks directly from your dashboard. + +### Adding the integration + + + +### Secrets + +ArchiveTeam Warrior does not require authentication by default. If your instance is protected with basic authentication, you can provide a username and password. + + diff --git a/apps/docs/docs/integrations/archiveteam-warrior/index.ts b/apps/docs/docs/integrations/archiveteam-warrior/index.ts new file mode 100644 index 000000000..5a18f6f4f --- /dev/null +++ b/apps/docs/docs/integrations/archiveteam-warrior/index.ts @@ -0,0 +1,9 @@ +import { IntegrationDefinition } from "@site/src/types"; + +export const archiveTeamWarriorIntegration: IntegrationDefinition = { + name: "ArchiveTeam Warrior", + description: + "ArchiveTeam Warrior is an easy-to-run virtual machine that helps preserve websites by using some of your bandwidth and disk space to download and upload content to ArchiveTeam’s archive.", + iconUrl: "https://cdn.jsdelivr.net/gh/selfhst/icons/png/archiveteam-warrior.png", + path: "../../integrations/archiveteam-warrior", +}; diff --git a/apps/docs/docs/integrations/codeberg/index.mdx b/apps/docs/docs/integrations/codeberg/index.mdx deleted file mode 100644 index 98ca314c2..000000000 --- a/apps/docs/docs/integrations/codeberg/index.mdx +++ /dev/null @@ -1,37 +0,0 @@ ---- -title: 'Codeberg' -description: "Codeberg is a non-profit, community-driven platform for hosting and collaborating on software projects." -hide_title: true ---- - -import { IntegrationHeader } from '@site/src/components/integrations/header'; -import { IntegrationCapabilites } from '@site/src/components/integrations/widgets'; -import { AddingIntegration } from '@site/src/components/integrations/adding'; -import { IntegrationSecrets } from '@site/src/components/integrations/secrets'; -import { codebergIntegration } from '.'; -import { releasesWidget } from '@site/docs/widgets/releases'; - - - - -### Widgets & Capabilities - - -### Adding the integration - - -### Secrets - \ No newline at end of file diff --git a/apps/docs/docs/integrations/codeberg/index.ts b/apps/docs/docs/integrations/codeberg/index.ts deleted file mode 100644 index f2a43ad48..000000000 --- a/apps/docs/docs/integrations/codeberg/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { IntegrationDefinition } from "@site/src/types"; - -export const codebergIntegration: IntegrationDefinition = { - name: "Codeberg", - description: - "Codeberg is a non-profit, community-driven platform for hosting and collaborating on software projects.", - iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/codeberg.svg", - path: "../../integrations/codeberg", -}; diff --git a/apps/docs/docs/integrations/docker-hub/index.mdx b/apps/docs/docs/integrations/docker-hub/index.mdx deleted file mode 100644 index a49d03047..000000000 --- a/apps/docs/docs/integrations/docker-hub/index.mdx +++ /dev/null @@ -1,37 +0,0 @@ ---- -title: 'Docker Hub' -description: "Docker Hub is a cloud-based registry service for sharing and managing Docker images." -hide_title: true ---- - -import { IntegrationHeader } from '@site/src/components/integrations/header'; -import { IntegrationCapabilites } from '@site/src/components/integrations/widgets'; -import { AddingIntegration } from '@site/src/components/integrations/adding'; -import { IntegrationSecrets } from '@site/src/components/integrations/secrets'; -import { dockerHubIntegration } from '.'; -import { releasesWidget } from '@site/docs/widgets/releases'; - - - - -### Widgets & Capabilities - - -### Adding the integration - - -### Secrets - \ No newline at end of file diff --git a/apps/docs/docs/integrations/docker-hub/index.ts b/apps/docs/docs/integrations/docker-hub/index.ts deleted file mode 100644 index 1a49feeae..000000000 --- a/apps/docs/docs/integrations/docker-hub/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { IntegrationDefinition } from "@site/src/types"; - -export const dockerHubIntegration: IntegrationDefinition = { - name: "Docker Hub", - description: "Docker Hub is a cloud-based registry service for sharing and managing Docker images.", - iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/docker.svg", - path: "../../integrations/docker-hub", -}; diff --git a/apps/docs/docs/integrations/github-containerregistry/index.mdx b/apps/docs/docs/integrations/github-containerregistry/index.mdx deleted file mode 100644 index f4cb5b310..000000000 --- a/apps/docs/docs/integrations/github-containerregistry/index.mdx +++ /dev/null @@ -1,42 +0,0 @@ ---- -title: 'GitHub Container Registry' -description: "GitHub Container Registry is a platform for hosting and managing packages." -hide_title: true ---- - -import { IntegrationHeader } from '@site/src/components/integrations/header'; -import { IntegrationCapabilites } from '@site/src/components/integrations/widgets'; -import { AddingIntegration } from '@site/src/components/integrations/adding'; -import { IntegrationSecrets } from '@site/src/components/integrations/secrets'; -import { gitHubContainerRegistryIntegration } from '.'; -import { releasesWidget } from '@site/docs/widgets/releases'; - - - - - -### Widgets & Capabilities - - -### Adding the integration - - -### Secrets - \ No newline at end of file diff --git a/apps/docs/docs/integrations/github-containerregistry/index.ts b/apps/docs/docs/integrations/github-containerregistry/index.ts deleted file mode 100644 index b88f426d3..000000000 --- a/apps/docs/docs/integrations/github-containerregistry/index.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { IntegrationDefinition } from "@site/src/types"; - -export const gitHubContainerRegistryIntegration: IntegrationDefinition = { - name: "GitHub Container Registry", - description: "GitHub Container Registry is a platform for hosting and managing packages.", - iconUrl: { - light: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/github.svg", - dark: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/github-light.svg", - }, - path: "../../integrations/github-packages", -}; diff --git a/apps/docs/docs/integrations/github/index.mdx b/apps/docs/docs/integrations/github/index.mdx deleted file mode 100644 index 39c9777b3..000000000 --- a/apps/docs/docs/integrations/github/index.mdx +++ /dev/null @@ -1,41 +0,0 @@ ---- -title: 'GitHub' -description: "GitHub is a web-based platform for version control and collaboration." -hide_title: true ---- - -import { IntegrationHeader } from '@site/src/components/integrations/header'; -import { IntegrationCapabilites } from '@site/src/components/integrations/widgets'; -import { AddingIntegration } from '@site/src/components/integrations/adding'; -import { IntegrationSecrets } from '@site/src/components/integrations/secrets'; -import { githubIntegration } from '.'; -import { releasesWidget } from '@site/docs/widgets/releases'; - - - - -### Widgets & Capabilities - - -### Adding the integration - - -### Secrets - \ No newline at end of file diff --git a/apps/docs/docs/integrations/github/index.ts b/apps/docs/docs/integrations/github/index.ts deleted file mode 100644 index 7bb9b6b5d..000000000 --- a/apps/docs/docs/integrations/github/index.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { IntegrationDefinition } from "@site/src/types"; - -export const githubIntegration: IntegrationDefinition = { - name: "GitHub", - description: "GitHub is a web-based platform for version control and collaboration.", - iconUrl: { - light: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/github.svg", - dark: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/github-light.svg", - }, - path: "../../integrations/github", -}; diff --git a/apps/docs/docs/integrations/gitlab/index.mdx b/apps/docs/docs/integrations/gitlab/index.mdx deleted file mode 100644 index edc77e1cd..000000000 --- a/apps/docs/docs/integrations/gitlab/index.mdx +++ /dev/null @@ -1,37 +0,0 @@ ---- -title: 'GitLab' -description: "GitLab is a web-based DevOps lifecycle tool that provides a Git repository manager." -hide_title: true ---- - -import { IntegrationHeader } from '@site/src/components/integrations/header'; -import { IntegrationCapabilites } from '@site/src/components/integrations/widgets'; -import { AddingIntegration } from '@site/src/components/integrations/adding'; -import { IntegrationSecrets } from '@site/src/components/integrations/secrets'; -import { gitlabIntegration } from '.'; -import { releasesWidget } from '@site/docs/widgets/releases'; - - - - -### Widgets & Capabilities - - -### Adding the integration - - -### Secrets - \ No newline at end of file diff --git a/apps/docs/docs/integrations/gitlab/index.ts b/apps/docs/docs/integrations/gitlab/index.ts deleted file mode 100644 index c252cd6ff..000000000 --- a/apps/docs/docs/integrations/gitlab/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { IntegrationDefinition } from "@site/src/types"; - -export const gitlabIntegration: IntegrationDefinition = { - name: "GitLab", - description: "GitLab is a web-based DevOps lifecycle tool that provides a Git repository manager.", - iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/gitlab.svg", - path: "../../integrations/gitlab", -}; diff --git a/apps/docs/docs/integrations/linux-server-io/index.mdx b/apps/docs/docs/integrations/linux-server-io/index.mdx deleted file mode 100644 index 426dc7f68..000000000 --- a/apps/docs/docs/integrations/linux-server-io/index.mdx +++ /dev/null @@ -1,34 +0,0 @@ ---- -title: 'LinuxServer.io' -description: "LinuxServer.io is a platform for managing and deploying containerized applications." -hide_title: true ---- - -import { IntegrationHeader } from '@site/src/components/integrations/header'; -import { IntegrationCapabilites } from '@site/src/components/integrations/widgets'; -import { AddingIntegration } from '@site/src/components/integrations/adding'; -import { IntegrationSecrets } from '@site/src/components/integrations/secrets'; -import { linuxServerIoIntegration } from '.'; -import { releasesWidget } from '@site/docs/widgets/releases'; - - - - -### Widgets & Capabilities - - -### Adding the integration - - -### Secrets - \ No newline at end of file diff --git a/apps/docs/docs/integrations/linux-server-io/index.ts b/apps/docs/docs/integrations/linux-server-io/index.ts deleted file mode 100644 index 82bc0fac6..000000000 --- a/apps/docs/docs/integrations/linux-server-io/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { IntegrationDefinition } from "@site/src/types"; - -export const linuxServerIoIntegration: IntegrationDefinition = { - name: "LinuxServer.io", - description: "LinuxServer.io is a platform for managing and deploying containerized applications.", - iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/linuxserver-io.svg", - path: "../../integrations/linux-server-io", -}; diff --git a/apps/docs/docs/integrations/npm/index.mdx b/apps/docs/docs/integrations/npm/index.mdx deleted file mode 100644 index c4d21135e..000000000 --- a/apps/docs/docs/integrations/npm/index.mdx +++ /dev/null @@ -1,34 +0,0 @@ ---- -title: 'NPM' -description: "NPM is a package manager for the JavaScript programming language, primarily used for managing Node.js packages." -hide_title: true ---- - -import { IntegrationHeader } from '@site/src/components/integrations/header'; -import { IntegrationCapabilites } from '@site/src/components/integrations/widgets'; -import { AddingIntegration } from '@site/src/components/integrations/adding'; -import { IntegrationSecrets } from '@site/src/components/integrations/secrets'; -import { npmIntegration } from '.'; -import { releasesWidget } from '@site/docs/widgets/releases'; - - - - -### Widgets & Capabilities - - -### Adding the integration - - -### Secrets - \ No newline at end of file diff --git a/apps/docs/docs/integrations/npm/index.ts b/apps/docs/docs/integrations/npm/index.ts deleted file mode 100644 index 34fbafb5c..000000000 --- a/apps/docs/docs/integrations/npm/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { IntegrationDefinition } from "@site/src/types"; - -export const npmIntegration: IntegrationDefinition = { - name: "NPM", - description: - "NPM is a package manager for the JavaScript programming language, primarily used for managing Node.js packages.", - iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/npm.svg", - path: "../../integrations/npm", -}; diff --git a/apps/docs/docs/integrations/quay/index.mdx b/apps/docs/docs/integrations/quay/index.mdx deleted file mode 100644 index 9033f81cf..000000000 --- a/apps/docs/docs/integrations/quay/index.mdx +++ /dev/null @@ -1,37 +0,0 @@ ---- -title: 'Quay' -description: "Quay is a container registry for storing and managing container images." -hide_title: true ---- - -import { IntegrationHeader } from '@site/src/components/integrations/header'; -import { IntegrationCapabilites } from '@site/src/components/integrations/widgets'; -import { AddingIntegration } from '@site/src/components/integrations/adding'; -import { IntegrationSecrets } from '@site/src/components/integrations/secrets'; -import { quayIntegration } from '.'; -import { releasesWidget } from '@site/docs/widgets/releases'; - - - - -### Widgets & Capabilities - - -### Adding the integration - - -### Secrets - \ No newline at end of file diff --git a/apps/docs/docs/integrations/quay/index.ts b/apps/docs/docs/integrations/quay/index.ts deleted file mode 100644 index 315732bff..000000000 --- a/apps/docs/docs/integrations/quay/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { IntegrationDefinition } from "@site/src/types"; - -export const quayIntegration: IntegrationDefinition = { - name: "Quay", - description: "Quay is a container registry for storing and managing container images.", - iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/png/quay.png", - path: "../../integrations/quay", -}; diff --git a/apps/docs/docs/integrations/search-ch/index.mdx b/apps/docs/docs/integrations/search-ch/index.mdx deleted file mode 100644 index 6286d5cca..000000000 --- a/apps/docs/docs/integrations/search-ch/index.mdx +++ /dev/null @@ -1,28 +0,0 @@ ---- -title: 'Search.ch' -description: "Search.ch supports searching for timetables in Switzerland." -hide_title: true ---- - -import { IntegrationHeader } from '@site/src/components/integrations/header'; -import { IntegrationCapabilites } from '@site/src/components/integrations/widgets'; -import { AddingIntegration } from '@site/src/components/integrations/adding'; -import { IntegrationSecrets } from '@site/src/components/integrations/secrets'; -import { IconDownload } from "@tabler/icons-react"; -import { searchChIntegration } from '.'; -import Alert from '@theme/Admonition'; -import { timetableWidget } from '@site/docs/widgets/timetable'; - - - - -### Widgets & Capabilities - - -### Adding the integration - \ No newline at end of file diff --git a/apps/docs/docs/integrations/search-ch/index.ts b/apps/docs/docs/integrations/search-ch/index.ts deleted file mode 100644 index 6c5819d67..000000000 --- a/apps/docs/docs/integrations/search-ch/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { IntegrationDefinition } from "@site/src/types"; -export const searchChIntegration: IntegrationDefinition = { - name: "Search.ch", - description: "Search.ch supports searching for timetables in Switzerland.", - iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/png/search-ch.png", - path: "../../integrations/search-ch", -}; diff --git a/apps/docs/docs/widgets/calendar/index.mdx b/apps/docs/docs/widgets/calendar/index.mdx index 60095aabe..5db4e9b30 100644 --- a/apps/docs/docs/widgets/calendar/index.mdx +++ b/apps/docs/docs/widgets/calendar/index.mdx @@ -63,7 +63,9 @@ import withoutIntegrations from './img/without-integrations.png'; ### Interactions -- View calendar events by clicking on the marked dates. +- Hover over a marked date to preview its events in a popover. +- Click on a marked date to pin the popover open (it will stay visible even after moving the mouse away). +- Click the date again or click outside to close the pinned popover. ### Adding the widget diff --git a/apps/docs/docs/widgets/immich-album-carousel/index.ts b/apps/docs/docs/widgets/immich-album-carousel/index.ts index 327314c3d..f6c4b224c 100644 --- a/apps/docs/docs/widgets/immich-album-carousel/index.ts +++ b/apps/docs/docs/widgets/immich-album-carousel/index.ts @@ -9,9 +9,8 @@ export const immichAlbumCarouselWidget: WidgetDefinition = { configuration: { items: [ { - name: "Album ID", - description: - "This is a unique ID which refers to your Immich album. You can find this id by opening an album and copying the unique string from the URL, for example 'a21aca09-9f05-47dc-b1a1-7b53c547226e'", + name: "Album", + description: "Select the Immich album to show in the slideshow.", values: { type: "string" }, defaultValue: "", }, @@ -25,7 +24,13 @@ export const immichAlbumCarouselWidget: WidgetDefinition = { name: "Show photo info", description: "Display date of the picture and the current index at the bottom of the slideshow", values: { type: "boolean" }, - defaultValue: "yes", + defaultValue: "no", + }, + { + name: "Randomize photos", + description: "Show photos in a random order instead of always starting with the same album order.", + values: { type: "boolean" }, + defaultValue: "no", }, ], }, diff --git a/apps/docs/docs/widgets/releases/index.mdx b/apps/docs/docs/widgets/releases/index.mdx index 7808756f0..faf386a13 100644 --- a/apps/docs/docs/widgets/releases/index.mdx +++ b/apps/docs/docs/widgets/releases/index.mdx @@ -1,69 +1,44 @@ --- -title: 'Releases' -description: 'Displays a list of the current version of the given repositories with the given version regex.' +title: "Releases" +description: "Displays a list of the current version of the given repositories with the given version regex." hide_title: true --- -import { AddingWidget } from '@site/src/components/widgets/adding'; -import { WidgetConfig } from '@site/src/components/widgets/configuration'; -import { WidgetHeader } from '@site/src/components/widgets/header'; -import { WidgetIntegrations } from '@site/src/components/widgets/integrations'; -import { releasesWidget } from '.'; -import { githubIntegration } from '@site/docs/integrations/github'; -import { gitlabIntegration } from '@site/docs/integrations/gitlab'; -import { dockerHubIntegration } from '@site/docs/integrations/docker-hub'; -import { npmIntegration } from '@site/docs/integrations/npm'; -import { codebergIntegration } from '@site/docs/integrations/codeberg'; +import { AddingWidget } from "@site/src/components/widgets/adding"; +import { WidgetConfig } from "@site/src/components/widgets/configuration"; +import { WidgetHeader } from "@site/src/components/widgets/header"; +import { releasesWidget } from "."; - + -The releases widget enables you to track a list of repositories and see their respective latest release. -It also supports importing releases from your local docker containers. +The releases widget enables you to track a list of repositories and see their respective latest release. +It also supports importing releases from your local docker containers. ### Screenshots -import collapsibleCard from './img/collapsible.png'; -import fullCard from './img/full.png'; -import noDetailsCard from './img/no-details.png'; -import { quayIntegration } from '@site/docs/integrations/quay'; -import { gitHubContainerRegistryIntegration } from '@site/docs/integrations/github-containerregistry'; -import { linuxServerIoIntegration } from '@site/docs/integrations/linux-server-io'; +import collapsibleCard from "./img/collapsible.png"; +import fullCard from "./img/full.png"; +import noDetailsCard from "./img/no-details.png"; -
+
releases full releases collapsible releases no details
-### Supported Integrations +### Supported Providers - +GitHub, Docker Hub, GitLab, NPM, Codeberg, GitHub Container Registry, Quay, and LinuxServer.io are configured per repository in the widget. + +For GitHub Container Registry, use `owner/package` identifiers such as `homarr-labs/homarr`. +Full image references such as `ghcr.io/homarr-labs/homarr:latest` are also accepted. ### Adding the widget ### Configuration + :::note @@ -74,4 +49,3 @@ Setting the Prefix to `v`, precision to `2` and the Suffix to `-alpha`, will mat Setting the Prefix to `v`, precision to `3`, will match `v5.2.0` but not `v1.0.2-alpha` or `Beta v5.2.1`. ::: - diff --git a/apps/docs/docs/widgets/releases/index.ts b/apps/docs/docs/widgets/releases/index.ts index dbe3c1271..1f2270e82 100644 --- a/apps/docs/docs/widgets/releases/index.ts +++ b/apps/docs/docs/widgets/releases/index.ts @@ -54,16 +54,23 @@ export const releasesWidget: WidgetDefinition = { items: [ { name: "Provider", - description: "Integration to fetch the repository from.", - values: "Select from the available integrations.", + description: "Provider to fetch the repository or package releases from.", + values: "Select from the available providers.", defaultValue: "-", }, { name: "Identifier", - description: 'The identifier of the repository, e.g. "homarr-labs/homarr".', - values: "Any valid repository identifier.", + description: 'The identifier of the repository or package, e.g. "homarr-labs/homarr".', + values: + 'Any valid provider identifier. GHCR also accepts full image refs like "ghcr.io/owner/package:tag".', defaultValue: "-", }, + { + name: "Provider URL", + description: "Base URL for the provider.", + values: "Any valid URL.", + defaultValue: "Provider default", + }, { name: "Name", description: "Display name of the repository.", diff --git a/apps/docs/docs/widgets/timetable/index.mdx b/apps/docs/docs/widgets/timetable/index.mdx index fe9a1b11c..e9bdfd326 100644 --- a/apps/docs/docs/widgets/timetable/index.mdx +++ b/apps/docs/docs/widgets/timetable/index.mdx @@ -1,44 +1,40 @@ --- -title: 'Timetable' -description: 'Displays departure times for a station.' +title: "Timetable" +description: "Displays departure times for a station." hide_title: true --- -import { AddingWidget } from '@site/src/components/widgets/adding'; -import { WidgetConfig } from '@site/src/components/widgets/configuration'; -import { WidgetHeader } from '@site/src/components/widgets/header'; -import { WidgetIntegrations } from '@site/src/components/widgets/integrations'; -import { timetableWidget } from '.'; -import { searchChIntegration } from '@site/docs/integrations/search-ch'; +import { AddingWidget } from "@site/src/components/widgets/adding"; +import { WidgetConfig } from "@site/src/components/widgets/configuration"; +import { WidgetHeader } from "@site/src/components/widgets/header"; +import { timetableWidget } from "."; - - + The Timetable widget provides real-time departure times for a selected station. ### Screenshots -import trainDepartures from './img/train-departures.png'; -import streetDepartures from './img/street-departures.png'; +import trainDepartures from "./img/train-departures.png"; +import streetDepartures from "./img/street-departures.png";
- timetable-widget-train-departures - timetable-widget-bus-and-tram-departures + timetable-widget-train-departures + timetable-widget-bus-and-tram-departures
### Adding the widget -### Supported Integrations - - - ### Configuration + diff --git a/apps/docs/docs/widgets/timetable/index.ts b/apps/docs/docs/widgets/timetable/index.ts index 6f36349ee..1cefb6bcd 100644 --- a/apps/docs/docs/widgets/timetable/index.ts +++ b/apps/docs/docs/widgets/timetable/index.ts @@ -8,6 +8,12 @@ export const timetableWidget: WidgetDefinition = { path: "../../widgets/timetable", configuration: { items: [ + { + name: "Search.ch URL", + description: "Base URL for the Search.ch timetable API.", + values: "Any valid URL.", + defaultValue: "https://search.ch", + }, { name: "Station", description: "Select the station", diff --git a/apps/docs/package.json b/apps/docs/package.json index f4c7655b9..3fd82711d 100644 --- a/apps/docs/package.json +++ b/apps/docs/package.json @@ -37,7 +37,7 @@ "dayjs": "catalog:", "docusaurus-plugin-image-zoom": "^3.0.1", "leader-line-new": "^1.1.9", - "postcss": "^8.5.15", + "postcss": "^8.5.16", "posthog-docusaurus": "^2.0.5", "prism-react-renderer": "^2.4.1", "random-word-slugs": "^0.1.7", diff --git a/apps/nextjs/package.json b/apps/nextjs/package.json index cfea600e7..36bb405ad 100644 --- a/apps/nextjs/package.json +++ b/apps/nextjs/package.json @@ -59,10 +59,11 @@ "@modelcontextprotocol/sdk": "catalog:", "@scalar/api-reference-react": "catalog:", "@tabler/icons-react": "catalog:", - "@tanstack/query-persist-client-core": "catalog:", + "@tanstack/query-async-storage-persister": "catalog:", "@tanstack/react-query": "catalog:", "@tanstack/react-query-devtools": "catalog:", "@tanstack/react-query-next-experimental": "catalog:", + "@tanstack/react-query-persist-client": "catalog:", "@trpc/client": "catalog:", "@trpc/next": "catalog:", "@trpc/react-query": "catalog:", diff --git a/apps/nextjs/src/app/[locale]/_client-providers/query-cache-persister.ts b/apps/nextjs/src/app/[locale]/_client-providers/query-cache-persister.ts index 70d8581c7..1b9a011aa 100644 --- a/apps/nextjs/src/app/[locale]/_client-providers/query-cache-persister.ts +++ b/apps/nextjs/src/app/[locale]/_client-providers/query-cache-persister.ts @@ -1,49 +1,29 @@ -import type { AsyncStorage, PersistedQuery } from "@tanstack/query-persist-client-core"; -import { experimental_createQueryPersister } from "@tanstack/query-persist-client-core"; -import { parse, stringify } from "superjson"; +import { createAsyncStoragePersister } from "@tanstack/query-async-storage-persister"; import { fetchApi } from "@homarr/api/client"; -import { - getActiveQueryCacheBoardId, - isPersistableWidgetQueryKey, - queryCacheStoragePrefix, -} from "@homarr/api/query-cache"; +import { getActiveQueryCacheBoardId, queryCacheStoragePrefix } from "@homarr/api/query-cache"; const queryCacheStorage = { - async getItem(_key) { - return null; - }, - async setItem(key, value) { + getItem: (_key: string) => null as string | null, + async setItem(_key: string, value: string) { const boardId = getActiveQueryCacheBoardId(); if (!boardId) return; - - try { - await fetchApi.queryCache.setItem.mutate({ boardId, key, value }); - } catch { - // Query persistence is best-effort; failed writes should not affect widget rendering. - } + await fetchApi.queryCache.setItem.mutate({ boardId, value }).catch((error) => { + console.warn("[query-cache] persist failed", error); + }); }, - async removeItem(key) { + async removeItem(_key: string) { const boardId = getActiveQueryCacheBoardId(); if (!boardId) return; - - try { - await fetchApi.queryCache.removeItem.mutate({ boardId, key }); - } catch { - // Query persistence is best-effort; failed removals should not affect widget rendering. - } + await fetchApi.queryCache.removeItem.mutate({ boardId }).catch((error) => { + console.warn("[query-cache] remove failed", error); + }); }, -} satisfies AsyncStorage; +}; export const createWidgetQueryPersister = () => - experimental_createQueryPersister({ + createAsyncStoragePersister({ storage: typeof window === "undefined" ? undefined : queryCacheStorage, - maxAge: Number.POSITIVE_INFINITY, - prefix: queryCacheStoragePrefix, - refetchOnRestore: true, - serialize: (query) => stringify(query), - deserialize: (value) => parse(value), - filters: { - predicate: (query) => isPersistableWidgetQueryKey(query.queryKey), - }, + key: queryCacheStoragePrefix, + throttleTime: 2000, }); diff --git a/apps/nextjs/src/app/[locale]/_client-providers/trpc.tsx b/apps/nextjs/src/app/[locale]/_client-providers/trpc.tsx index d09acd8c8..7a02e9284 100644 --- a/apps/nextjs/src/app/[locale]/_client-providers/trpc.tsx +++ b/apps/nextjs/src/app/[locale]/_client-providers/trpc.tsx @@ -2,9 +2,10 @@ import type { PropsWithChildren } from "react"; import { useState } from "react"; -import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { QueryClient } from "@tanstack/react-query"; import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; import { ReactQueryStreamedHydration } from "@tanstack/react-query-next-experimental"; +import { PersistQueryClientProvider } from "@tanstack/react-query-persist-client"; import { createWSClient, httpBatchStreamLink, @@ -21,7 +22,12 @@ import { TRPCClientError } from "@trpc/client"; import type { AppRouter } from "@homarr/api"; import { clientApi } from "@homarr/api/client"; -import { queryCacheDefaultStaleTimeMs } from "@homarr/api/query-cache"; +import { + isPersistableWidgetQueryKey, + queryCacheDefaultGcTimeMs, + queryCacheDefaultRefetchIntervalMs, + queryCacheDefaultStaleTimeMs, +} from "@homarr/api/query-cache"; import { createHeadersCallbackForSource, getTrpcUrl } from "@homarr/api/shared"; import { env } from "@homarr/common/env"; import { showWarningNotification } from "@homarr/notifications"; @@ -29,7 +35,6 @@ import { showWarningNotification } from "@homarr/notifications"; import { createWidgetQueryPersister } from "./query-cache-persister"; const getWebSocketProtocol = () => { - // window is not defined on server side if (typeof window === "undefined") { return "ws"; } @@ -55,32 +60,36 @@ const wsClient = createWSClient({ }); export function TRPCReactProvider(props: PropsWithChildren) { - const [queryPersister] = useState(() => createWidgetQueryPersister()); - const [queryClient] = useState( - () => - new QueryClient({ - defaultOptions: { - queries: { - persister: queryPersister.persisterFn, - staleTime: queryCacheDefaultStaleTimeMs, - }, - mutations: { - onError(error) { - if ( - error instanceof TRPCClientError && - error.data?.code === "FORBIDDEN" && - error.message === "Mutations are disabled in demo mode" - ) { - showWarningNotification({ - title: "Demo mode", - message: "This action is disabled in demo mode.", - }); - } - }, + const [persister] = useState(() => createWidgetQueryPersister()); + const [queryClient] = useState(() => { + const client = new QueryClient({ + defaultOptions: { + queries: { + staleTime: queryCacheDefaultStaleTimeMs, + gcTime: queryCacheDefaultGcTimeMs, + refetchOnWindowFocus: false, + refetchOnReconnect: false, + retry: 3, + }, + mutations: { + onError(error) { + if ( + error instanceof TRPCClientError && + error.data?.code === "FORBIDDEN" && + error.message === "Mutations are disabled in demo mode" + ) { + showWarningNotification({ + title: "Demo mode", + message: "This action is disabled in demo mode.", + }); + } }, }, - }), - ); + }, + }); + client.setQueryDefaults([["widget"]], { refetchInterval: queryCacheDefaultRefetchIntervalMs }); + return client; + }); const [trpcClient] = useState(() => { return clientApi.createClient({ @@ -98,9 +107,6 @@ export function TRPCReactProvider(props: PropsWithChildren) { false: splitLink({ condition: ({ input }) => isNonJsonSerializable(input), true: httpLink({ - /** - * We don't want to transform the data here as we want to use form data - */ transformer: { serialize(object: unknown) { return object; @@ -115,7 +121,7 @@ export function TRPCReactProvider(props: PropsWithChildren) { false: httpBatchStreamLink({ transformer: superjson, url: getTrpcUrl(), - maxURLLength: 2083, // Suggested by tRPC: https://trpc.io/docs/client/links/httpBatchLink#setting-a-maximum-url-length + maxURLLength: 2083, headers: createHeadersCallbackForSource("nextjs-react (json)"), }), }), @@ -126,10 +132,20 @@ export function TRPCReactProvider(props: PropsWithChildren) { return ( - + + query.state.status === "success" && isPersistableWidgetQueryKey(query.queryKey), + }, + }} + > {props.children} - + ); } diff --git a/apps/nextjs/src/app/[locale]/boards/(content)/_creator.tsx b/apps/nextjs/src/app/[locale]/boards/(content)/_creator.tsx index 2144304c5..aff5e395c 100644 --- a/apps/nextjs/src/app/[locale]/boards/(content)/_creator.tsx +++ b/apps/nextjs/src/app/[locale]/boards/(content)/_creator.tsx @@ -5,10 +5,8 @@ import { TRPCError } from "@trpc/server"; // Placed here because gridstack styles are used for board content import "~/styles/gridstack.scss"; -import type { DehydratedState } from "@tanstack/react-query"; import { dehydrate, HydrationBoundary } from "@tanstack/react-query"; -import { parse } from "superjson"; - +import type { PersistedClient } from "@tanstack/react-query-persist-client"; import { getQueryClient } from "@homarr/api/server"; import { IntegrationProvider } from "@homarr/auth/client"; import { auth } from "@homarr/auth/next"; @@ -17,7 +15,7 @@ import { isNullOrWhitespace } from "@homarr/common"; import { createLogger } from "@homarr/core/infrastructure/logs"; import { ErrorWithMetadata } from "@homarr/core/infrastructure/logs/error"; import type { WidgetKind } from "@homarr/definitions"; -import { getAllQueryCacheAsync } from "@homarr/redis"; +import { getQueryCacheAsync } from "@homarr/redis"; import { getI18n } from "@homarr/translation/server"; import { prefetchForKindAsync } from "@homarr/widgets/prefetch"; @@ -118,36 +116,17 @@ export const createBoardContentPage = >( }; }; -interface PersistedEntry { - state: { data: unknown; dataUpdatedAt: number; status: string }; - queryKey: unknown[]; - queryHash: string; -} - async function QueryCacheHydration({ userId, boardId }: { userId: string; boardId: string }) { try { - const entries = await getAllQueryCacheAsync(userId, boardId); - const queries: DehydratedState["queries"] = []; + const serialized = await getQueryCacheAsync(userId, boardId); + if (!serialized) return null; - for (const serialized of Object.values(entries)) { - try { - const persisted = parse(serialized); - if (persisted.state.status !== "success") continue; + const persisted = JSON.parse(serialized) as PersistedClient; + if (!persisted?.clientState?.queries?.length) return null; - queries.push({ - state: persisted.state as DehydratedState["queries"][number]["state"], - queryKey: persisted.queryKey, - queryHash: persisted.queryHash, - }); - } catch { - // skip malformed entries - } - } - - if (queries.length === 0) return null; - - return ; - } catch { + return ; + } catch (error) { + logger.warn(new ErrorWithMetadata("Failed to hydrate query cache", { userId, boardId }, { cause: error })); return null; } } diff --git a/apps/nextjs/src/app/[locale]/manage/tools/api/page.tsx b/apps/nextjs/src/app/[locale]/manage/tools/api/page.tsx index bdd86cc7c..db8050ff1 100644 --- a/apps/nextjs/src/app/[locale]/manage/tools/api/page.tsx +++ b/apps/nextjs/src/app/[locale]/manage/tools/api/page.tsx @@ -10,6 +10,7 @@ import { extractBaseUrlFromHeaders } from "@homarr/common"; import { getScopedI18n } from "@homarr/translation/server"; import { createMetaTitle } from "~/metadata"; +import { DynamicBreadcrumb } from "~/components/navigation/dynamic-breadcrumb"; import { extractMcpTools } from "~/app/api/mcp/_extract-tools"; import { ApiKeysManagement } from "./components/api-keys"; import { ApiPageTabs } from "./components/api-page-tabs"; @@ -79,17 +80,20 @@ export default async function ApiPage() { const toolGroups = getMcpToolGroups(); return ( - - } - authenticationPanel={} - baseUrl={baseUrl} - hasApiKeys={apiKeys.length > 0} - toolGroups={toolGroups} - /> - + <> + + + } + authenticationPanel={} + baseUrl={baseUrl} + hasApiKeys={apiKeys.length > 0} + toolGroups={toolGroups} + /> + + ); } diff --git a/apps/nextjs/src/app/[locale]/manage/tools/backup/page.tsx b/apps/nextjs/src/app/[locale]/manage/tools/backup/page.tsx index 80a794b57..037c11677 100644 --- a/apps/nextjs/src/app/[locale]/manage/tools/backup/page.tsx +++ b/apps/nextjs/src/app/[locale]/manage/tools/backup/page.tsx @@ -6,6 +6,7 @@ import { dbEnv } from "@homarr/core/infrastructure/db/env"; import { getScopedI18n } from "@homarr/translation/server"; import { createMetaTitle } from "~/metadata"; +import { DynamicBreadcrumb } from "~/components/navigation/dynamic-breadcrumb"; import { BackupExportCard } from "./_components/backup-export-card"; import { BackupImportCard } from "./_components/backup-import-card"; @@ -31,10 +32,13 @@ export default async function BackupPage() { const t = await getScopedI18n("management.page.tool.backup"); return ( - - {t("title")} - - - + <> + + + {t("title")} + + + + ); } diff --git a/apps/nextjs/src/app/[locale]/manage/tools/docker/docker-table.tsx b/apps/nextjs/src/app/[locale]/manage/tools/docker/docker-table.tsx index 764f91844..43bbc3ff3 100644 --- a/apps/nextjs/src/app/[locale]/manage/tools/docker/docker-table.tsx +++ b/apps/nextjs/src/app/[locale]/manage/tools/docker/docker-table.tsx @@ -1,5 +1,6 @@ "use client"; +import { useCallback } from "react"; import { useRouter } from "next/navigation"; import type { MantineColor } from "@mantine/core"; import { ActionIcon, Avatar, Badge, Box, Button, Group, Menu, Text, Tooltip } from "@mantine/core"; @@ -156,29 +157,26 @@ interface DockerTableProps { export function DockerTable({ initialData }: DockerTableProps) { const t = useI18n(); const tDocker = useScopedI18n("docker"); - const { data, isFetching } = clientApi.docker.getContainers.useQuery(undefined, { + const { data, isFetching, refetch } = clientApi.docker.getContainers.useQuery(undefined, { initialData, refetchOnMount: false, - refetchOnWindowFocus: false, - refetchOnReconnect: false, }); const relativeTime = useTimeAgo(data?.timestamp ?? new Date()); - const utils = clientApi.useUtils(); - const { mutate, isPending } = clientApi.docker.invalidate.useMutation({ - async onSuccess() { - await utils.docker.getContainers.invalidate(); - showSuccessNotification({ - title: tDocker("action.refresh.notification.success.title"), - message: tDocker("action.refresh.notification.success.message"), - }); - }, - onError() { - showErrorNotification({ - title: tDocker("action.refresh.notification.error.title"), - message: tDocker("action.refresh.notification.error.message"), - }); - }, - }); + const mutate = useCallback(() => { + void refetch().then((result) => { + if (result.isError) { + showErrorNotification({ + title: tDocker("action.refresh.notification.error.title"), + message: tDocker("action.refresh.notification.error.message"), + }); + } else { + showSuccessNotification({ + title: tDocker("action.refresh.notification.success.title"), + message: tDocker("action.refresh.notification.success.message"), + }); + } + }); + }, [refetch, tDocker]); const containers = data?.containers ?? []; @@ -207,7 +205,12 @@ export function DockerTable({ initialData }: DockerTableProps) { mantineTableHeadCellProps: { style: { padding: "4px 8px" } }, renderRowActions: ({ row }: { row: MRT_Row }) => , renderTopToolbarCustomActions: () => ( - ), diff --git a/apps/nextjs/src/app/[locale]/manage/users/groups/[id]/layout.tsx b/apps/nextjs/src/app/[locale]/manage/users/groups/[id]/layout.tsx index fd6766417..3c8724068 100644 --- a/apps/nextjs/src/app/[locale]/manage/users/groups/[id]/layout.tsx +++ b/apps/nextjs/src/app/[locale]/manage/users/groups/[id]/layout.tsx @@ -7,6 +7,7 @@ import { getI18n, getScopedI18n } from "@homarr/translation/server"; import { Link } from "@homarr/ui"; import { ManageContainer } from "~/components/manage/manage-container"; +import { DynamicBreadcrumb } from "~/components/navigation/dynamic-breadcrumb"; import { NavigationLink } from "./_navigation"; interface LayoutProps { @@ -24,6 +25,7 @@ export default async function Layout(props: PropsWithChildren) { return ( + diff --git a/apps/nextjs/src/components/board/items/item-content.tsx b/apps/nextjs/src/components/board/items/item-content.tsx index 9f56a7d48..d869538e4 100644 --- a/apps/nextjs/src/components/board/items/item-content.tsx +++ b/apps/nextjs/src/components/board/items/item-content.tsx @@ -1,6 +1,8 @@ +import type { MutableRefObject } from "react"; +import { useRef } from "react"; import { Badge, Card } from "@mantine/core"; import { useElementSize } from "@mantine/hooks"; -import { QueryErrorResetBoundary } from "@tanstack/react-query"; +import { QueryErrorResetBoundary, useQueryClient } from "@tanstack/react-query"; import combineClasses from "clsx"; import { NoIntegrationSelectedError } from "@homarr/widgets/errors/classes"; import { ErrorBoundary } from "react-error-boundary"; @@ -16,6 +18,7 @@ import classes from "../sections/item.module.css"; import { useItemActions } from "./item-actions"; import itemContentClasses from "./item-content.module.css"; import { BoardItemMenu } from "./item-menu"; +import { WidgetContextMenu } from "./widget-context-menu"; interface BoardItemContentProps { item: SectionItem; @@ -30,30 +33,33 @@ const getOverflowFromKind = (kind: SectionItem["kind"]) => { export const BoardItemContent = ({ item }: BoardItemContentProps) => { const { ref, width, height } = useElementSize(); const board = useRequiredBoard(); + const widgetStateRef = useRef | null>(null); return ( <> - - - + + + + + {item.advancedOptions.title?.trim() && ( | null>; } const InnerContent = ({ item, ...dimensions }: InnerContentProps) => { @@ -98,12 +105,16 @@ const InnerContent = ({ item, ...dimensions }: InnerContentProps) => { updateItemOptions({ itemId: item.id, newOptions }); const widgetSupportsIntegrations = "supportedIntegrations" in definition && definition.supportedIntegrations.length >= 1; + const queryClient = useQueryClient(); return ( {({ reset }) => ( { + void queryClient.invalidateQueries({ queryKey: [["widget"]] }); + reset(); + }} fallbackRender={({ resetErrorBoundary, error }) => ( <> diff --git a/apps/nextjs/src/components/board/items/item-menu.tsx b/apps/nextjs/src/components/board/items/item-menu.tsx index 1c0c4100a..b8a136bbb 100644 --- a/apps/nextjs/src/components/board/items/item-menu.tsx +++ b/apps/nextjs/src/components/board/items/item-menu.tsx @@ -85,6 +85,7 @@ const BoardItemMenuInner = ({ offset, item, resetErrorBoundary }: BoardItemMenuP ), integrationSupport: "supportedIntegrations" in currentDefinition, settings, + itemId: item.id, appId: item.kind === "app" ? (item.options.appId as string | undefined) : undefined, }, { diff --git a/apps/nextjs/src/components/board/items/widget-context-menu.tsx b/apps/nextjs/src/components/board/items/widget-context-menu.tsx new file mode 100644 index 000000000..415151a52 --- /dev/null +++ b/apps/nextjs/src/components/board/items/widget-context-menu.tsx @@ -0,0 +1,287 @@ +"use client"; + +import type { MutableRefObject, ReactNode } from "react"; +import { useCallback, useEffect, useMemo, useState } from "react"; +import { Group, Menu, Switch, Text } from "@mantine/core"; +import { IconCopy, IconLayoutKanban, IconRefresh, IconSettings, IconTrash } from "@tabler/icons-react"; +import type { QueryClient } from "@tanstack/react-query"; +import { useQueryClient } from "@tanstack/react-query"; + +import { clientApi } from "@homarr/api/client"; +import { useSession } from "@homarr/auth/client"; +import { useRequiredBoard } from "@homarr/boards/context"; +import { useEditMode } from "@homarr/boards/edit-mode"; +import { useConfirmModal, useModalAction } from "@homarr/modals"; +import { useSettings } from "@homarr/settings"; +import { translateIfNecessary } from "@homarr/translation"; +import { useI18n, useScopedI18n } from "@homarr/translation/client"; +import type { WidgetContextMenuAction } from "@homarr/widgets"; +import { reduceWidgetOptionsWithDefaultValues, widgetImports } from "@homarr/widgets"; +import { WidgetEditModal } from "@homarr/widgets/modals"; + +import type { SectionItem } from "~/app/[locale]/boards/_types"; +import { useSectionContext } from "../sections/section-context"; +import { useItemActions } from "./item-actions"; +import { ItemMoveModal } from "./item-move-modal"; + +interface WidgetContextMenuProps { + item: SectionItem; + widgetStateRef: MutableRefObject | null>; + children: ReactNode; +} + +export const WidgetContextMenu = ({ item, widgetStateRef, children }: WidgetContextMenuProps) => { + const { data: session } = useSession(); + const [isEditMode] = useEditMode(); + const board = useRequiredBoard(); + const tItem = useScopedI18n("item"); + const tMenu = useScopedI18n("item.menu.label"); + const t = useI18n(); + const settings = useSettings(); + const { openModal } = useModalAction(WidgetEditModal); + const { openModal: openMoveModal } = useModalAction(ItemMoveModal); + const { openConfirmModal } = useConfirmModal(); + const { updateItemOptions, updateItemAdvancedOptions, updateItemIntegrations, duplicateItem, removeItem } = + useItemActions(); + const { data: integrationData, isPending } = clientApi.integration.all.useQuery(); + const { mutate: saveBoard } = clientApi.board.saveBoard.useMutation(); + const currentDefinition = useMemo(() => widgetImports[item.kind].definition, [item.kind]); + const { gridstack } = useSectionContext().refs; + const queryClient = useQueryClient(); + + const widgetQueryKey = useMemo(() => [["widget", item.kind]], [item.kind]); + const handleRefetch = useCallback(() => { + void queryClient.invalidateQueries({ queryKey: widgetQueryKey }); + }, [queryClient, widgetQueryKey]); + + const options = useMemo( + () => reduceWidgetOptionsWithDefaultValues(item.kind, settings, item.options) as Record, + [item.kind, settings, item.options], + ); + + type OptionDef = { type: string; skipContextMenu?: boolean }; + const toggleOptions = useMemo(() => { + const rawOptions = currentDefinition.createOptions(settings) as unknown as Record; + return Object.entries(rawOptions).filter(([, def]) => def.type === "switch" && !def.skipContextMenu); + }, [currentDefinition, settings]); + + const widgetContextActions = useMemo(() => { + const def = currentDefinition as Record; + if (typeof def.contextActions !== "function") return []; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const actions = (def.contextActions as any)({ + options, + setOptions: (partial: Record) => { + updateItemOptions({ itemId: item.id, newOptions: { ...options, ...partial } }); + }, + integrationIds: item.integrationIds, + context: { isEditMode, boardId: board.id, itemId: item.id }, + widgetStateRef, + }); + return (Array.isArray(actions) ? actions : []) as WidgetContextMenuAction[]; + }, [currentDefinition, options, item, updateItemOptions, isEditMode, board.id, widgetStateRef]); + + const persistBoard = useCallback( + (updatedItems: typeof board.items) => { + saveBoard({ ...board, items: updatedItems }); + }, + [board, saveBoard], + ); + + const openEditModal = useCallback(() => { + openModal( + { + kind: item.kind, + value: { + advancedOptions: item.advancedOptions, + options: item.options, + integrationIds: item.integrationIds, + }, + onSuccessfulEdit: (editResult) => { + updateItemOptions({ itemId: item.id, newOptions: editResult.options }); + updateItemAdvancedOptions({ itemId: item.id, newAdvancedOptions: editResult.advancedOptions }); + updateItemIntegrations({ itemId: item.id, newIntegrations: editResult.integrationIds }); + if (!isEditMode) { + persistBoard( + board.items.map((boardItem) => + boardItem.id !== item.id + ? boardItem + : { + ...boardItem, + options: editResult.options, + advancedOptions: editResult.advancedOptions, + integrationIds: editResult.integrationIds, + }, + ), + ); + } + }, + integrationData: (integrationData ?? []).filter( + (integration) => + "supportedIntegrations" in currentDefinition && + (currentDefinition.supportedIntegrations as string[]).some((kind) => kind === integration.kind), + ), + integrationSupport: "supportedIntegrations" in currentDefinition, + settings, + appId: item.kind === "app" ? (item.options.appId as string | undefined) : undefined, + }, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + { title: (fn: any) => `${fn("item.edit.title")} - ${fn(`widget.${item.kind}.name`)}` }, + ); + }, [ + openModal, + item, + updateItemOptions, + updateItemAdvancedOptions, + updateItemIntegrations, + integrationData, + currentDefinition, + settings, + isEditMode, + persistBoard, + board, + ]); + + const handleToggle = useCallback( + (key: string) => (checked: boolean) => { + const newOptions = { ...options, [key]: checked }; + updateItemOptions({ itemId: item.id, newOptions }); + persistBoard( + board.items.map((boardItem) => (boardItem.id !== item.id ? boardItem : { ...boardItem, options: newOptions })), + ); + }, + [options, item.id, updateItemOptions, persistBoard, board], + ); + + if (!session) return <>{children}; + + const visibleWidgetActions = widgetContextActions.filter((a) => !a.hidden); + + return ( + + {children} + + {toggleOptions.length > 0 && ( + <> + {tMenu("options")} + {toggleOptions.map(([key]) => ( + handleToggle(key)(!options[key])}> + {/* eslint-disable-next-line @typescript-eslint/no-explicit-any */} + + {String(translateIfNecessary(t, ((fn: any) => fn(`widget.${item.kind}.option.${key}.label`)) as any))} + + + + ))} + + )} + + {visibleWidgetActions.length > 0 && ( + <> + {toggleOptions.length > 0 && } + {tMenu("actions")} + {visibleWidgetActions.map((action) => { + const Icon = action.icon; + return ( + : undefined} + onClick={action.onClick} + disabled={action.disabled} + color={action.color} + > + {String(translateIfNecessary(t, action.label))} + + ); + })} + + )} + + {isEditMode && ( + <> + {(toggleOptions.length > 0 || visibleWidgetActions.length > 0) && } + } + onClick={() => { + if (!gridstack.current) return; + openMoveModal({ item, columnCount: gridstack.current.getColumn(), gridStack: gridstack.current }); + }} + > + {tItem("action.moveResize")} + + } + onClick={() => duplicateItem({ itemId: item.id })} + > + {tItem("action.duplicate")} + + + )} + + <> + {(toggleOptions.length > 0 || visibleWidgetActions.length > 0 || isEditMode) && } + } onClick={handleRefetch}> + + {tMenu("refresh")} + + + + + + } + onClick={openEditModal} + disabled={isPending} + > + {tMenu("settings")} + + + + {isEditMode && ( + <> + + } + onClick={() => { + openConfirmModal({ + title: tItem("remove.title"), + children: tItem("remove.message"), + onConfirm: () => removeItem({ itemId: item.id }), + }); + }} + > + {tItem("action.remove")} + + + )} + + + ); +}; + +const WidgetCacheAge = ({ queryClient, queryKey }: { queryClient: QueryClient; queryKey: unknown[] }) => { + const [, setTick] = useState(0); + useEffect(() => { + const id = setInterval(() => setTick((n) => n + 1), 1000); + return () => clearInterval(id); + }, []); + + const timestamps = queryClient + .getQueryCache() + .findAll({ queryKey }) + .map((q) => q.state.dataUpdatedAt) + .filter(Boolean); + if (timestamps.length === 0) return null; + + const oldest = Math.min(...timestamps); + const seconds = Math.floor((Date.now() - oldest) / 1000); + if (seconds < 5) return "just now"; + if (seconds < 60) return `${seconds}s ago`; + return `${Math.floor(seconds / 60)}m ago`; +}; diff --git a/apps/nextjs/src/components/board/sections/item.module.css b/apps/nextjs/src/components/board/sections/item.module.css index f679c62ca..a7168659e 100644 --- a/apps/nextjs/src/components/board/sections/item.module.css +++ b/apps/nextjs/src/components/board/sections/item.module.css @@ -10,3 +10,8 @@ background-color: var(--background-color) !important; border-color: var(--border-color) !important; } + +.itemCard:global(.iframe-wrapper) { + background-color: transparent !important; + border-color: transparent !important; +} diff --git a/apps/nextjs/src/components/integration/integration-grid-shared.ts b/apps/nextjs/src/components/integration/integration-grid-shared.ts index 2fc7228c8..7451af1e7 100644 --- a/apps/nextjs/src/components/integration/integration-grid-shared.ts +++ b/apps/nextjs/src/components/integration/integration-grid-shared.ts @@ -48,10 +48,8 @@ export const categoryTranslationKeys: Record = { search: "integration.category.search", mediaTranscoding: "integration.category.mediaTranscoding", networkController: "integration.category.networkController", - releasesProvider: "integration.category.releasesProvider", notifications: "integration.category.notifications", firewall: "integration.category.firewall", - timetable: "integration.category.timetable", photoService: "integration.category.photoService", notes: "integration.category.notes", mediaMonitoring: "integration.category.mediaMonitoring", diff --git a/apps/nextjs/src/components/navigation/dynamic-breadcrumb.tsx b/apps/nextjs/src/components/navigation/dynamic-breadcrumb.tsx index d200aa966..2ed6bc5f8 100644 --- a/apps/nextjs/src/components/navigation/dynamic-breadcrumb.tsx +++ b/apps/nextjs/src/components/navigation/dynamic-breadcrumb.tsx @@ -14,6 +14,8 @@ interface DynamicBreadcrumbProps { nonInteractable?: string[]; } +const categorySegments = new Set(["tools"]); + /** * Breadcrumb is client side rendered. Elements are automatically * calculated and translated using dynamic keys. @@ -60,8 +62,11 @@ export const DynamicBreadcrumb = ({ const translationKey = `${pathnameParts.slice(0, index + 1).join(".")}`; const mappedValue = dynamicMappings?.get(pathnamePart); + const isNonInteractable = + nonInteractable?.includes(pathnamePart) === true || categorySegments.has(pathnamePart); + if (mappedValue) { - if (nonInteractable?.includes(pathnamePart)) { + if (isNonInteractable) { return {mappedValue}; } return ( @@ -71,7 +76,7 @@ export const DynamicBreadcrumb = ({ ); } - if (nonInteractable?.includes(pathnamePart)) { + if (isNonInteractable) { return {t(`${translationKey}.label` as TranslationKeys)}; } diff --git a/apps/nextjs/src/versions/package-reader.ts b/apps/nextjs/src/versions/package-reader.ts index d62c97b20..bf1cd6405 100644 --- a/apps/nextjs/src/versions/package-reader.ts +++ b/apps/nextjs/src/versions/package-reader.ts @@ -35,14 +35,22 @@ export const getDependenciesAsync = async (): Promise = type DependencyCatalog = Map; const parseDependencyCatalogsAsync = async (): Promise => { const currentDir = path.dirname(fileURLToPath(import.meta.url)); - const workspaceConfigContent = await fsPromises.readFile( - path.join(currentDir, "..", "..", "..", "..", "pnpm-workspace.yaml"), - "utf-8", - ); - const workspaceConfig: unknown = parseYaml(workspaceConfigContent); - const parseResult = workspaceConfigCatalogSchema.parse(workspaceConfig); + const workspaceConfigPath = path.join(currentDir, "..", "..", "..", "..", "pnpm-workspace.yaml"); - return new Map(Object.entries(parseResult.catalog ?? {})); + try { + const workspaceConfigContent = await fsPromises.readFile(workspaceConfigPath, "utf-8"); + const workspaceConfig: unknown = parseYaml(workspaceConfigContent); + const parseResult = workspaceConfigCatalogSchema.parse(workspaceConfig); + return new Map(Object.entries(parseResult.catalog ?? {})); + } catch (error) { + // The workspace config is absent in Docker (only the runner stage is shipped), + // so a missing file is expected. Surface any other failure (e.g. malformed YAML). + if ((error as NodeJS.ErrnoException).code === "ENOENT") { + return new Map(); + } + + throw error; + } }; const workspaceConfigCatalogSchema = z.object({ diff --git a/apps/tasks/src/on-start/index.ts b/apps/tasks/src/on-start/index.ts index cb9564d1b..cc6967dce 100644 --- a/apps/tasks/src/on-start/index.ts +++ b/apps/tasks/src/on-start/index.ts @@ -1,7 +1,7 @@ -import { invalidateUpdateCheckerCacheAsync } from "./invalidate-update-checker-cache"; import { cleanupSessionsAsync } from "./session-cleanup"; +import { warmUpdateCheckerAsync } from "./warm-update-checker"; export async function onStartAsync() { await cleanupSessionsAsync(); - await invalidateUpdateCheckerCacheAsync(); + await warmUpdateCheckerAsync(); } diff --git a/apps/tasks/src/on-start/invalidate-update-checker-cache.ts b/apps/tasks/src/on-start/invalidate-update-checker-cache.ts deleted file mode 100644 index 470989dcf..000000000 --- a/apps/tasks/src/on-start/invalidate-update-checker-cache.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { createLogger } from "@homarr/core/infrastructure/logs"; -import { updateCheckerRequestHandler } from "@homarr/request-handler/update-checker"; - -const logger = createLogger({ module: "invalidateUpdateCheckerCache" }); - -/** - * Invalidates the update checker cache on startup to ensure fresh data. - * It is important as we want to avoid showing pending updates after the update to latest version. - */ -export async function invalidateUpdateCheckerCacheAsync() { - try { - const handler = updateCheckerRequestHandler.handler({}); - await handler.invalidateAsync(); - logger.debug("Update checker cache invalidated"); - } catch (error) { - logger.error(new Error("Failed to invalidate update checker cache", { cause: error })); - } -} diff --git a/apps/tasks/src/on-start/warm-update-checker.ts b/apps/tasks/src/on-start/warm-update-checker.ts new file mode 100644 index 000000000..a3b9f94a9 --- /dev/null +++ b/apps/tasks/src/on-start/warm-update-checker.ts @@ -0,0 +1,14 @@ +import { createLogger } from "@homarr/core/infrastructure/logs"; +import { updateCheckerRequestHandler } from "@homarr/request-handler/update-checker"; + +const logger = createLogger({ module: "warmUpdateChecker" }); + +export async function warmUpdateCheckerAsync() { + try { + const handler = updateCheckerRequestHandler.handler({}); + await handler.getDataAsync(); + logger.debug("Update checker warmed"); + } catch (error) { + logger.error(new Error("Failed to warm update checker", { cause: error })); + } +} diff --git a/deployments/prebuilt-debian/Dockerfile b/deployments/prebuilt-debian/Dockerfile index cfebe0a56..9d6e6af9d 100644 --- a/deployments/prebuilt-debian/Dockerfile +++ b/deployments/prebuilt-debian/Dockerfile @@ -1,4 +1,4 @@ -FROM node:24.17.0-trixie AS base +FROM node:24.18.0-trixie AS base WORKDIR /app COPY package.json . COPY pnpm-lock.yaml . diff --git a/deployments/prebuilt-debian/package.json b/deployments/prebuilt-debian/package.json index 8d59f2567..0374dd9a6 100644 --- a/deployments/prebuilt-debian/package.json +++ b/deployments/prebuilt-debian/package.json @@ -5,8 +5,8 @@ "better-sqlite3": "^12.10.0" }, "engines": { - "node": ">=24.17.0", - "pnpm": ">=11.8.0" + "node": ">=24.18.0", + "pnpm": ">=11.9.0" }, - "packageManager": "pnpm@11.8.0" + "packageManager": "pnpm@11.9.0" } diff --git a/docs/README.md b/docs/README.md index 8e37dfdc6..99aa947bf 100644 --- a/docs/README.md +++ b/docs/README.md @@ -102,20 +102,13 @@ - - Codeberg -
-

Codeberg

-
- - Coolify

Coolify

- - + + Dash.
@@ -130,13 +123,6 @@
- - Docker Hub -
-

Docker
Hub

-
- - Emby
@@ -144,27 +130,6 @@
- - Github -
-

Github

-
- - - - GitHub Container Registry -
-

GitHub
Container
Registry

-
- - - - Gitlab -
-

Gitlab

-
- - Glances
@@ -172,6 +137,13 @@
+ + Gluetun +
+

Gluetun

+
+ + Gotify
@@ -184,8 +156,8 @@

Home
Assistant

- - + + iCal
@@ -205,8 +177,8 @@

Jellyfin

- - + + Jellyseerr
@@ -221,13 +193,6 @@
- - LinuxServer.io -
-

LinuxServer.io

-
- - Navidrome
@@ -240,22 +205,15 @@

Nextcloud

- - - - NPM -
-

NPM

-
- - + + ntfy

ntfy

- - + + NZBGet
@@ -296,15 +254,15 @@

PeaNUT

- - + + Pi-hole

Pi-hole

- - + + Plex
@@ -333,13 +291,6 @@
- - Quay -
-

Quay

-
- - Radarr
@@ -361,13 +312,6 @@
- - Search.ch -
-

Search.ch

-
- - Seerr
@@ -401,15 +345,15 @@

Tdarr

- - + + Technitium DNS

Technitium
DNS

- - + + Tracearr
@@ -450,8 +394,8 @@

Unraid

- - + + Uptime Kuma
diff --git a/mise.toml b/mise.toml new file mode 100644 index 000000000..d6bd41fc8 --- /dev/null +++ b/mise.toml @@ -0,0 +1,2 @@ +[tools] +node = "24.18.0" diff --git a/package.json b/package.json index 0a6e869eb..cece828ac 100644 --- a/package.json +++ b/package.json @@ -61,10 +61,10 @@ "vitest": "catalog:" }, "engines": { - "node": ">=24.17.0", - "pnpm": ">=11.8.0" + "node": ">=24.18.0", + "pnpm": ">=11.9.0" }, - "packageManager": "pnpm@11.8.0", + "packageManager": "pnpm@11.9.0", "dependencies": { "jsonpath-plus": "catalog:" } diff --git a/packages/analytics/src/send-server-analytics.ts b/packages/analytics/src/send-server-analytics.ts index 2a961d94c..b851f86ec 100644 --- a/packages/analytics/src/send-server-analytics.ts +++ b/packages/analytics/src/send-server-analytics.ts @@ -36,8 +36,6 @@ const logger = createLogger({ module: "analytics" }); type AnalyticsResult = "sent" | "disabled" | "failed"; -// ponytail: no DB-level lock; concurrent calls may both generate an ID, but the last write wins -// and the ID stabilizes after the first successful run. Upgrade path: use DB upsert with WHERE instanceId IS NULL. const getOrCreateInstanceId = async ( analyticsSettings: Awaited>>, ): Promise => { diff --git a/packages/api/src/mcp.ts b/packages/api/src/mcp.ts index 887e878ad..b39e4a97d 100644 --- a/packages/api/src/mcp.ts +++ b/packages/api/src/mcp.ts @@ -17,6 +17,7 @@ import { healthMonitoringRouter } from "./router/widgets/health-monitoring"; import { mediaRequestsRouter } from "./router/widgets/media-requests"; import { mediaServerRouter } from "./router/widgets/media-server"; import { smartHomeRouter } from "./router/widgets/smart-home"; +import { widgetSecretsRouter } from "./router/widgets/widget-secrets"; export const mcpRouter = createTRPCRouter({ app: appRouterForApps, @@ -35,4 +36,5 @@ export const mcpRouter = createTRPCRouter({ mediaRequests: mediaRequestsRouter, mediaServer: mediaServerRouter, smartHome: smartHomeRouter, + widgetSecrets: widgetSecretsRouter, }); diff --git a/packages/api/src/query-cache.ts b/packages/api/src/query-cache.ts index 690059723..965c21aae 100644 --- a/packages/api/src/query-cache.ts +++ b/packages/api/src/query-cache.ts @@ -1,7 +1,8 @@ import type { QueryKey } from "@tanstack/react-query"; -export const queryCacheRetentionMs = 1000 * 60 * 60 * 24; -export const queryCacheDefaultStaleTimeMs = 1000 * 60 * 5; +export const queryCacheDefaultStaleTimeMs = 0; +export const queryCacheDefaultRefetchIntervalMs = 30_000; +export const queryCacheDefaultGcTimeMs = 1000 * 60 * 60 * 24; export const queryCacheMaxValueBytes = 1024 * 1024; export const queryCacheStoragePrefix = "homarr-widget-query"; diff --git a/packages/api/src/router/docker/docker-router.ts b/packages/api/src/router/docker/docker-router.ts index 0011545d0..44b4ce381 100644 --- a/packages/api/src/router/docker/docker-router.ts +++ b/packages/api/src/router/docker/docker-router.ts @@ -25,9 +25,7 @@ export const dockerRouter = createTRPCRouter({ .concat(dockerMiddleware()) .query(async () => { const innerHandler = dockerContainersRequestHandler.handler({}); - const result = await innerHandler.getCachedOrUpdatedDataAsync({ - forceUpdate: false, - }); + const result = await innerHandler.getDataAsync(); const { data, timestamp } = result; @@ -36,26 +34,6 @@ export const dockerRouter = createTRPCRouter({ timestamp, }; }), - subscribeContainers: permissionRequiredProcedure - .requiresPermission("admin") - .concat(dockerMiddleware()) - .subscription(() => { - return observable((emit) => { - const innerHandler = dockerContainersRequestHandler.handler({}); - const unsubscribe = innerHandler.subscribe((data) => { - emit.next(data); - }); - - return unsubscribe; - }); - }), - invalidate: permissionRequiredProcedure - .requiresPermission("admin") - .concat(dockerMiddleware()) - .mutation(async () => { - const innerHandler = dockerContainersRequestHandler.handler({}); - await innerHandler.invalidateAsync(); - }), startAll: permissionRequiredProcedure .requiresPermission("admin") .meta({ @@ -73,9 +51,6 @@ export const dockerRouter = createTRPCRouter({ await container.start(); }), ); - - const innerHandler = dockerContainersRequestHandler.handler({}); - await innerHandler.invalidateAsync(); }), stopAll: permissionRequiredProcedure .requiresPermission("admin") @@ -94,9 +69,6 @@ export const dockerRouter = createTRPCRouter({ await container.stop(); }), ); - - const innerHandler = dockerContainersRequestHandler.handler({}); - await innerHandler.invalidateAsync(); }), restartAll: permissionRequiredProcedure .requiresPermission("admin") @@ -116,9 +88,6 @@ export const dockerRouter = createTRPCRouter({ await container.restart(); }), ); - - const innerHandler = dockerContainersRequestHandler.handler({}); - await innerHandler.invalidateAsync(); }), removeAll: permissionRequiredProcedure .requiresPermission("admin") @@ -138,9 +107,6 @@ export const dockerRouter = createTRPCRouter({ await container.remove(); }), ); - - const innerHandler = dockerContainersRequestHandler.handler({}); - await innerHandler.invalidateAsync(); }), logs: permissionRequiredProcedure .requiresPermission("admin") diff --git a/packages/api/src/router/icons.ts b/packages/api/src/router/icons.ts index 4f0e59d33..6758dd012 100644 --- a/packages/api/src/router/icons.ts +++ b/packages/api/src/router/icons.ts @@ -30,8 +30,6 @@ export const iconsRouter = createTRPCRouter({ columns: { id: true, name: true, url: true }, where: whereCondition, orderBy: (table, { asc, sql }) => { - // ponytail: SVG first, then alphabetical; with a search term, - // prepend a relevance tier (exact > prefix > substring). const svgFirst = sql`CASE WHEN ${table.name} LIKE '%.svg' THEN 0 ELSE 1 END`; const nameAsc = asc(table.name); if (term.length === 0) { diff --git a/packages/api/src/router/query-cache.ts b/packages/api/src/router/query-cache.ts index 6143501fc..e2aa77b9b 100644 --- a/packages/api/src/router/query-cache.ts +++ b/packages/api/src/router/query-cache.ts @@ -2,53 +2,41 @@ import { z } from "zod/v4"; import { eq } from "@homarr/db"; import { boards } from "@homarr/db/schema"; -import { createQueryCacheChannel } from "@homarr/redis"; +import { removeQueryCacheAsync, setQueryCacheAsync } from "@homarr/redis"; -import { queryCacheMaxValueBytes, queryCacheRetentionMs } from "../query-cache"; +import { queryCacheDefaultGcTimeMs, queryCacheMaxValueBytes } from "../query-cache"; import { createTRPCRouter, publicProcedure } from "../trpc"; import { throwIfActionForbiddenAsync } from "./board/board-access"; -const queryCacheKeyInput = z.object({ +const queryCacheBoardInput = z.object({ boardId: z.string().min(1), - key: z.string().min(1).max(4096), }); -const queryCacheSetInput = queryCacheKeyInput.extend({ +const queryCacheSetInput = queryCacheBoardInput.extend({ value: z.string().max(queryCacheMaxValueBytes * 2), }); -const createChannel = (input: z.infer, userId: string) => - createQueryCacheChannel({ - userId, - boardId: input.boardId, - key: input.key, - ttlMs: queryCacheRetentionMs, - maxValueBytes: queryCacheMaxValueBytes, - }); - const validateBoardAccessAsync = async (ctx: Parameters[0], boardId: string) => { await throwIfActionForbiddenAsync(ctx, eq(boards.id, boardId), "view"); }; export const queryCacheRouter = createTRPCRouter({ - getItem: publicProcedure.input(queryCacheKeyInput).query(async ({ ctx, input }) => { - await validateBoardAccessAsync(ctx, input.boardId); - - const channel = createChannel(input, ctx.session?.user.id ?? "anonymous"); - return await channel.getAsync(); - }), setItem: publicProcedure.input(queryCacheSetInput).mutation(async ({ ctx, input }) => { await validateBoardAccessAsync(ctx, input.boardId); - - const channel = createChannel(input, ctx.session?.user.id ?? "anonymous"); + const userId = ctx.session?.user.id ?? "anonymous"; return { - stored: await channel.setAsync(input.value), + stored: await setQueryCacheAsync( + userId, + input.boardId, + input.value, + queryCacheDefaultGcTimeMs, + queryCacheMaxValueBytes, + ), }; }), - removeItem: publicProcedure.input(queryCacheKeyInput).mutation(async ({ ctx, input }) => { + removeItem: publicProcedure.input(queryCacheBoardInput).mutation(async ({ ctx, input }) => { await validateBoardAccessAsync(ctx, input.boardId); - - const channel = createChannel(input, ctx.session?.user.id ?? "anonymous"); - await channel.removeAsync(); + const userId = ctx.session?.user.id ?? "anonymous"; + await removeQueryCacheAsync(userId, input.boardId); }), }); diff --git a/packages/api/src/router/test/docker/docker-router.spec.ts b/packages/api/src/router/test/docker/docker-router.spec.ts index cbea4063b..4a5a32a88 100644 --- a/packages/api/src/router/test/docker/docker-router.spec.ts +++ b/packages/api/src/router/test/docker/docker-router.spec.ts @@ -23,28 +23,12 @@ vi.mock("@homarr/request-handler/docker", () => ({ }, dockerContainersRequestHandler: { handler: () => ({ - getCachedOrUpdatedDataAsync: async () => { - return await Promise.resolve({ containers: [] }); - }, - invalidateAsync: async () => { - return await Promise.resolve(); + getDataAsync: async () => { + return await Promise.resolve({ data: [], timestamp: new Date() }); }, }), }, })); -vi.mock("@homarr/redis", () => ({ - createCacheChannel: () => ({ - // eslint-disable-next-line @typescript-eslint/require-await - consumeAsync: async () => ({ - timestamp: new Date().toISOString(), - data: { containers: [] }, - }), - // eslint-disable-next-line @typescript-eslint/no-empty-function - invalidateAsync: async () => {}, - }), - createWidgetOptionsChannel: () => ({}), -})); - vi.mock("@homarr/docker/env", () => ({ env: { ENABLE_DOCKER: true, @@ -67,12 +51,10 @@ const validInputs: { [key in (typeof procedureKeys)[number]]: RouterInputs["docker"][key]; } = { getContainers: undefined, - subscribeContainers: undefined, startAll: { ids: ["1"] }, stopAll: { ids: ["1"] }, restartAll: { ids: ["1"] }, removeAll: { ids: ["1"] }, - invalidate: undefined, logs: { id: "1", tail: 200 }, subscribeLogs: { id: "1", tail: 200 }, }; diff --git a/packages/api/src/router/test/integration/integration-test-connection.spec.ts b/packages/api/src/router/test/integration/integration-test-connection.spec.ts index f2519c533..0da8356c4 100644 --- a/packages/api/src/router/test/integration/integration-test-connection.spec.ts +++ b/packages/api/src/router/test/integration/integration-test-connection.spec.ts @@ -270,78 +270,4 @@ describe("testConnectionAsync should run test connection of integration", () => externalUrl: null, }); }); - - test("with input of existing github app", async () => { - // Arrange - const factorySpy = vi.spyOn(homarrIntegrations, "createIntegrationAsync"); - const optionsSpy = vi.spyOn(homarrDefinitions, "getAllSecretKindOptions"); - factorySpy.mockReturnValue( - Promise.resolve({ - testConnectionAsync: async () => await Promise.resolve({ success: true }), - } as homarrIntegrations.PiHoleIntegrationV6), - ); - optionsSpy.mockReturnValue([[], ["githubAppId", "githubInstallationId", "privateKey"]]); - - const integration = { - id: "new", - name: "GitHub", - url: "https://api.github.com", - kind: "github" as const, - secrets: [ - { - kind: "githubAppId" as const, - value: "345", - }, - { - kind: "githubInstallationId" as const, - value: "456", - }, - { - kind: "privateKey" as const, - value: null, - }, - ], - }; - - const dbSecrets = [ - { - kind: "githubAppId" as const, - value: "123.encrypted" as const, - }, - { - kind: "githubInstallationId" as const, - value: "234.encrypted" as const, - }, - { - kind: "privateKey" as const, - value: "privateKey.encrypted" as const, - }, - ]; - - // Act - await testConnectionAsync(integration, dbSecrets); - - // Assert - expect(factorySpy).toHaveBeenCalledWith({ - id: "new", - name: "GitHub", - url: "https://api.github.com", - kind: "github" as const, - decryptedSecrets: [ - expect.objectContaining({ - kind: "githubAppId", - value: "345", - }), - expect.objectContaining({ - kind: "githubInstallationId", - value: "456", - }), - expect.objectContaining({ - kind: "privateKey", - value: "privateKey", - }), - ], - externalUrl: null, - }); - }); }); diff --git a/packages/api/src/router/update-checker.ts b/packages/api/src/router/update-checker.ts index 8957be5f2..8cb6c097e 100644 --- a/packages/api/src/router/update-checker.ts +++ b/packages/api/src/router/update-checker.ts @@ -9,7 +9,7 @@ export const updateCheckerRouter = createTRPCRouter({ getAvailableUpdates: permissionRequiredProcedure.requiresPermission("admin").query(async () => { try { const handler = updateCheckerRequestHandler.handler({}); - const data = await handler.getCachedOrUpdatedDataAsync({}); + const data = await handler.getDataAsync(); return data.data.availableUpdates; } catch (error) { logger.error(new Error("Failed to get available updates", { cause: error })); diff --git a/packages/api/src/router/widgets/anchor-notes.ts b/packages/api/src/router/widgets/anchor-notes.ts index 7e5b7043c..d431c0de9 100644 --- a/packages/api/src/router/widgets/anchor-notes.ts +++ b/packages/api/src/router/widgets/anchor-notes.ts @@ -45,9 +45,7 @@ export const anchorNotesRouter = createTRPCRouter({ .query(async ({ ctx, input }) => { const handler = anchorNotesListRequestHandler.handler(ctx.integration, { ...input, limit: input.limit ?? 50 }); - const { data } = await handler.getCachedOrUpdatedDataAsync({ - forceUpdate: false, - }); + const { data } = await handler.getDataAsync(); return data; }), @@ -57,9 +55,7 @@ export const anchorNotesRouter = createTRPCRouter({ .query(async ({ ctx, input }) => { const handler = anchorNoteRequestHandler.handler(ctx.integration, { noteId: input.noteId }); - const { data } = await handler.getCachedOrUpdatedDataAsync({ - forceUpdate: false, - }); + const { data } = await handler.getDataAsync(); return data; }), @@ -79,9 +75,6 @@ export const anchorNotesRouter = createTRPCRouter({ ...(normalizedContent !== undefined ? { content: normalizedContent } : {}), }); - const noteHandler = anchorNoteRequestHandler.handler(ctx.integration, { noteId: input.noteId }); - await noteHandler.invalidateAsync(); - return updatedNote; } catch (error) { if (error instanceof ResponseError && error.statusCode === 400) { diff --git a/packages/api/src/router/widgets/app.ts b/packages/api/src/router/widgets/app.ts index d07241725..92e1298da 100644 --- a/packages/api/src/router/widgets/app.ts +++ b/packages/api/src/router/widgets/app.ts @@ -1,10 +1,8 @@ import { TRPCError } from "@trpc/server"; -import { observable } from "@trpc/server/observable"; import { z } from "zod/v4"; import { getServerSettingByKeyAsync } from "@homarr/db/queries"; import { sendPingRequestAsync } from "@homarr/ping"; -import { pingChannel, pingUrlChannel } from "@homarr/redis"; import { createTRPCRouter, publicProcedure } from "../../trpc"; import { AppRepository } from "../app"; @@ -45,55 +43,4 @@ export const appRouter = createTRPCRouter({ ...pingResult, }; }), - updatedPing: publicProcedure - .input( - z.object({ - id: z.string(), - }), - ) - .subscription(async ({ ctx, input }) => { - const boardSettings = await getServerSettingByKeyAsync(ctx.db, "board"); - if (boardSettings.forceDisableStatus) { - throw new TRPCError({ - code: "FORBIDDEN", - message: "Ping functionality is disabled by server settings", - }); - } - - const repository = new AppRepository(ctx.db, ctx.session?.user ?? null); - const app = await repository.getByIdAsync(input.id); - - if (!app) { - throw new TRPCError({ - code: "NOT_FOUND", - message: "App not found", - }); - } - - const pingUrl = app.pingUrl ?? app.href; - - if (!pingUrl) { - throw new TRPCError({ - code: "CONFLICT", - message: "No URL to ping configured for this app", - }); - } - - await pingUrlChannel.addAsync(pingUrl); - - return observable<{ url: string; statusCode: number; durationMs: number } | { url: string; error: string }>( - (emit) => { - const unsubscribe = pingChannel.subscribe((message) => { - // Only emit if same url - if (message.url !== pingUrl) return; - emit.next(message); - }); - - return () => { - unsubscribe(); - void pingUrlChannel.removeAsync(pingUrl); - }; - }, - ); - }), }); diff --git a/packages/api/src/router/widgets/archive-team-warrior.ts b/packages/api/src/router/widgets/archive-team-warrior.ts index 0824c165a..d71b7546a 100644 --- a/packages/api/src/router/widgets/archive-team-warrior.ts +++ b/packages/api/src/router/widgets/archive-team-warrior.ts @@ -1,6 +1,3 @@ -import { observable } from "@trpc/server/observable"; - -import type { ArchiveTeamWarriorStatus } from "@homarr/integrations"; import { archiveTeamWarriorRequestHandler } from "@homarr/request-handler/archive-team-warrior"; import { createOneIntegrationMiddleware } from "../../middlewares/integration"; @@ -11,29 +8,11 @@ export const archiveTeamWarriorRouter = createTRPCRouter({ .concat(createOneIntegrationMiddleware("query", "archiveTeamWarrior")) .query(async ({ ctx }) => { const handler = archiveTeamWarriorRequestHandler.handler(ctx.integration, {}); - const { data, timestamp } = await handler.getCachedOrUpdatedDataAsync({ forceUpdate: false }); + const { data, timestamp } = await handler.getDataAsync(); return { status: data, updatedAt: timestamp, }; }), - - subscribeStatus: publicProcedure - .concat(createOneIntegrationMiddleware("query", "archiveTeamWarrior")) - .subscription(({ ctx }) => { - return observable<{ status: ArchiveTeamWarriorStatus; updatedAt: Date }>((emit) => { - const handler = archiveTeamWarriorRequestHandler.handler(ctx.integration, {}); - const unsubscribe = handler.subscribe((status) => { - emit.next({ - status, - updatedAt: new Date(), - }); - }); - - return () => { - unsubscribe(); - }; - }); - }), }); diff --git a/packages/api/src/router/widgets/audio-stats.ts b/packages/api/src/router/widgets/audio-stats.ts index d43876583..14fd7d49c 100644 --- a/packages/api/src/router/widgets/audio-stats.ts +++ b/packages/api/src/router/widgets/audio-stats.ts @@ -13,14 +13,14 @@ export const audioStatsRouter = createTRPCRouter({ const fetchByKind = { navidrome: async () => { const handler = navidromeRequestHandler.handler({ ...ctx.integration, kind: "navidrome" as const }, {}); - return (await handler.getCachedOrUpdatedDataAsync({ forceUpdate: false })).data; + return (await handler.getDataAsync()).data; }, audiobookshelf: async () => { const handler = audiobookshelfRequestHandler.handler( { ...ctx.integration, kind: "audiobookshelf" as const }, {}, ); - return (await handler.getCachedOrUpdatedDataAsync({ forceUpdate: false })).data; + return (await handler.getDataAsync()).data; }, } as const; diff --git a/packages/api/src/router/widgets/beszel.ts b/packages/api/src/router/widgets/beszel.ts index cbb453d03..797de8609 100644 --- a/packages/api/src/router/widgets/beszel.ts +++ b/packages/api/src/router/widgets/beszel.ts @@ -1,16 +1,12 @@ import { TRPCError } from "@trpc/server"; -import { observable } from "@trpc/server/observable"; import { z } from "zod/v4"; import { createLogger } from "@homarr/core/infrastructure/logs"; -import { createIntegrationAsync } from "@homarr/integrations"; -import type { BeszelContainerStatsRecord, BeszelSystemStatsRecord } from "@homarr/integrations/types"; import { beszelAlertsRequestHandler, beszelStatsRequestHandler, beszelSystemsRequestHandler, } from "@homarr/request-handler/beszel"; -import type { BeszelAlertsData, BeszelSystemRow } from "@homarr/request-handler/beszel"; import { createManyIntegrationMiddleware } from "../../middlewares/integration"; import { createTRPCRouter, publicProcedure } from "../../trpc"; @@ -33,9 +29,7 @@ export const beszelRouter = createTRPCRouter({ const settled = await Promise.allSettled( ctx.integrations.map(async (integration) => { const innerHandler = beszelSystemsRequestHandler.handler(integration, {}); - const { data, timestamp } = await innerHandler.getCachedOrUpdatedDataAsync({ - forceUpdate: false, - }); + const { data, timestamp } = await innerHandler.getDataAsync(); return { integrationId: integration.id, integrationName: integration.name, @@ -71,30 +65,6 @@ export const beszelRouter = createTRPCRouter({ return results; }), - subscribeSystems: publicProcedure - .concat(createManyIntegrationMiddleware("query", "beszel", "mock")) - .subscription(({ ctx }) => { - return observable<{ - integrationId: string; - systems: BeszelSystemRow[]; - timestamp: Date; - }>((emit) => { - const unsubscribes = ctx.integrations.map((integration) => { - const innerHandler = beszelSystemsRequestHandler.handler(integration, {}); - return innerHandler.subscribe((systems) => { - emit.next({ - integrationId: integration.id, - systems, - timestamp: new Date(), - }); - }); - }); - return () => { - unsubscribes.forEach((fn) => fn()); - }; - }); - }), - getAlerts: publicProcedure .meta({ mcp: { @@ -125,13 +95,17 @@ export const beszelRouter = createTRPCRouter({ maxHistoryItems: input.maxHistoryItems, }); const systemsHandler = beszelSystemsRequestHandler.handler(integration, {}); - const [alertsResult, systemsResult] = await Promise.all([ - alertsHandler.getCachedOrUpdatedDataAsync({ forceUpdate: false }), - systemsHandler.getCachedOrUpdatedDataAsync({ forceUpdate: false }), + const [alertsSettled, systemsSettled] = await Promise.allSettled([ + alertsHandler.getDataAsync(), + systemsHandler.getDataAsync(), ]); + if (alertsSettled.status === "rejected") throw alertsSettled.reason; + const alertsResult = alertsSettled.value; const systemNameMap: Record = {}; - for (const system of systemsResult.data) { - systemNameMap[system.id] = system.name; + if (systemsSettled.status === "fulfilled") { + for (const system of systemsSettled.value.data) { + systemNameMap[system.id] = system.name; + } } return { integrationId: integration.id, @@ -170,39 +144,6 @@ export const beszelRouter = createTRPCRouter({ return results; }), - subscribeAlerts: publicProcedure - .concat(createManyIntegrationMiddleware("query", "beszel", "mock")) - .input( - z.object({ - includeHistory: z.boolean().default(true), - maxHistoryItems: z.number().min(1).max(100).default(10), - }), - ) - .subscription(({ ctx, input }) => { - return observable<{ - integrationId: string; - alerts: BeszelAlertsData; - timestamp: Date; - }>((emit) => { - const unsubscribes = ctx.integrations.map((integration) => { - const innerHandler = beszelAlertsRequestHandler.handler(integration, { - includeHistory: input.includeHistory, - maxHistoryItems: input.maxHistoryItems, - }); - return innerHandler.subscribe((data) => { - emit.next({ - integrationId: integration.id, - alerts: data, - timestamp: new Date(), - }); - }); - }); - return () => { - unsubscribes.forEach((fn) => fn()); - }; - }); - }), - getSystemStats: publicProcedure .meta({ mcp: { @@ -237,7 +178,7 @@ export const beszelRouter = createTRPCRouter({ timePeriod: input.timePeriod, includeDocker: input.includeDocker, }); - const { data, timestamp } = await innerHandler.getCachedOrUpdatedDataAsync({ forceUpdate: false }); + const { data, timestamp } = await innerHandler.getDataAsync(); logger.debug("getSystemStats completed", { userId: ctx.session?.user?.id, integrationId: integration.id, @@ -260,61 +201,4 @@ export const beszelRouter = createTRPCRouter({ }; } }), - - subscribeSystemStats: publicProcedure - .concat(createManyIntegrationMiddleware("query", "beszel", "mock")) - .input( - z.object({ - systemId: z.string(), - }), - ) - .subscription(({ ctx, input }) => { - return observable<{ - stats: BeszelSystemStatsRecord; - containerStats: BeszelContainerStatsRecord | null; - }>((emit) => { - const integration = ctx.integrations[0]; - if (!integration) { - emit.error(new Error("No Beszel integration configured")); - return () => {}; - } - logger.debug("subscribeSystemStats opened", { - userId: ctx.session?.user?.id, - integrationId: integration.id, - systemId: input.systemId, - }); - - const abortController = new AbortController(); - - void (async () => { - try { - const instance = await createIntegrationAsync(integration); - await instance.subscribeRealtimeMetrics(input.systemId, (data) => emit.next(data), abortController.signal); - } catch (error) { - if (!abortController.signal.aborted) { - logger.warn("subscribeSystemStats error", { - userId: ctx.session?.user?.id, - integrationId: integration.id, - systemId: input.systemId, - error, - }); - if (error instanceof Error) { - emit.error(error); - } else { - emit.error(new Error(String(error))); - } - } - } - })(); - - return () => { - abortController.abort(); - logger.debug("subscribeSystemStats closed", { - userId: ctx.session?.user?.id, - integrationId: integration.id, - systemId: input.systemId, - }); - }; - }); - }), }); diff --git a/packages/api/src/router/widgets/calendar.ts b/packages/api/src/router/widgets/calendar.ts index 50ba71b3e..729e82634 100644 --- a/packages/api/src/router/widgets/calendar.ts +++ b/packages/api/src/router/widgets/calendar.ts @@ -1,13 +1,8 @@ -import { observable } from "@trpc/server/observable"; import { z } from "zod/v4"; -import type { Modify } from "@homarr/common/types"; import { createLogger } from "@homarr/core/infrastructure/logs"; import { ErrorWithMetadata } from "@homarr/core/infrastructure/logs/error"; -import type { Integration } from "@homarr/db/schema"; -import type { IntegrationKindByCategory } from "@homarr/definitions"; import { getIntegrationKindsByCategory } from "@homarr/definitions"; -import type { CalendarEvent } from "@homarr/integrations/types"; import { radarrReleaseTypes } from "@homarr/integrations/types"; import { calendarMonthRequestHandler } from "@homarr/request-handler/calendar"; @@ -39,9 +34,7 @@ export const calendarRouter = createTRPCRouter({ ctx.integrations.map(async (integration) => { const { integrationIds: _integrationIds, ...handlerInput } = input; const innerHandler = calendarMonthRequestHandler.handler(integration, handlerInput); - const { data } = await innerHandler.getCachedOrUpdatedDataAsync({ - forceUpdate: false, - }); + const { data } = await innerHandler.getDataAsync(); return { events: data, @@ -72,39 +65,4 @@ export const calendarRouter = createTRPCRouter({ return []; }); }), - subscribeToEvents: publicProcedure - .input( - z.object({ - year: z.number(), - month: z.number(), - releaseType: z.array(z.enum(radarrReleaseTypes)), - showUnmonitored: z.boolean(), - }), - ) - .concat(createManyIntegrationMiddleware("query", ...getIntegrationKindsByCategory("calendar"))) - .subscription(({ ctx, input }) => { - return observable<{ - integration: Modify }>; - events: CalendarEvent[]; - }>((emit) => { - const unsubscribes: (() => void)[] = []; - for (const integrationWithSecrets of ctx.integrations) { - const { decryptedSecrets: _, ...integration } = integrationWithSecrets; - const { integrationIds: _integrationIds, ...handlerInput } = input; - const innerHandler = calendarMonthRequestHandler.handler(integrationWithSecrets, handlerInput); - const unsubscribe = innerHandler.subscribe((events) => { - emit.next({ - integration, - events, - }); - }); - unsubscribes.push(unsubscribe); - } - return () => { - unsubscribes.forEach((unsubscribe) => { - unsubscribe(); - }); - }; - }); - }), }); diff --git a/packages/api/src/router/widgets/coolify.ts b/packages/api/src/router/widgets/coolify.ts index 8bdaa6f3e..9476039d9 100644 --- a/packages/api/src/router/widgets/coolify.ts +++ b/packages/api/src/router/widgets/coolify.ts @@ -1,49 +1,23 @@ -import { observable } from "@trpc/server/observable"; - -import type { CoolifyInstanceInfo } from "@homarr/integrations/types"; import { coolifyRequestHandler } from "@homarr/request-handler/coolify"; import { createManyIntegrationMiddleware } from "../../middlewares/integration"; +import { settleIntegrationQueries } from "../../settle-integrations"; import { createTRPCRouter, publicProcedure } from "../../trpc"; export const coolifyRouter = createTRPCRouter({ getInstancesInfo: publicProcedure .concat(createManyIntegrationMiddleware("query", "coolify")) .query(async ({ ctx }) => { - const results = await Promise.all( - ctx.integrations.map(async (integration) => { - const innerHandler = coolifyRequestHandler.handler(integration, {}); - const { data, timestamp } = await innerHandler.getCachedOrUpdatedDataAsync({ forceUpdate: false }); + return await settleIntegrationQueries(ctx.integrations, async (integration) => { + const innerHandler = coolifyRequestHandler.handler(integration, {}); + const { data, timestamp } = await innerHandler.getDataAsync(); - return { - integrationId: integration.id, - integrationName: integration.name, - integrationUrl: integration.url, - instanceInfo: data, - updatedAt: timestamp, - }; - }), - ); - - return results; - }), - subscribeInstancesInfo: publicProcedure - .concat(createManyIntegrationMiddleware("query", "coolify")) - .subscription(({ ctx }) => { - return observable<{ integrationId: string; instanceInfo: CoolifyInstanceInfo; timestamp: Date }>((emit) => { - const unsubscribes = ctx.integrations.map((integration) => { - const innerHandler = coolifyRequestHandler.handler(integration, {}); - return innerHandler.subscribe((instanceInfo) => { - emit.next({ - integrationId: integration.id, - instanceInfo, - timestamp: new Date(), - }); - }); - }); - - return () => { - unsubscribes.forEach((unsubscribe) => unsubscribe()); + return { + integrationId: integration.id, + integrationName: integration.name, + integrationUrl: integration.url, + instanceInfo: data, + updatedAt: timestamp, }; }); }), diff --git a/packages/api/src/router/widgets/dns-hole.ts b/packages/api/src/router/widgets/dns-hole.ts index 93162a1e3..32f15da98 100644 --- a/packages/api/src/router/widgets/dns-hole.ts +++ b/packages/api/src/router/widgets/dns-hole.ts @@ -1,15 +1,11 @@ -import { observable } from "@trpc/server/observable"; import { z } from "zod/v4"; -import type { Modify } from "@homarr/common/types"; -import type { Integration } from "@homarr/db/schema"; -import type { IntegrationKindByCategory } from "@homarr/definitions"; import { getIntegrationKindsByCategory } from "@homarr/definitions"; import { createIntegrationAsync } from "@homarr/integrations"; -import type { DnsHoleSummary } from "@homarr/integrations/types"; import { dnsHoleRequestHandler } from "@homarr/request-handler/dns-hole"; import { createManyIntegrationMiddleware, createOneIntegrationMiddleware } from "../../middlewares/integration"; +import { settleIntegrationQueries } from "../../settle-integrations"; import { createTRPCRouter, protectedProcedure, publicProcedure } from "../../trpc"; export const dnsHoleRouter = createTRPCRouter({ @@ -23,50 +19,11 @@ export const dnsHoleRouter = createTRPCRouter({ }) .concat(createManyIntegrationMiddleware("query", ...getIntegrationKindsByCategory("dnsHole"))) .query(async ({ ctx }) => { - const results = await Promise.all( - ctx.integrations.map(async (integration) => { - const innerHandler = dnsHoleRequestHandler.handler(integration, {}); - const { data, timestamp } = await innerHandler.getCachedOrUpdatedDataAsync({ - forceUpdate: false, - }); - - return { - integration: { - id: integration.id, - name: integration.name, - kind: integration.kind, - updatedAt: timestamp, - }, - summary: data, - }; - }), - ); - return results; - }), - - subscribeToSummary: publicProcedure - .concat(createManyIntegrationMiddleware("query", ...getIntegrationKindsByCategory("dnsHole"))) - .subscription(({ ctx }) => { - return observable<{ - integration: Modify }>; - summary: DnsHoleSummary; - }>((emit) => { - const unsubscribes: (() => void)[] = []; - for (const integrationWithSecrets of ctx.integrations) { - const { decryptedSecrets: _, ...integration } = integrationWithSecrets; - const innerHandler = dnsHoleRequestHandler.handler(integrationWithSecrets, {}); - const unsubscribe = innerHandler.subscribe((summary) => { - emit.next({ - integration, - summary, - }); - }); - unsubscribes.push(unsubscribe); - } - return () => { - unsubscribes.forEach((unsubscribe) => { - unsubscribe(); - }); + return await settleIntegrationQueries(ctx.integrations, async (integration) => { + const { data, timestamp } = await dnsHoleRequestHandler.handler(integration, {}).getDataAsync(); + return { + integration: { id: integration.id, name: integration.name, kind: integration.kind, updatedAt: timestamp }, + summary: data, }; }); }), @@ -83,12 +40,6 @@ export const dnsHoleRouter = createTRPCRouter({ .mutation(async ({ ctx: { integration } }) => { const client = await createIntegrationAsync(integration); await client.enableAsync(); - - const innerHandler = dnsHoleRequestHandler.handler(integration, {}); - // We need to wait for the integration to be enabled before invalidating the cache - await new Promise((resolve) => { - setTimeout(() => void innerHandler.invalidateAsync().then(resolve), 1000); - }); }), disable: protectedProcedure @@ -108,11 +59,5 @@ export const dnsHoleRouter = createTRPCRouter({ .mutation(async ({ ctx: { integration }, input }) => { const client = await createIntegrationAsync(integration); await client.disableAsync(input.duration); - - const innerHandler = dnsHoleRequestHandler.handler(integration, {}); - // We need to wait for the integration to be disabled before invalidating the cache - await new Promise((resolve) => { - setTimeout(() => void innerHandler.invalidateAsync().then(resolve), 1000); - }); }), }); diff --git a/packages/api/src/router/widgets/downloads.ts b/packages/api/src/router/widgets/downloads.ts index 682decdc4..50aee350e 100644 --- a/packages/api/src/router/widgets/downloads.ts +++ b/packages/api/src/router/widgets/downloads.ts @@ -1,16 +1,12 @@ -import { observable } from "@trpc/server/observable"; import { z } from "zod/v4"; -import type { Modify } from "@homarr/common/types"; -import type { Integration } from "@homarr/db/schema"; -import type { IntegrationKindByCategory } from "@homarr/definitions"; import { getIntegrationKindsByCategory } from "@homarr/definitions"; -import type { DownloadClientJobsAndStatus } from "@homarr/integrations"; import { createIntegrationAsync, downloadClientItemSchema } from "@homarr/integrations"; import { downloadClientRequestHandler } from "@homarr/request-handler/downloads"; import type { IntegrationAction } from "../../middlewares/integration"; import { createManyIntegrationMiddleware } from "../../middlewares/integration"; +import { settleIntegrationQueries } from "../../settle-integrations"; import { createTRPCRouter, protectedProcedure, publicProcedure } from "../../trpc"; const createDownloadClientIntegrationMiddleware = (action: IntegrationAction) => @@ -28,52 +24,12 @@ export const downloadsRouter = createTRPCRouter({ .concat(createDownloadClientIntegrationMiddleware("query")) .input(z.object({ limitPerIntegration: z.number().default(50) })) .query(async ({ ctx, input }) => { - return await Promise.all( - ctx.integrations.map(async (integration) => { - const innerHandler = downloadClientRequestHandler.handler(integration, { limit: input.limitPerIntegration }); - - const { data, timestamp } = await innerHandler.getCachedOrUpdatedDataAsync({ - forceUpdate: false, - }); - - return { - integration: { - id: integration.id, - name: integration.name, - kind: integration.kind, - updatedAt: timestamp, - }, - data, - }; - }), - ); - }), - subscribeToJobsAndStatuses: publicProcedure - .concat(createDownloadClientIntegrationMiddleware("query")) - .input(z.object({ limitPerIntegration: z.number().default(50) })) - .subscription(({ ctx, input }) => { - return observable<{ - integration: Modify }>; - data: DownloadClientJobsAndStatus; - }>((emit) => { - const unsubscribes: (() => void)[] = []; - for (const integrationWithSecrets of ctx.integrations) { - const { decryptedSecrets: _, ...integration } = integrationWithSecrets; - const innerHandler = downloadClientRequestHandler.handler(integrationWithSecrets, { - limit: input.limitPerIntegration, - }); - const unsubscribe = innerHandler.subscribe((data) => { - emit.next({ - integration, - data, - }); - }); - unsubscribes.push(unsubscribe); - } - return () => { - unsubscribes.forEach((unsubscribe) => { - unsubscribe(); - }); + return await settleIntegrationQueries(ctx.integrations, async (integration) => { + const innerHandler = downloadClientRequestHandler.handler(integration, { limit: input.limitPerIntegration }); + const { data, timestamp } = await innerHandler.getDataAsync(); + return { + integration: { id: integration.id, name: integration.name, kind: integration.kind, updatedAt: timestamp }, + data, }; }); }), diff --git a/packages/api/src/router/widgets/firewall.ts b/packages/api/src/router/widgets/firewall.ts index 2d582cb1f..08951da7c 100644 --- a/packages/api/src/router/widgets/firewall.ts +++ b/packages/api/src/router/widgets/firewall.ts @@ -1,15 +1,4 @@ -import { observable } from "@trpc/server/observable"; - -import type { Modify } from "@homarr/common/types"; -import type { Integration } from "@homarr/db/schema"; -import type { IntegrationKindByCategory } from "@homarr/definitions"; import { getIntegrationKindsByCategory } from "@homarr/definitions"; -import type { - FirewallCpuSummary, - FirewallInterfacesSummary, - FirewallMemorySummary, - FirewallVersionSummary, -} from "@homarr/integrations"; import { firewallCpuRequestHandler, firewallInterfacesRequestHandler, @@ -18,198 +7,31 @@ import { } from "@homarr/request-handler/firewall"; import { createManyIntegrationMiddleware } from "../../middlewares/integration"; +import { settleIntegrationQueries } from "../../settle-integrations"; import { createTRPCRouter, publicProcedure } from "../../trpc"; +const firewallMiddleware = createManyIntegrationMiddleware("query", ...getIntegrationKindsByCategory("firewall")); + +const queryFirewall = < + THandler extends { + handler: (integration: any, input: any) => { getDataAsync: () => Promise<{ data: any; timestamp: Date }> }; + }, +>( + handler: THandler, +) => + publicProcedure.concat(firewallMiddleware).query(async ({ ctx }) => + settleIntegrationQueries(ctx.integrations, async (integration) => { + const { data, timestamp } = await handler.handler(integration, {}).getDataAsync(); + return { + integration: { id: integration.id, name: integration.name, kind: integration.kind, updatedAt: timestamp }, + summary: data as Awaited["getDataAsync"]>>["data"], + }; + }), + ); + export const firewallRouter = createTRPCRouter({ - getFirewallCpuStatus: publicProcedure - .concat(createManyIntegrationMiddleware("query", ...getIntegrationKindsByCategory("firewall"))) - .query(async ({ ctx }) => { - const results = await Promise.all( - ctx.integrations.map(async (integration) => { - const innerHandler = firewallCpuRequestHandler.handler(integration, {}); - const { data, timestamp } = await innerHandler.getCachedOrUpdatedDataAsync({ forceUpdate: false }); - - return { - integration: { - id: integration.id, - name: integration.name, - kind: integration.kind, - updatedAt: timestamp, - }, - summary: data, - }; - }), - ); - return results; - }), - subscribeFirewallCpuStatus: publicProcedure - .concat(createManyIntegrationMiddleware("query", ...getIntegrationKindsByCategory("firewall"))) - .subscription(({ ctx }) => { - return observable<{ - integration: Modify }>; - summary: FirewallCpuSummary; - }>((emit) => { - const unsubscribes: (() => void)[] = []; - for (const integrationWithSecrets of ctx.integrations) { - const { decryptedSecrets: _, ...integration } = integrationWithSecrets; - const innerHandler = firewallCpuRequestHandler.handler(integrationWithSecrets, {}); - const unsubscribe = innerHandler.subscribe((summary) => { - emit.next({ - integration, - summary, - }); - }); - unsubscribes.push(unsubscribe); - } - return () => { - unsubscribes.forEach((unsubscribe) => { - unsubscribe(); - }); - }; - }); - }), - - getFirewallInterfacesStatus: publicProcedure - .concat(createManyIntegrationMiddleware("query", ...getIntegrationKindsByCategory("firewall"))) - .query(async ({ ctx }) => { - const results = await Promise.all( - ctx.integrations.map(async (integration) => { - const innerHandler = firewallInterfacesRequestHandler.handler(integration, {}); - const { data, timestamp } = await innerHandler.getCachedOrUpdatedDataAsync({ forceUpdate: false }); - - return { - integration: { - id: integration.id, - name: integration.name, - kind: integration.kind, - updatedAt: timestamp, - }, - summary: data, - }; - }), - ); - return results; - }), - subscribeFirewallInterfacesStatus: publicProcedure - .concat(createManyIntegrationMiddleware("query", ...getIntegrationKindsByCategory("firewall"))) - .subscription(({ ctx }) => { - return observable<{ - integration: Modify }>; - summary: FirewallInterfacesSummary[]; - }>((emit) => { - const unsubscribes: (() => void)[] = []; - for (const integrationWithSecrets of ctx.integrations) { - const { decryptedSecrets: _, ...integration } = integrationWithSecrets; - const innerHandler = firewallInterfacesRequestHandler.handler(integrationWithSecrets, {}); - const unsubscribe = innerHandler.subscribe((summary) => { - emit.next({ - integration, - summary, - }); - }); - unsubscribes.push(unsubscribe); - } - return () => { - unsubscribes.forEach((unsubscribe) => { - unsubscribe(); - }); - }; - }); - }), - - getFirewallVersionStatus: publicProcedure - .concat(createManyIntegrationMiddleware("query", ...getIntegrationKindsByCategory("firewall"))) - .query(async ({ ctx }) => { - const results = await Promise.all( - ctx.integrations.map(async (integration) => { - const innerHandler = firewallVersionRequestHandler.handler(integration, {}); - const { data, timestamp } = await innerHandler.getCachedOrUpdatedDataAsync({ forceUpdate: false }); - - return { - integration: { - id: integration.id, - name: integration.name, - kind: integration.kind, - updatedAt: timestamp, - }, - summary: data, - }; - }), - ); - return results; - }), - subscribeFirewallVersionStatus: publicProcedure - .concat(createManyIntegrationMiddleware("query", ...getIntegrationKindsByCategory("firewall"))) - .subscription(({ ctx }) => { - return observable<{ - integration: Modify }>; - summary: FirewallVersionSummary; - }>((emit) => { - const unsubscribes: (() => void)[] = []; - for (const integrationWithSecrets of ctx.integrations) { - const { decryptedSecrets: _, ...integration } = integrationWithSecrets; - const innerHandler = firewallVersionRequestHandler.handler(integrationWithSecrets, {}); - const unsubscribe = innerHandler.subscribe((summary) => { - emit.next({ - integration, - summary, - }); - }); - unsubscribes.push(unsubscribe); - } - return () => { - unsubscribes.forEach((unsubscribe) => { - unsubscribe(); - }); - }; - }); - }), - - getFirewallMemoryStatus: publicProcedure - .concat(createManyIntegrationMiddleware("query", ...getIntegrationKindsByCategory("firewall"))) - .query(async ({ ctx }) => { - const results = await Promise.all( - ctx.integrations.map(async (integration) => { - const innerHandler = firewallMemoryRequestHandler.handler(integration, {}); - const { data, timestamp } = await innerHandler.getCachedOrUpdatedDataAsync({ forceUpdate: false }); - - return { - integration: { - id: integration.id, - name: integration.name, - kind: integration.kind, - updatedAt: timestamp, - }, - summary: data, - }; - }), - ); - return results; - }), - subscribeFirewallMemoryStatus: publicProcedure - .concat(createManyIntegrationMiddleware("query", ...getIntegrationKindsByCategory("firewall"))) - .subscription(({ ctx }) => { - return observable<{ - integration: Modify }>; - summary: FirewallMemorySummary; - }>((emit) => { - const unsubscribes: (() => void)[] = []; - for (const integrationWithSecrets of ctx.integrations) { - const { decryptedSecrets: _, ...integration } = integrationWithSecrets; - const innerHandler = firewallMemoryRequestHandler.handler(integrationWithSecrets, {}); - const unsubscribe = innerHandler.subscribe((summary) => { - emit.next({ - integration, - summary, - }); - }); - unsubscribes.push(unsubscribe); - } - return () => { - unsubscribes.forEach((unsubscribe) => { - unsubscribe(); - }); - }; - }); - }), + getFirewallCpuStatus: queryFirewall(firewallCpuRequestHandler), + getFirewallInterfacesStatus: queryFirewall(firewallInterfacesRequestHandler), + getFirewallVersionStatus: queryFirewall(firewallVersionRequestHandler), + getFirewallMemoryStatus: queryFirewall(firewallMemoryRequestHandler), }); diff --git a/packages/api/src/router/widgets/health-monitoring.ts b/packages/api/src/router/widgets/health-monitoring.ts index 03856f5a0..de49a1083 100644 --- a/packages/api/src/router/widgets/health-monitoring.ts +++ b/packages/api/src/router/widgets/health-monitoring.ts @@ -1,10 +1,7 @@ -import { observable } from "@trpc/server/observable"; - -import type { SystemHealthMonitoring } from "@homarr/integrations"; -import type { ProxmoxClusterInfo } from "@homarr/integrations/types"; import { clusterInfoRequestHandler, systemInfoRequestHandler } from "@homarr/request-handler/health-monitoring"; import { createManyIntegrationMiddleware, createOneIntegrationMiddleware } from "../../middlewares/integration"; +import { settleIntegrationQueries } from "../../settle-integrations"; import { createTRPCRouter, publicProcedure } from "../../trpc"; export const healthMonitoringRouter = createTRPCRouter({ @@ -20,48 +17,13 @@ export const healthMonitoringRouter = createTRPCRouter({ createManyIntegrationMiddleware("query", "openmediavault", "dashDot", "truenas", "unraid", "glances", "mock"), ) .query(async ({ ctx }) => { - return await Promise.all( - ctx.integrations.map(async (integration) => { - const innerHandler = systemInfoRequestHandler.handler(integration, {}); - const { data, timestamp } = await innerHandler.getCachedOrUpdatedDataAsync({ - forceUpdate: false, - }); - - return { - integrationId: integration.id, - integrationName: integration.name, - healthInfo: data, - updatedAt: timestamp, - }; - }), - ); - }), - subscribeSystemHealthStatus: publicProcedure - .concat( - createManyIntegrationMiddleware("query", "openmediavault", "dashDot", "truenas", "unraid", "glances", "mock"), - ) - .subscription(({ ctx }) => { - return observable<{ - integrationId: string; - healthInfo: SystemHealthMonitoring; - timestamp: Date; - }>((emit) => { - const unsubscribes: (() => void)[] = []; - for (const integration of ctx.integrations) { - const innerHandler = systemInfoRequestHandler.handler(integration, {}); - const unsubscribe = innerHandler.subscribe((healthInfo) => { - emit.next({ - integrationId: integration.id, - healthInfo, - timestamp: new Date(), - }); - }); - unsubscribes.push(unsubscribe); - } - return () => { - unsubscribes.forEach((unsubscribe) => { - unsubscribe(); - }); + return await settleIntegrationQueries(ctx.integrations, async (integration) => { + const { data, timestamp } = await systemInfoRequestHandler.handler(integration, {}).getDataAsync(); + return { + integrationId: integration.id, + integrationName: integration.name, + healthInfo: data, + updatedAt: timestamp, }; }); }), @@ -76,24 +38,7 @@ export const healthMonitoringRouter = createTRPCRouter({ .concat(createOneIntegrationMiddleware("query", "proxmox", "mock")) .query(async ({ ctx }) => { const innerHandler = clusterInfoRequestHandler.handler(ctx.integration, {}); - const { data } = await innerHandler.getCachedOrUpdatedDataAsync({ - forceUpdate: false, - }); + const { data } = await innerHandler.getDataAsync(); return data; }), - subscribeClusterHealthStatus: publicProcedure - .concat(createOneIntegrationMiddleware("query", "proxmox", "mock")) - .subscription(({ ctx }) => { - return observable((emit) => { - const unsubscribes: (() => void)[] = []; - const innerHandler = clusterInfoRequestHandler.handler(ctx.integration, {}); - const unsubscribe = innerHandler.subscribe((healthInfo) => { - emit.next(healthInfo); - }); - unsubscribes.push(unsubscribe); - return () => { - unsubscribe(); - }; - }); - }), }); diff --git a/packages/api/src/router/widgets/immich.ts b/packages/api/src/router/widgets/immich.ts index f1428a80c..7e8c54d49 100644 --- a/packages/api/src/router/widgets/immich.ts +++ b/packages/api/src/router/widgets/immich.ts @@ -17,7 +17,7 @@ const createImmichIntegrationMiddleware = (action: IntegrationAction) => export const immichRouter = createTRPCRouter({ getServerStats: publicProcedure.concat(createImmichIntegrationMiddleware("query")).query(async ({ ctx }) => { const innerHandler = immichStatsRequestHandler.handler(ctx.integration, {}); - const data = await innerHandler.getCachedOrUpdatedDataAsync({ forceUpdate: false }); + const data = await innerHandler.getDataAsync(); return data.data; }), @@ -30,13 +30,13 @@ export const immichRouter = createTRPCRouter({ ) .query(async ({ ctx, input }) => { const innerHandler = immichAlbumRequestHandler.handler(ctx.integration, { albumId: input.albumId }); - const data = await innerHandler.getCachedOrUpdatedDataAsync({ forceUpdate: false }); + const data = await innerHandler.getDataAsync(); return data.data; }), getAlbums: publicProcedure.concat(createImmichIntegrationMiddleware("query")).query(async ({ ctx }) => { const innerHandler = immichAlbumsRequestHandler.handler(ctx.integration, {}); - const data = await innerHandler.getCachedOrUpdatedDataAsync({ forceUpdate: false }); + const data = await innerHandler.getDataAsync(); return data.data; }), }); diff --git a/packages/api/src/router/widgets/index.ts b/packages/api/src/router/widgets/index.ts index 80776db45..046f422fe 100644 --- a/packages/api/src/router/widgets/index.ts +++ b/packages/api/src/router/widgets/index.ts @@ -39,4 +39,5 @@ export const widgetRouter = createTRPCRouter({ vpn: lazy(() => import("./vpn").then((mod) => mod.vpnRouter)), ups: lazy(() => import("./ups").then((mod) => mod.upsRouter)), customApi: lazy(() => import("./custom-api").then((mod) => mod.customApiRouter)), + secrets: lazy(() => import("./widget-secrets").then((mod) => mod.widgetSecretsRouter)), }); diff --git a/packages/api/src/router/widgets/indexer-manager.ts b/packages/api/src/router/widgets/indexer-manager.ts index cb8b88262..54a949dab 100644 --- a/packages/api/src/router/widgets/indexer-manager.ts +++ b/packages/api/src/router/widgets/indexer-manager.ts @@ -1,13 +1,12 @@ import { TRPCError } from "@trpc/server"; -import { observable } from "@trpc/server/observable"; import { getIntegrationKindsByCategory } from "@homarr/definitions"; import { createIntegrationAsync } from "@homarr/integrations"; -import type { Indexer } from "@homarr/integrations/types"; import { indexerManagerRequestHandler } from "@homarr/request-handler/indexer-manager"; import type { IntegrationAction } from "../../middlewares/integration"; import { createManyIntegrationMiddleware } from "../../middlewares/integration"; +import { settleIntegrationQueries } from "../../settle-integrations"; import { createTRPCRouter, protectedProcedure, publicProcedure } from "../../trpc"; const createIndexerManagerIntegrationMiddleware = (action: IntegrationAction) => @@ -17,39 +16,13 @@ export const indexerManagerRouter = createTRPCRouter({ getIndexersStatus: publicProcedure .concat(createIndexerManagerIntegrationMiddleware("query")) .query(async ({ ctx }) => { - const results = await Promise.all( - ctx.integrations.map(async (integration) => { - const innerHandler = indexerManagerRequestHandler.handler(integration, {}); - const { data: indexers } = await innerHandler.getCachedOrUpdatedDataAsync({ forceUpdate: false }); + return await settleIntegrationQueries(ctx.integrations, async (integration) => { + const innerHandler = indexerManagerRequestHandler.handler(integration, {}); + const { data: indexers } = await innerHandler.getDataAsync(); - return { - integrationId: integration.id, - indexers, - }; - }), - ); - return results; - }), - - subscribeIndexersStatus: publicProcedure - .concat(createIndexerManagerIntegrationMiddleware("query")) - .subscription(({ ctx }) => { - return observable<{ integrationId: string; indexers: Indexer[] }>((emit) => { - const unsubscribes: (() => void)[] = []; - for (const integrationWithSecrets of ctx.integrations) { - const innerHandler = indexerManagerRequestHandler.handler(integrationWithSecrets, {}); - const unsubscribe = innerHandler.subscribe((indexers) => { - emit.next({ - integrationId: integrationWithSecrets.id, - indexers, - }); - }); - unsubscribes.push(unsubscribe); - } - return () => { - unsubscribes.forEach((unsubscribe) => { - unsubscribe(); - }); + return { + integrationId: integration.id, + indexers, }; }); }), diff --git a/packages/api/src/router/widgets/media-release.ts b/packages/api/src/router/widgets/media-release.ts index fddc174cc..f05a46142 100644 --- a/packages/api/src/router/widgets/media-release.ts +++ b/packages/api/src/router/widgets/media-release.ts @@ -1,35 +1,28 @@ -import { observable } from "@trpc/server/observable"; - -import type { Modify } from "@homarr/common/types"; -import type { Integration } from "@homarr/db/schema"; -import type { IntegrationKindByCategory } from "@homarr/definitions"; import { getIntegrationKindsByCategory } from "@homarr/definitions"; -import type { MediaRelease } from "@homarr/integrations/types"; import { mediaReleaseRequestHandler } from "@homarr/request-handler/media-release"; import { createManyIntegrationMiddleware } from "../../middlewares/integration"; +import { settleIntegrationQueries } from "../../settle-integrations"; import { createTRPCRouter, publicProcedure } from "../../trpc"; export const mediaReleaseRouter = createTRPCRouter({ getMediaReleases: publicProcedure .concat(createManyIntegrationMiddleware("query", ...getIntegrationKindsByCategory("mediaRelease"))) .query(async ({ ctx }) => { - const results = await Promise.all( - ctx.integrations.map(async (integration) => { - const innerHandler = mediaReleaseRequestHandler.handler(integration, {}); - const { data, timestamp } = await innerHandler.getCachedOrUpdatedDataAsync({ forceUpdate: false }); + const results = await settleIntegrationQueries(ctx.integrations, async (integration) => { + const innerHandler = mediaReleaseRequestHandler.handler(integration, {}); + const { data, timestamp } = await innerHandler.getDataAsync(); - return { - integration: { - id: integration.id, - name: integration.name, - kind: integration.kind, - updatedAt: timestamp, - }, - releases: data, - }; - }), - ); + return { + integration: { + id: integration.id, + name: integration.name, + kind: integration.kind, + updatedAt: timestamp, + }, + releases: data, + }; + }); return results.flatMap((result) => result.releases.map((release) => ({ ...release, @@ -37,31 +30,4 @@ export const mediaReleaseRouter = createTRPCRouter({ })), ); }), - - subscribeToReleases: publicProcedure - .concat(createManyIntegrationMiddleware("query", ...getIntegrationKindsByCategory("mediaRelease"))) - .subscription(({ ctx }) => { - return observable<{ - integration: Modify }>; - releases: MediaRelease[]; - }>((emit) => { - const unsubscribes: (() => void)[] = []; - for (const integrationWithSecrets of ctx.integrations) { - const { decryptedSecrets: _, ...integration } = integrationWithSecrets; - const innerHandler = mediaReleaseRequestHandler.handler(integrationWithSecrets, {}); - const unsubscribe = innerHandler.subscribe((releases) => { - emit.next({ - integration, - releases, - }); - }); - unsubscribes.push(unsubscribe); - } - return () => { - unsubscribes.forEach((unsubscribe) => { - unsubscribe(); - }); - }; - }); - }), }); diff --git a/packages/api/src/router/widgets/media-requests.ts b/packages/api/src/router/widgets/media-requests.ts index 715ae1327..99c4906e3 100644 --- a/packages/api/src/router/widgets/media-requests.ts +++ b/packages/api/src/router/widgets/media-requests.ts @@ -1,14 +1,13 @@ -import { observable } from "@trpc/server/observable"; import { z } from "zod/v4"; import { getIntegrationKindsByCategory } from "@homarr/definitions"; import { createIntegrationAsync } from "@homarr/integrations"; import { mediaRequestStatusConfiguration } from "@homarr/integrations/types"; -import type { MediaRequest } from "@homarr/integrations/types"; import { mediaRequestListRequestHandler } from "@homarr/request-handler/media-request-list"; import { mediaRequestStatsRequestHandler } from "@homarr/request-handler/media-request-stats"; import { createManyIntegrationMiddleware, createOneIntegrationMiddleware } from "../../middlewares/integration"; +import { settleIntegrationQueries } from "../../settle-integrations"; import { createTRPCRouter, protectedProcedure, publicProcedure } from "../../trpc"; export const mediaRequestsRouter = createTRPCRouter({ @@ -22,22 +21,10 @@ export const mediaRequestsRouter = createTRPCRouter({ }) .concat(createManyIntegrationMiddleware("query", ...getIntegrationKindsByCategory("mediaRequest"))) .query(async ({ ctx }) => { - const results = await Promise.all( - ctx.integrations.map(async (integration) => { - const innerHandler = mediaRequestListRequestHandler.handler(integration, {}); - const { data } = await innerHandler.getCachedOrUpdatedDataAsync({ - forceUpdate: false, - }); - return { - integration: { - id: integration.id, - name: integration.name, - kind: integration.kind, - }, - data, - }; - }), - ); + const results = await settleIntegrationQueries(ctx.integrations, async (integration) => { + const { data } = await mediaRequestListRequestHandler.handler(integration, {}).getDataAsync(); + return { integration: { id: integration.id, name: integration.name, kind: integration.kind }, data }; + }); return results .flatMap(({ data, integration }) => data.map((request) => ({ @@ -56,32 +43,6 @@ export const mediaRequestsRouter = createTRPCRouter({ ); }); }), - subscribeToLatestRequests: publicProcedure - .concat(createManyIntegrationMiddleware("query", ...getIntegrationKindsByCategory("mediaRequest"))) - .subscription(({ ctx }) => { - return observable<{ - integrationId: string; - requests: MediaRequest[]; - }>((emit) => { - const unsubscribes: (() => void)[] = []; - for (const integrationWithSecrets of ctx.integrations) { - const { decryptedSecrets: _, ...integration } = integrationWithSecrets; - const innerHandler = mediaRequestListRequestHandler.handler(integrationWithSecrets, {}); - const unsubscribe = innerHandler.subscribe((requests) => { - emit.next({ - integrationId: integration.id, - requests, - }); - }); - unsubscribes.push(unsubscribe); - } - return () => { - unsubscribes.forEach((unsubscribe) => { - unsubscribe(); - }); - }; - }); - }), getStats: publicProcedure .meta({ mcp: { @@ -92,22 +53,10 @@ export const mediaRequestsRouter = createTRPCRouter({ }) .concat(createManyIntegrationMiddleware("query", ...getIntegrationKindsByCategory("mediaRequest"))) .query(async ({ ctx }) => { - const results = await Promise.all( - ctx.integrations.map(async (integration) => { - const innerHandler = mediaRequestStatsRequestHandler.handler(integration, {}); - const { data } = await innerHandler.getCachedOrUpdatedDataAsync({ - forceUpdate: false, - }); - return { - integration: { - id: integration.id, - name: integration.name, - kind: integration.kind, - }, - data, - }; - }), - ); + const results = await settleIntegrationQueries(ctx.integrations, async (integration) => { + const { data } = await mediaRequestStatsRequestHandler.handler(integration, {}).getDataAsync(); + return { integration: { id: integration.id, name: integration.name, kind: integration.kind }, data }; + }); return { stats: results.flatMap((result) => result.data.stats), users: results @@ -139,7 +88,6 @@ export const mediaRequestsRouter = createTRPCRouter({ ) .mutation(async ({ ctx: { integration }, input }) => { const integrationInstance = await createIntegrationAsync(integration); - const innerHandler = mediaRequestListRequestHandler.handler(integration, {}); const answerActions = { approve: (id: number) => integrationInstance.approveRequestAsync(id), @@ -147,6 +95,5 @@ export const mediaRequestsRouter = createTRPCRouter({ } as const; await answerActions[input.answer](input.requestId); - await innerHandler.invalidateAsync(); }), }); diff --git a/packages/api/src/router/widgets/media-server.ts b/packages/api/src/router/widgets/media-server.ts index de61fef75..3e3e64a4f 100644 --- a/packages/api/src/router/widgets/media-server.ts +++ b/packages/api/src/router/widgets/media-server.ts @@ -1,12 +1,11 @@ -import { observable } from "@trpc/server/observable"; import { z } from "zod/v4"; import { getIntegrationKindsByCategory } from "@homarr/definitions"; -import type { StreamSession } from "@homarr/integrations"; import { mediaServerRequestHandler } from "@homarr/request-handler/media-server"; import type { IntegrationAction } from "../../middlewares/integration"; import { createManyIntegrationMiddleware } from "../../middlewares/integration"; +import { settleIntegrationQueries } from "../../settle-integrations"; import { createTRPCRouter, publicProcedure } from "../../trpc"; const createMediaServerIntegrationMiddleware = (action: IntegrationAction) => @@ -24,46 +23,11 @@ export const mediaServerRouter = createTRPCRouter({ .concat(createMediaServerIntegrationMiddleware("query")) .input(z.object({ showOnlyPlaying: z.boolean() })) .query(async ({ ctx, input }) => { - return await Promise.all( - ctx.integrations.map(async (integration) => { - const innerHandler = mediaServerRequestHandler.handler(integration, { - showOnlyPlaying: input.showOnlyPlaying, - }); - const { data } = await innerHandler.getCachedOrUpdatedDataAsync({ - forceUpdate: false, - }); - return { - integrationId: integration.id, - integrationKind: integration.kind, - sessions: data, - }; - }), - ); - }), - subscribeToCurrentStreams: publicProcedure - .concat(createMediaServerIntegrationMiddleware("query")) - .input(z.object({ showOnlyPlaying: z.boolean() })) - .subscription(({ ctx, input }) => { - return observable<{ integrationId: string; data: StreamSession[] }>((emit) => { - const unsubscribes: (() => void)[] = []; - for (const integration of ctx.integrations) { - const innerHandler = mediaServerRequestHandler.handler(integration, { - showOnlyPlaying: input.showOnlyPlaying, - }); - - const unsubscribe = innerHandler.subscribe((sessions) => { - emit.next({ - integrationId: integration.id, - data: sessions, - }); - }); - unsubscribes.push(unsubscribe); - } - return () => { - unsubscribes.forEach((unsubscribe) => { - unsubscribe(); - }); - }; + return await settleIntegrationQueries(ctx.integrations, async (integration) => { + const { data } = await mediaServerRequestHandler + .handler(integration, { showOnlyPlaying: input.showOnlyPlaying }) + .getDataAsync(); + return { integrationId: integration.id, integrationKind: integration.kind, sessions: data }; }); }), }); diff --git a/packages/api/src/router/widgets/media-transcoding.ts b/packages/api/src/router/widgets/media-transcoding.ts index b02f0a714..88bd91704 100644 --- a/packages/api/src/router/widgets/media-transcoding.ts +++ b/packages/api/src/router/widgets/media-transcoding.ts @@ -1,7 +1,4 @@ -import { observable } from "@trpc/server/observable"; - import { getIntegrationKindsByCategory } from "@homarr/definitions"; -import type { MediaTranscoding } from "@homarr/request-handler/media-transcoding"; import { mediaTranscodingRequestHandler } from "@homarr/request-handler/media-transcoding"; import { paginatedSchema } from "@homarr/validation/common"; @@ -21,26 +18,11 @@ export const mediaTranscodingRouter = createTRPCRouter({ pageOffset: (input.page - 1) * input.pageSize, pageSize: input.pageSize, }); - const { data } = await innerHandler.getCachedOrUpdatedDataAsync({ forceUpdate: false }); + const { data } = await innerHandler.getDataAsync(); return { integrationId: ctx.integration.id, data, }; }), - subscribeData: publicProcedure - .concat(createIndexerManagerIntegrationMiddleware("query")) - .input(paginatedSchema.pick({ page: true, pageSize: true })) - .subscription(({ ctx, input }) => { - return observable<{ integrationId: string; data: MediaTranscoding }>((emit) => { - const innerHandler = mediaTranscodingRequestHandler.handler(ctx.integration, { - pageOffset: (input.page - 1) * input.pageSize, - pageSize: input.pageSize, - }); - const unsubscribe = innerHandler.subscribe((data) => { - emit.next({ integrationId: input.integrationId, data }); - }); - return unsubscribe; - }); - }), }); diff --git a/packages/api/src/router/widgets/minecraft.ts b/packages/api/src/router/widgets/minecraft.ts index 7f646c3bc..9cc5e25d7 100644 --- a/packages/api/src/router/widgets/minecraft.ts +++ b/packages/api/src/router/widgets/minecraft.ts @@ -1,7 +1,5 @@ -import { observable } from "@trpc/server/observable"; import { z } from "zod/v4"; -import type { MinecraftServerStatus } from "@homarr/request-handler/minecraft-server-status"; import { minecraftServerStatusRequestHandler } from "@homarr/request-handler/minecraft-server-status"; import { createTRPCRouter, publicProcedure } from "../../trpc"; @@ -16,21 +14,6 @@ export const minecraftRouter = createTRPCRouter({ isBedrockServer: input.isBedrockServer, domain: input.domain, }); - return await innerHandler.getCachedOrUpdatedDataAsync({ forceUpdate: true }); - }), - subscribeServerStatus: publicProcedure.input(serverStatusInputSchema).subscription(({ input }) => { - return observable((emit) => { - const innerHandler = minecraftServerStatusRequestHandler.handler({ - isBedrockServer: input.isBedrockServer, - domain: input.domain, - }); - const unsubscribe = innerHandler.subscribe((data) => { - emit.next(data); - }); - - return () => { - unsubscribe(); - }; - }); + return await innerHandler.getDataAsync(); }), }); diff --git a/packages/api/src/router/widgets/network-controller.ts b/packages/api/src/router/widgets/network-controller.ts index 053bd0dcb..61257d1ec 100644 --- a/packages/api/src/router/widgets/network-controller.ts +++ b/packages/api/src/router/widgets/network-controller.ts @@ -1,61 +1,26 @@ -import { observable } from "@trpc/server/observable"; - -import type { Modify } from "@homarr/common/types"; -import type { Integration } from "@homarr/db/schema"; -import type { IntegrationKindByCategory } from "@homarr/definitions"; import { getIntegrationKindsByCategory } from "@homarr/definitions"; -import type { NetworkControllerSummary } from "@homarr/integrations/types"; import { networkControllerRequestHandler } from "@homarr/request-handler/network-controller"; import { createManyIntegrationMiddleware } from "../../middlewares/integration"; +import { settleIntegrationQueries } from "../../settle-integrations"; import { createTRPCRouter, publicProcedure } from "../../trpc"; export const networkControllerRouter = createTRPCRouter({ summary: publicProcedure .concat(createManyIntegrationMiddleware("query", ...getIntegrationKindsByCategory("networkController"))) .query(async ({ ctx }) => { - const results = await Promise.all( - ctx.integrations.map(async (integration) => { - const innerHandler = networkControllerRequestHandler.handler(integration, {}); - const { data, timestamp } = await innerHandler.getCachedOrUpdatedDataAsync({ forceUpdate: false }); + return await settleIntegrationQueries(ctx.integrations, async (integration) => { + const innerHandler = networkControllerRequestHandler.handler(integration, {}); + const { data, timestamp } = await innerHandler.getDataAsync(); - return { - integration: { - id: integration.id, - name: integration.name, - kind: integration.kind, - }, - summary: data, - updatedAt: timestamp, - }; - }), - ); - return results; - }), - - subscribeToSummary: publicProcedure - .concat(createManyIntegrationMiddleware("query", ...getIntegrationKindsByCategory("networkController"))) - .subscription(({ ctx }) => { - return observable<{ - integration: Modify }>; - summary: NetworkControllerSummary; - }>((emit) => { - const unsubscribes: (() => void)[] = []; - for (const integrationWithSecrets of ctx.integrations) { - const { decryptedSecrets: _, ...integration } = integrationWithSecrets; - const innerHandler = networkControllerRequestHandler.handler(integrationWithSecrets, {}); - const unsubscribe = innerHandler.subscribe((summary) => { - emit.next({ - integration, - summary, - }); - }); - unsubscribes.push(unsubscribe); - } - return () => { - unsubscribes.forEach((unsubscribe) => { - unsubscribe(); - }); + return { + integration: { + id: integration.id, + name: integration.name, + kind: integration.kind, + }, + summary: data, + updatedAt: timestamp, }; }); }), diff --git a/packages/api/src/router/widgets/notifications.ts b/packages/api/src/router/widgets/notifications.ts index 89ec95165..bae8ba9fd 100644 --- a/packages/api/src/router/widgets/notifications.ts +++ b/packages/api/src/router/widgets/notifications.ts @@ -1,14 +1,9 @@ -import { observable } from "@trpc/server/observable"; - -import type { Modify } from "@homarr/common/types"; -import type { Integration } from "@homarr/db/schema"; -import type { IntegrationKindByCategory } from "@homarr/definitions"; import { getIntegrationKindsByCategory } from "@homarr/definitions"; -import type { Notification } from "@homarr/integrations"; import { notificationsRequestHandler } from "@homarr/request-handler/notifications"; import type { IntegrationAction } from "../../middlewares/integration"; import { createManyIntegrationMiddleware } from "../../middlewares/integration"; +import { settleIntegrationQueries } from "../../settle-integrations"; import { createTRPCRouter, publicProcedure } from "../../trpc"; const createNotificationsIntegrationMiddleware = (action: IntegrationAction) => @@ -18,46 +13,18 @@ export const notificationsRouter = createTRPCRouter({ getNotifications: publicProcedure .unstable_concat(createNotificationsIntegrationMiddleware("query")) .query(async ({ ctx }) => { - return await Promise.all( - ctx.integrations.map(async (integration) => { - const innerHandler = notificationsRequestHandler.handler(integration, {}); - const { data, timestamp } = await innerHandler.getCachedOrUpdatedDataAsync({ forceUpdate: false }); + return await settleIntegrationQueries(ctx.integrations, async (integration) => { + const innerHandler = notificationsRequestHandler.handler(integration, {}); + const { data, timestamp } = await innerHandler.getDataAsync(); - return { - integration: { - id: integration.id, - name: integration.name, - kind: integration.kind, - updatedAt: timestamp, - }, - data, - }; - }), - ); - }), - subscribeNotifications: publicProcedure - .unstable_concat(createNotificationsIntegrationMiddleware("query")) - .subscription(({ ctx }) => { - return observable<{ - integration: Modify }>; - data: Notification[]; - }>((emit) => { - const unsubscribes: (() => void)[] = []; - for (const integrationWithSecrets of ctx.integrations) { - const { decryptedSecrets: _, ...integration } = integrationWithSecrets; - const innerHandler = notificationsRequestHandler.handler(integrationWithSecrets, {}); - const unsubscribe = innerHandler.subscribe((data) => { - emit.next({ - integration, - data, - }); - }); - unsubscribes.push(unsubscribe); - } - return () => { - unsubscribes.forEach((unsubscribe) => { - unsubscribe(); - }); + return { + integration: { + id: integration.id, + name: integration.name, + kind: integration.kind, + updatedAt: timestamp, + }, + data, }; }); }), diff --git a/packages/api/src/router/widgets/paperless-ngx.ts b/packages/api/src/router/widgets/paperless-ngx.ts index 0fb81e72a..2a1bf40de 100644 --- a/packages/api/src/router/widgets/paperless-ngx.ts +++ b/packages/api/src/router/widgets/paperless-ngx.ts @@ -6,7 +6,7 @@ import { createTRPCRouter, publicProcedure } from "../../trpc"; export const paperlessNgxRouter = createTRPCRouter({ getStats: publicProcedure.concat(createOneIntegrationMiddleware("query", "paperlessNgx")).query(async ({ ctx }) => { const innerHandler = paperlessNgxStatsRequestHandler.handler(ctx.integration, {}); - const data = await innerHandler.getCachedOrUpdatedDataAsync({ forceUpdate: false }); + const data = await innerHandler.getDataAsync(); return data.data; }), }); diff --git a/packages/api/src/router/widgets/releases.ts b/packages/api/src/router/widgets/releases.ts index 3d355b26c..b58fecd19 100644 --- a/packages/api/src/router/widgets/releases.ts +++ b/packages/api/src/router/widgets/releases.ts @@ -1,11 +1,15 @@ +import SuperJSON from "superjson"; import { escapeForRegEx } from "@tiptap/react"; import { z } from "zod/v4"; -import { getIntegrationKindsByCategory } from "@homarr/definitions"; +import { decryptSecret } from "@homarr/common/server"; +import { eq } from "@homarr/db"; +import { boards, items, widgetSecrets } from "@homarr/db/schema"; +import { releaseProviderKinds } from "@homarr/definitions"; import { releasesRequestHandler } from "@homarr/request-handler/releases"; -import { createOneIntegrationMiddleware } from "../../middlewares/integration"; import { createTRPCRouter, publicProcedure } from "../../trpc"; +import { throwIfActionForbiddenAsync } from "../board/board-access"; const formatVersionFilterRegex = (versionFilter: z.infer | undefined) => { if (!versionFilter) return undefined; @@ -25,37 +29,83 @@ const releaseVersionFilterSchema = z.object({ export const releasesRouter = createTRPCRouter({ getLatest: publicProcedure - .concat(createOneIntegrationMiddleware("query", ...getIntegrationKindsByCategory("releasesProvider"))) .input( z.object({ + itemId: z.string().optional(), repositories: z.array( z.object({ - id: z.string(), + id: z.string().optional(), + provider: z.enum(releaseProviderKinds), identifier: z.string(), versionFilter: releaseVersionFilterSchema.optional(), + providerUrl: z.string().url().optional(), }), ), }), ) .query(async ({ ctx, input }) => { + const tokensByProvider = new Map(); + let allowedRepoIds: Set | null = null; + + if (input.itemId) { + try { + const item = await ctx.db.query.items.findFirst({ where: eq(items.id, input.itemId) }); + if (item && item.kind === "releases") { + await throwIfActionForbiddenAsync(ctx, eq(boards.id, item.boardId), "view"); + + const options = SuperJSON.parse>(item.options); + const repos = options.repositories as Array<{ id?: string }> | undefined; + if (repos) { + allowedRepoIds = new Set(repos.map((r) => r.id).filter((id): id is string => Boolean(id))); + } + + const secrets = await ctx.db.query.widgetSecrets.findMany({ + where: eq(widgetSecrets.itemId, input.itemId), + }); + for (const secret of secrets) { + try { + tokensByProvider.set(secret.kind, decryptSecret(secret.value)); + } catch { + // Skip corrupt secrets + } + } + } + } catch { + // Access denied or table not yet migrated -- proceed without tokens + } + } + return await Promise.all( input.repositories.map(async (repository) => { - const response = await releasesRequestHandler - .handler(ctx.integration, { - id: repository.id, - identifier: repository.identifier, - versionRegex: formatVersionFilterRegex(repository.versionFilter), - }) - .getCachedOrUpdatedDataAsync({ - forceUpdate: false, - }); + const repositoryId = repository.id ?? repository.identifier; + try { + const useToken = allowedRepoIds === null || allowedRepoIds.has(repositoryId); + const response = await releasesRequestHandler + .handler({ + id: repositoryId, + provider: repository.provider, + identifier: repository.identifier, + versionRegex: formatVersionFilterRegex(repository.versionFilter), + providerUrl: repository.providerUrl, + token: useToken ? tokensByProvider.get(repository.provider) : undefined, + }) + .getDataAsync(); - return { - id: repository.id, - integration: { name: ctx.integration.name, kind: ctx.integration.kind }, - timestamp: response.timestamp, - ...response.data, - }; + return { + id: repositoryId, + provider: repository.provider, + timestamp: response.timestamp, + ...response.data, + }; + } catch (error) { + return { + id: repositoryId, + provider: repository.provider, + timestamp: new Date(), + success: false as const, + error: { code: "unexpected" as const, message: String(error) }, + }; + } }), ); }), diff --git a/packages/api/src/router/widgets/rssFeed.ts b/packages/api/src/router/widgets/rssFeed.ts index ec9c223e0..5e43efe3e 100644 --- a/packages/api/src/router/widgets/rssFeed.ts +++ b/packages/api/src/router/widgets/rssFeed.ts @@ -1,4 +1,3 @@ -import { observable } from "@trpc/server/observable"; import SuperJSON from "superjson"; import { z } from "zod/v4"; @@ -6,12 +5,14 @@ import type { Session } from "@homarr/auth"; import type { Database } from "@homarr/db"; import { eq } from "@homarr/db"; import { items, users } from "@homarr/db/schema"; -import type { ExtendedFeedEntry } from "@homarr/request-handler/rss-feeds"; +import { createLogger } from "@homarr/core/infrastructure/logs"; import { rssFeedsRequestHandler } from "@homarr/request-handler/rss-feeds"; import type { WidgetComponentProps } from "../../../../widgets/src"; import { createTRPCRouter, publicProcedure } from "../../trpc"; +const logger = createLogger({ module: "rssFeed" }); + const feedsInput = z.object({ urls: z.array(z.string()).max(100), maximumAmountPosts: z.number(), @@ -23,41 +24,28 @@ export const rssFeedRouter = createTRPCRouter({ ? input.urls : await restrictUrlsAsync(ctx.db, input.urls); - const rssFeeds = await Promise.all( + const settled = await Promise.allSettled( urls.map(async (url) => { const innerHandler = rssFeedsRequestHandler.handler({ url, count: input.maximumAmountPosts, }); - return await innerHandler.getCachedOrUpdatedDataAsync({ - forceUpdate: false, - }); + return await innerHandler.getDataAsync(); }), ); - return rssFeeds - .flatMap((rssFeed) => rssFeed.data.entries) - .slice(0, input.maximumAmountPosts) - .sort((entryA, entryB) => { + return settled + .flatMap((result, index) => { + if (result.status === "fulfilled") return result.value.data.entries; + logger.warn("RSS feed fetch failed", { url: urls[index], error: result.reason }); + return []; + }) + .toSorted((entryA, entryB) => { return entryA.published && entryB.published ? new Date(entryB.published).getTime() - new Date(entryA.published).getTime() : 0; - }); - }), - subscribeFeeds: publicProcedure.input(feedsInput).subscription(({ input }) => { - // It is not necessary to check access to all feeds here, as the client will only subscribe to feeds which already exist - return observable<{ url: string; entries: ExtendedFeedEntry[] }>((emit) => { - const unsubscribes = input.urls.map((url) => { - const innerHandler = rssFeedsRequestHandler.handler({ url, count: input.maximumAmountPosts }); - return innerHandler.subscribe((data) => { - emit.next({ url, entries: data.entries }); - }); - }); - - return () => { - unsubscribes.forEach((unsubscribe) => unsubscribe()); - }; - }); + }) + .slice(0, input.maximumAmountPosts); }), }); diff --git a/packages/api/src/router/widgets/smart-home.ts b/packages/api/src/router/widgets/smart-home.ts index dec3770a7..c37468752 100644 --- a/packages/api/src/router/widgets/smart-home.ts +++ b/packages/api/src/router/widgets/smart-home.ts @@ -1,4 +1,3 @@ -import { observable } from "@trpc/server/observable"; import { z } from "zod/v4"; import { getIntegrationKindsByCategory } from "@homarr/definitions"; @@ -25,31 +24,9 @@ export const smartHomeRouter = createTRPCRouter({ .concat(createSmartHomeIntegrationMiddleware("query")) .query(async ({ ctx: { integration }, input }) => { const innerHandler = smartHomeEntityStateRequestHandler.handler(integration, { entityId: input.entityId }); - const { data } = await innerHandler.getCachedOrUpdatedDataAsync({ - forceUpdate: false, - }); + const { data } = await innerHandler.getDataAsync(); return data; }), - subscribeEntityState: publicProcedure - .concat(createSmartHomeIntegrationMiddleware("query")) - .input(z.object({ entityId: z.string() })) - .subscription(({ input, ctx }) => { - return observable<{ - entityId: string; - state: string; - }>((emit) => { - const innerHandler = smartHomeEntityStateRequestHandler.handler(ctx.integration, { - entityId: input.entityId, - }); - const unsubscribe = innerHandler.subscribe((state) => { - emit.next({ state, entityId: input.entityId }); - }); - - return () => { - unsubscribe(); - }; - }); - }), switchEntity: protectedProcedure .meta({ mcp: { @@ -64,9 +41,6 @@ export const smartHomeRouter = createTRPCRouter({ const client = await createIntegrationAsync(integration); const success = await client.triggerToggleAsync(input.entityId); - const innerHandler = smartHomeEntityStateRequestHandler.handler(integration, { entityId: input.entityId }); - await innerHandler.invalidateAsync(); - return success; }), executeAutomation: protectedProcedure diff --git a/packages/api/src/router/widgets/speedtest-tracker.ts b/packages/api/src/router/widgets/speedtest-tracker.ts index be4245921..01e7fb58b 100644 --- a/packages/api/src/router/widgets/speedtest-tracker.ts +++ b/packages/api/src/router/widgets/speedtest-tracker.ts @@ -1,54 +1,23 @@ -import { observable } from "@trpc/server/observable"; - -import type { SpeedtestTrackerDashboardData } from "@homarr/integrations/types"; import { speedtestTrackerRequestHandler } from "@homarr/request-handler/speedtest-tracker"; import { createManyIntegrationMiddleware } from "../../middlewares/integration"; +import { settleIntegrationQueries } from "../../settle-integrations"; import { createTRPCRouter, publicProcedure } from "../../trpc"; export const speedtestTrackerRouter = createTRPCRouter({ getDashboard: publicProcedure .concat(createManyIntegrationMiddleware("query", "speedtestTracker")) .query(async ({ ctx }) => { - const results = await Promise.all( - ctx.integrations.map(async (integration) => { - const innerHandler = speedtestTrackerRequestHandler.handler(integration, {}); - const { data, timestamp } = await innerHandler.getCachedOrUpdatedDataAsync({ forceUpdate: false }); + return await settleIntegrationQueries(ctx.integrations, async (integration) => { + const innerHandler = speedtestTrackerRequestHandler.handler(integration, {}); + const { data, timestamp } = await innerHandler.getDataAsync(); - return { - integrationId: integration.id, - integrationName: integration.name, - integrationUrl: integration.url, - dashboard: data, - updatedAt: timestamp, - }; - }), - ); - - return results; - }), - - subscribeToDashboard: publicProcedure - .concat(createManyIntegrationMiddleware("query", "speedtestTracker")) - .subscription(({ ctx }) => { - return observable<{ - integrationId: string; - dashboard: SpeedtestTrackerDashboardData; - timestamp: Date; - }>((emit) => { - const unsubscribes = ctx.integrations.map((integration) => { - const innerHandler = speedtestTrackerRequestHandler.handler(integration, {}); - return innerHandler.subscribe((dashboard) => { - emit.next({ - integrationId: integration.id, - dashboard, - timestamp: new Date(), - }); - }); - }); - - return () => { - unsubscribes.forEach((unsubscribe) => unsubscribe()); + return { + integrationId: integration.id, + integrationName: integration.name, + integrationUrl: integration.url, + dashboard: data, + updatedAt: timestamp, }; }); }), diff --git a/packages/api/src/router/widgets/stocks.ts b/packages/api/src/router/widgets/stocks.ts index d7b4ed13a..7fc08005b 100644 --- a/packages/api/src/router/widgets/stocks.ts +++ b/packages/api/src/router/widgets/stocks.ts @@ -18,6 +18,6 @@ export const stockPriceRouter = createTRPCRouter({ timeRange: input.timeRange, timeInterval: input.timeInterval, }); - return await innerHandler.getCachedOrUpdatedDataAsync({ forceUpdate: false }); + return await innerHandler.getDataAsync(); }), }); diff --git a/packages/api/src/router/widgets/timetable.ts b/packages/api/src/router/widgets/timetable.ts index 787be4c37..ef60ce320 100644 --- a/packages/api/src/router/widgets/timetable.ts +++ b/packages/api/src/router/widgets/timetable.ts @@ -1,43 +1,36 @@ import z from "zod"; -import { getIntegrationKindsByCategory } from "@homarr/definitions"; -import { timetableOptionsSchema } from "@homarr/integrations/types"; import { timetableGetTimetableRequestHandler, timetableSearchStationsRequestHandler, } from "@homarr/request-handler/timetable"; -import { createOneIntegrationMiddleware } from "../../middlewares/integration"; import { createTRPCRouter, publicProcedure } from "../../trpc"; +const baseUrlSchema = z.string().url(); + export const timetableRouter = createTRPCRouter({ getTimetable: publicProcedure - .concat(createOneIntegrationMiddleware("query", ...getIntegrationKindsByCategory("timetable"))) - .input(timetableOptionsSchema) - .query(async ({ input, ctx }) => { - const innerHandler = timetableGetTimetableRequestHandler.handler(ctx.integration, { - stationId: input.stationId, - limit: input.limit, - }); - - const { data } = await innerHandler.getCachedOrUpdatedDataAsync({ forceUpdate: false }); - + .input( + z.object({ + baseUrl: baseUrlSchema, + stationId: z.string(), + limit: z.number().int().min(1).max(100), + }), + ) + .query(async ({ input }) => { + const { data } = await timetableGetTimetableRequestHandler.handler(input).getDataAsync(); return data; }), searchStations: publicProcedure - .concat(createOneIntegrationMiddleware("query", ...getIntegrationKindsByCategory("timetable"))) .input( z.object({ + baseUrl: baseUrlSchema, query: z.string(), }), ) - .query(async ({ input, ctx }) => { - const innerHandler = timetableSearchStationsRequestHandler.handler(ctx.integration, { - query: input.query, - }); - - const { data } = await innerHandler.getCachedOrUpdatedDataAsync({ forceUpdate: false }); - + .query(async ({ input }) => { + const { data } = await timetableSearchStationsRequestHandler.handler(input).getDataAsync(); return data; }), }); diff --git a/packages/api/src/router/widgets/tracearr.ts b/packages/api/src/router/widgets/tracearr.ts index d46c80c66..5075c990b 100644 --- a/packages/api/src/router/widgets/tracearr.ts +++ b/packages/api/src/router/widgets/tracearr.ts @@ -1,48 +1,22 @@ -import { observable } from "@trpc/server/observable"; - -import type { TracearrDashboardData } from "@homarr/integrations/types"; import { tracearrRequestHandler } from "@homarr/request-handler/tracearr"; import { createManyIntegrationMiddleware } from "../../middlewares/integration"; +import { settleIntegrationQueries } from "../../settle-integrations"; import { createTRPCRouter, publicProcedure } from "../../trpc"; export const tracearrRouter = createTRPCRouter({ getDashboard: publicProcedure.concat(createManyIntegrationMiddleware("query", "tracearr")).query(async ({ ctx }) => { - const results = await Promise.all( - ctx.integrations.map(async (integration) => { - const innerHandler = tracearrRequestHandler.handler(integration, {}); - const { data, timestamp } = await innerHandler.getCachedOrUpdatedDataAsync({ forceUpdate: false }); + return await settleIntegrationQueries(ctx.integrations, async (integration) => { + const innerHandler = tracearrRequestHandler.handler(integration, {}); + const { data, timestamp } = await innerHandler.getDataAsync(); - return { - integrationId: integration.id, - integrationName: integration.name, - integrationUrl: integration.url, - dashboard: data, - updatedAt: timestamp, - }; - }), - ); - - return results; + return { + integrationId: integration.id, + integrationName: integration.name, + integrationUrl: integration.url, + dashboard: data, + updatedAt: timestamp, + }; + }); }), - subscribeToDashboard: publicProcedure - .concat(createManyIntegrationMiddleware("query", "tracearr")) - .subscription(({ ctx }) => { - return observable<{ integrationId: string; dashboard: TracearrDashboardData; timestamp: Date }>((emit) => { - const unsubscribes = ctx.integrations.map((integration) => { - const innerHandler = tracearrRequestHandler.handler(integration, {}); - return innerHandler.subscribe((dashboard) => { - emit.next({ - integrationId: integration.id, - dashboard, - timestamp: new Date(), - }); - }); - }); - - return () => { - unsubscribes.forEach((unsubscribe) => unsubscribe()); - }; - }); - }), }); diff --git a/packages/api/src/router/widgets/umami.ts b/packages/api/src/router/widgets/umami.ts index 2c240f4bb..3994f398e 100644 --- a/packages/api/src/router/widgets/umami.ts +++ b/packages/api/src/router/widgets/umami.ts @@ -1,4 +1,3 @@ -import { observable } from "@trpc/server/observable"; import { z } from "zod/v4"; import { createLogger } from "@homarr/core/infrastructure/logs"; @@ -14,6 +13,7 @@ import { } from "@homarr/request-handler/umami"; import { createManyIntegrationMiddleware, createOneIntegrationMiddleware } from "../../middlewares/integration"; +import { settleIntegrationQueries } from "../../settle-integrations"; import { createTRPCRouter, publicProcedure } from "../../trpc"; const logger = createLogger({ module: "umami-router" }); @@ -39,26 +39,22 @@ export const umamiRouter = createTRPCRouter({ ) .concat(createManyIntegrationMiddleware("query", "umami")) .query(async ({ ctx, input }) => { - const results = await Promise.all( - ctx.integrations.map(async (integration) => { - const innerHandler = umamiRequestHandler.handler(integration, { - websiteId: input.websiteId, - timeFrame: input.timeFrame, - eventName: input.eventName, - }); - const { data, timestamp } = await innerHandler.getCachedOrUpdatedDataAsync({ forceUpdate: false }); + return await settleIntegrationQueries(ctx.integrations, async (integration) => { + const innerHandler = umamiRequestHandler.handler(integration, { + websiteId: input.websiteId, + timeFrame: input.timeFrame, + eventName: input.eventName, + }); + const { data, timestamp } = await innerHandler.getDataAsync(); - return { - integrationId: integration.id, - integrationName: integration.name, - integrationUrl: integration.url, - visitorStats: data, - updatedAt: timestamp, - }; - }), - ); - - return results; + return { + integrationId: integration.id, + integrationName: integration.name, + integrationUrl: integration.url, + visitorStats: data, + updatedAt: timestamp, + }; + }); }), getEventNames: publicProcedure @@ -67,7 +63,7 @@ export const umamiRouter = createTRPCRouter({ .query(async ({ ctx, input }) => { try { const innerHandler = umamiEventNamesRequestHandler.handler(ctx.integration, { websiteId: input.websiteId }); - const { data } = await innerHandler.getCachedOrUpdatedDataAsync({ forceUpdate: false }); + const { data } = await innerHandler.getDataAsync(); return data; } catch (error) { logger.warn( @@ -93,7 +89,7 @@ export const umamiRouter = createTRPCRouter({ timeFrame: input.timeFrame, limit: input.limit, }); - const { data } = await innerHandler.getCachedOrUpdatedDataAsync({ forceUpdate: false }); + const { data } = await innerHandler.getDataAsync(); return data; } catch (error) { logger.warn( @@ -123,7 +119,7 @@ export const umamiRouter = createTRPCRouter({ timeFrame: input.timeFrame, limit: input.limit, }); - const { data } = await innerHandler.getCachedOrUpdatedDataAsync({ forceUpdate: false }); + const { data } = await innerHandler.getDataAsync(); return data; } catch (error) { logger.warn( @@ -148,13 +144,13 @@ export const umamiRouter = createTRPCRouter({ .concat(createOneIntegrationMiddleware("query", "umami")) .query(async ({ ctx, input }) => { try { - const sortedNames = [...input.eventNames].sort(); + const sortedNames = [...input.eventNames].toSorted(); const innerHandler = umamiMultiEventRequestHandler.handler(ctx.integration, { websiteId: input.websiteId, timeFrame: input.timeFrame, eventNames: sortedNames, }); - const { data } = await innerHandler.getCachedOrUpdatedDataAsync({ forceUpdate: false }); + const { data } = await innerHandler.getDataAsync(); return data; } catch (error) { logger.warn( @@ -176,7 +172,7 @@ export const umamiRouter = createTRPCRouter({ const innerHandler = umamiActiveVisitorsRequestHandler.handler(ctx.integration, { websiteId: input.websiteId, }); - const { data } = await innerHandler.getCachedOrUpdatedDataAsync({ forceUpdate: false }); + const { data } = await innerHandler.getDataAsync(); return data; } catch (error) { logger.warn( @@ -185,21 +181,4 @@ export const umamiRouter = createTRPCRouter({ return 0; } }), - - subscribeActiveVisitors: publicProcedure - .input(z.object({ websiteId: z.string() })) - .concat(createOneIntegrationMiddleware("query", "umami")) - .subscription(({ ctx, input }) => { - return observable((emit) => { - const innerHandler = umamiActiveVisitorsRequestHandler.handler(ctx.integration, { - websiteId: input.websiteId, - }); - const unsubscribe = innerHandler.subscribe((count) => { - emit.next(count); - }); - return () => { - unsubscribe(); - }; - }); - }), }); diff --git a/packages/api/src/router/widgets/ups.ts b/packages/api/src/router/widgets/ups.ts index f1c27c434..16c6312dd 100644 --- a/packages/api/src/router/widgets/ups.ts +++ b/packages/api/src/router/widgets/ups.ts @@ -1,50 +1,24 @@ -import { observable } from "@trpc/server/observable"; - import { getIntegrationKindsByCategory } from "@homarr/definitions"; -import type { UpsSummary } from "@homarr/integrations/types"; import { upsSummariesRequestHandler } from "@homarr/request-handler/ups"; import { createManyIntegrationMiddleware } from "../../middlewares/integration"; +import { settleIntegrationQueries } from "../../settle-integrations"; import { createTRPCRouter, publicProcedure } from "../../trpc"; export const upsRouter = createTRPCRouter({ getSummaries: publicProcedure .concat(createManyIntegrationMiddleware("query", ...getIntegrationKindsByCategory("ups"))) .query(async ({ ctx }) => { - const results = await Promise.all( - ctx.integrations.map(async (integration) => { - const innerHandler = upsSummariesRequestHandler.handler(integration, {}); - const { data, timestamp } = await innerHandler.getCachedOrUpdatedDataAsync({ forceUpdate: false }); + return await settleIntegrationQueries(ctx.integrations, async (integration) => { + const innerHandler = upsSummariesRequestHandler.handler(integration, {}); + const { data, timestamp } = await innerHandler.getDataAsync(); - return { - integrationId: integration.id, - integrationName: integration.name, - integrationUrl: integration.url, - summaries: data, - updatedAt: timestamp, - }; - }), - ); - - return results; - }), - subscribeSummaries: publicProcedure - .concat(createManyIntegrationMiddleware("query", ...getIntegrationKindsByCategory("ups"))) - .subscription(({ ctx }) => { - return observable<{ integrationId: string; summaries: UpsSummary[]; timestamp: Date }>((emit) => { - const unsubscribes = ctx.integrations.map((integration) => { - const innerHandler = upsSummariesRequestHandler.handler(integration, {}); - return innerHandler.subscribe((summaries) => { - emit.next({ - integrationId: integration.id, - summaries, - timestamp: new Date(), - }); - }); - }); - - return () => { - unsubscribes.forEach((unsubscribe) => unsubscribe()); + return { + integrationId: integration.id, + integrationName: integration.name, + integrationUrl: integration.url, + summaries: data, + updatedAt: timestamp, }; }); }), diff --git a/packages/api/src/router/widgets/uptime-kuma.ts b/packages/api/src/router/widgets/uptime-kuma.ts index eb28f9e7e..6df14090c 100644 --- a/packages/api/src/router/widgets/uptime-kuma.ts +++ b/packages/api/src/router/widgets/uptime-kuma.ts @@ -1,54 +1,23 @@ -import { observable } from "@trpc/server/observable"; - -import type { UptimeKumaDashboardData } from "@homarr/integrations/types"; import { uptimeKumaRequestHandler } from "@homarr/request-handler/uptime-kuma"; import { createManyIntegrationMiddleware } from "../../middlewares/integration"; +import { settleIntegrationQueries } from "../../settle-integrations"; import { createTRPCRouter, publicProcedure } from "../../trpc"; export const uptimeKumaRouter = createTRPCRouter({ getDashboard: publicProcedure .concat(createManyIntegrationMiddleware("query", "uptimeKuma")) .query(async ({ ctx }) => { - const results = await Promise.all( - ctx.integrations.map(async (integration) => { - const innerHandler = uptimeKumaRequestHandler.handler(integration, {}); - const { data, timestamp } = await innerHandler.getCachedOrUpdatedDataAsync({ forceUpdate: false }); + return await settleIntegrationQueries(ctx.integrations, async (integration) => { + const innerHandler = uptimeKumaRequestHandler.handler(integration, {}); + const { data, timestamp } = await innerHandler.getDataAsync(); - return { - integrationId: integration.id, - integrationName: integration.name, - integrationUrl: integration.url, - dashboard: data, - updatedAt: timestamp, - }; - }), - ); - - return results; - }), - - subscribeToDashboard: publicProcedure - .concat(createManyIntegrationMiddleware("query", "uptimeKuma")) - .subscription(({ ctx }) => { - return observable<{ - integrationId: string; - dashboard: UptimeKumaDashboardData; - timestamp: Date; - }>((emit) => { - const unsubscribes = ctx.integrations.map((integration) => { - const innerHandler = uptimeKumaRequestHandler.handler(integration, {}); - return innerHandler.subscribe((dashboard) => { - emit.next({ - integrationId: integration.id, - dashboard, - timestamp: new Date(), - }); - }); - }); - - return () => { - unsubscribes.forEach((unsubscribe) => unsubscribe()); + return { + integrationId: integration.id, + integrationName: integration.name, + integrationUrl: integration.url, + dashboard: data, + updatedAt: timestamp, }; }); }), diff --git a/packages/api/src/router/widgets/vpn.ts b/packages/api/src/router/widgets/vpn.ts index f50d65f33..407e4986f 100644 --- a/packages/api/src/router/widgets/vpn.ts +++ b/packages/api/src/router/widgets/vpn.ts @@ -1,25 +1,16 @@ -import { observable } from "@trpc/server/observable"; - -import type { Modify } from "@homarr/common/types"; -import type { Integration } from "@homarr/db/schema"; -import type { IntegrationKindByCategory } from "@homarr/definitions"; import { getIntegrationKindsByCategory } from "@homarr/definitions"; -import type { VpnSummary } from "@homarr/integrations/types"; import { vpnSummaryHandler } from "@homarr/request-handler/vpn"; import { createManyIntegrationMiddleware } from "../../middlewares/integration"; +import { settleIntegrationQueries } from "../../settle-integrations"; import { createTRPCRouter, publicProcedure } from "../../trpc"; -const createVpnIntegrationMiddleware = () => - createManyIntegrationMiddleware("query", ...getIntegrationKindsByCategory("vpn")); - export const vpnRouter = createTRPCRouter({ - getSummaries: publicProcedure.unstable_concat(createVpnIntegrationMiddleware()).query(async ({ ctx }) => { - return await Promise.all( - ctx.integrations.map(async (integration) => { - const innerHandler = vpnSummaryHandler.handler(integration, {}); - const { data, timestamp } = await innerHandler.getCachedOrUpdatedDataAsync({ forceUpdate: false }); - + getSummaries: publicProcedure + .unstable_concat(createManyIntegrationMiddleware("query", ...getIntegrationKindsByCategory("vpn"))) + .query(async ({ ctx }) => { + return await settleIntegrationQueries(ctx.integrations, async (integration) => { + const { data, timestamp } = await vpnSummaryHandler.handler(integration, {}).getDataAsync(); return { integration: { id: integration.id, @@ -29,31 +20,6 @@ export const vpnRouter = createTRPCRouter({ }, summary: data, }; - }), - ); - }), - subscribeSummaries: publicProcedure.unstable_concat(createVpnIntegrationMiddleware()).subscription(({ ctx }) => { - return observable<{ - integration: Modify }>; - summary: VpnSummary | null; - }>((emit) => { - const unsubscribes: (() => void)[] = []; - for (const integrationWithSecrets of ctx.integrations) { - const { decryptedSecrets: _, ...integration } = integrationWithSecrets; - const innerHandler = vpnSummaryHandler.handler(integrationWithSecrets, {}); - const unsubscribe = innerHandler.subscribe((summary) => { - emit.next({ - integration, - summary, - }); - }); - unsubscribes.push(unsubscribe); - } - return () => { - unsubscribes.forEach((unsubscribe) => { - unsubscribe(); - }); - }; - }); - }), + }); + }), }); diff --git a/packages/api/src/router/widgets/weather.ts b/packages/api/src/router/widgets/weather.ts index bb1077569..87ea6b48a 100644 --- a/packages/api/src/router/widgets/weather.ts +++ b/packages/api/src/router/widgets/weather.ts @@ -1,7 +1,5 @@ -import { observable } from "@trpc/server/observable"; import { z } from "zod/v4"; -import type { Weather } from "@homarr/request-handler/weather"; import { weatherRequestHandler } from "@homarr/request-handler/weather"; import { createTRPCRouter, publicProcedure } from "../../trpc"; @@ -14,16 +12,6 @@ const atLocationInput = z.object({ export const weatherRouter = createTRPCRouter({ atLocation: publicProcedure.input(atLocationInput).query(async ({ input }) => { const handler = weatherRequestHandler.handler(input); - return await handler.getCachedOrUpdatedDataAsync({ forceUpdate: false }).then((result) => result.data); - }), - subscribeAtLocation: publicProcedure.input(atLocationInput).subscription(({ input }) => { - return observable((emit) => { - const handler = weatherRequestHandler.handler(input); - const unsubscribe = handler.subscribe((data) => { - emit.next(data); - }); - - return unsubscribe; - }); + return await handler.getDataAsync().then((result) => result.data); }), }); diff --git a/packages/api/src/router/widgets/widget-secrets.ts b/packages/api/src/router/widgets/widget-secrets.ts new file mode 100644 index 000000000..75ae23cca --- /dev/null +++ b/packages/api/src/router/widgets/widget-secrets.ts @@ -0,0 +1,97 @@ +import { TRPCError } from "@trpc/server"; +import { z } from "zod/v4"; + +import { encryptSecret } from "@homarr/common/server"; +import { and, eq, handleTransactionsAsync } from "@homarr/db"; +import { boards, items, widgetSecrets } from "@homarr/db/schema"; +import { releaseProviderKinds } from "@homarr/definitions"; + +import { createTRPCRouter, protectedProcedure } from "../../trpc"; +import { throwIfActionForbiddenAsync } from "../board/board-access"; + +const secretKindSchema = z.enum(releaseProviderKinds); + +const resolveItemWithBoardAccess = async (ctx: { db: any; session: any }, itemId: string) => { + const item = await ctx.db.query.items.findFirst({ where: eq(items.id, itemId) }); + if (!item || item.kind !== "releases") throw new TRPCError({ code: "NOT_FOUND" }); + await throwIfActionForbiddenAsync(ctx, eq(boards.id, item.boardId), "modify"); + return item; +}; + +export const widgetSecretsRouter = createTRPCRouter({ + setSecret: protectedProcedure + .meta({ + mcp: { + enabled: true, + description: + "Set an authentication token for a release provider on a specific releases widget. Requires modify permission on the widget's board. Use widget_secrets_getConfiguredKinds to check existing tokens", + }, + }) + .input(z.object({ itemId: z.string(), kind: secretKindSchema, value: z.string().min(1).max(4096) })) + .mutation(async ({ ctx, input }) => { + await resolveItemWithBoardAccess(ctx, input.itemId); + + const encrypted = encryptSecret(input.value); + const row = { itemId: input.itemId, kind: input.kind, value: encrypted, updatedAt: new Date() }; + + await handleTransactionsAsync(ctx.db, { + async handleAsync(db, schema) { + await db.transaction(async (tx) => { + await tx + .delete(schema.widgetSecrets) + .where(and(eq(schema.widgetSecrets.itemId, input.itemId), eq(schema.widgetSecrets.kind, input.kind))); + await tx.insert(schema.widgetSecrets).values(row); + }); + }, + handleSync(db) { + db.transaction((tx) => { + tx.delete(widgetSecrets) + .where(and(eq(widgetSecrets.itemId, input.itemId), eq(widgetSecrets.kind, input.kind))) + .run(); + tx.insert(widgetSecrets).values(row).run(); + }); + }, + }); + }), + + deleteSecret: protectedProcedure + .meta({ + mcp: { + enabled: true, + description: + "Remove an authentication token for a release provider from a specific releases widget. Requires modify permission on the widget's board", + }, + }) + .input(z.object({ itemId: z.string(), kind: secretKindSchema })) + .mutation(async ({ ctx, input }) => { + await resolveItemWithBoardAccess(ctx, input.itemId); + + await ctx.db + .delete(widgetSecrets) + .where(and(eq(widgetSecrets.itemId, input.itemId), eq(widgetSecrets.kind, input.kind))); + }), + + getConfiguredKinds: protectedProcedure + .meta({ + mcp: { + enabled: true, + description: + "List which release provider kinds have tokens configured for a releases widget. Returns an array of provider kind strings (e.g. github, gitlab, dockerHub). Requires modify permission on the widget's board", + }, + }) + .input(z.object({ itemId: z.string() })) + .query(async ({ ctx, input }) => { + await resolveItemWithBoardAccess(ctx, input.itemId); + + try { + const secrets = await ctx.db.query.widgetSecrets.findMany({ + where: eq(widgetSecrets.itemId, input.itemId), + columns: { kind: true }, + }); + return secrets.map((s: { kind: string }) => s.kind); + } catch { + // ponytail: table may not exist pre-migration, return empty + return [] as string[]; + } + }), +}); diff --git a/packages/api/src/services/duckduckgo-bangs.ts b/packages/api/src/services/duckduckgo-bangs.ts index 297f5c8ed..48de45dd6 100644 --- a/packages/api/src/services/duckduckgo-bangs.ts +++ b/packages/api/src/services/duckduckgo-bangs.ts @@ -39,7 +39,7 @@ export const searchDuckDuckGoBangsAsync = async (input: { const queryTokenPrefix = normalizeBangToken(input.query); if (!queryTokenPrefix) return []; - const { data: allBangs } = await duckDuckGoBangsRequestHandler.handler({}).getCachedOrUpdatedDataAsync({}); + const { data: allBangs } = await duckDuckGoBangsRequestHandler.handler({}).getDataAsync(); const startIndex = lowerBound(allBangs, queryTokenPrefix); const matches: DuckDuckGoBang[] = []; diff --git a/packages/api/src/settle-integrations.ts b/packages/api/src/settle-integrations.ts new file mode 100644 index 000000000..9ae1b8f04 --- /dev/null +++ b/packages/api/src/settle-integrations.ts @@ -0,0 +1,29 @@ +import { createLogger } from "@homarr/core/infrastructure/logs"; +import { ErrorWithMetadata } from "@homarr/core/infrastructure/logs/error"; + +const logger = createLogger({ module: "settleIntegrations" }); + +interface IntegrationLike { + id: string; + name: string; + kind: string; +} + +export async function settleIntegrationQueries( + integrations: TIntegration[], + fn: (integration: TIntegration) => Promise, +): Promise { + const settled = await Promise.allSettled(integrations.map(async (integration) => fn(integration))); + return settled.flatMap((result, index) => { + if (result.status === "fulfilled") return [result.value]; + const integration = integrations[index]; + logger.warn( + new ErrorWithMetadata( + "Integration query failed", + { integrationId: integration?.id, integrationKind: integration?.kind }, + { cause: result.reason }, + ), + ); + return []; + }); +} diff --git a/packages/api/src/test/query-cache.spec.ts b/packages/api/src/test/query-cache.spec.ts index c765af031..090544cb2 100644 --- a/packages/api/src/test/query-cache.spec.ts +++ b/packages/api/src/test/query-cache.spec.ts @@ -10,59 +10,31 @@ import { isPersistableWidgetQueryKey, queryCacheDefaultStaleTimeMs, queryCacheMaxValueBytes, - queryCacheRetentionMs, } from "@homarr/api/query-cache"; import { queryCacheRouter } from "../router/query-cache"; const redisMock = vi.hoisted(() => { - const values = new Map(); - const calls: { - userId: string; - boardId: string; - key: string; - ttlMs: number; - maxValueBytes: number; - }[] = []; + const values = new Map(); return { values, - calls, - createQueryCacheChannel: vi.fn( - (options: { userId: string; boardId: string; key: string; ttlMs: number; maxValueBytes: number }) => { - calls.push(options); - const storageKey = `${options.userId}:${options.boardId}:${options.key}`; - - return { - getAsync: async () => { - const item = values.get(storageKey); - if (!item) return null; - if (Date.now() > item.expiresAt) { - values.delete(storageKey); - return null; - } - - return item.value; - }, - setAsync: async (value: string) => { - if (Buffer.byteLength(value, "utf8") > options.maxValueBytes) { - return false; - } - - values.set(storageKey, { value, expiresAt: Date.now() + options.ttlMs }); - return true; - }, - removeAsync: async () => { - values.delete(storageKey); - }, - }; + setQueryCacheAsync: vi.fn( + async (_userId: string, _boardId: string, value: string, _ttlMs: number, maxValueBytes: number) => { + if (Buffer.byteLength(value, "utf8") > maxValueBytes) return false; + values.set(`${_userId}:${_boardId}`, value); + return true; }, ), + removeQueryCacheAsync: vi.fn(async (_userId: string, _boardId: string) => { + values.delete(`${_userId}:${_boardId}`); + }), }; }); vi.mock("@homarr/redis", () => ({ - createQueryCacheChannel: redisMock.createQueryCacheChannel, + setQueryCacheAsync: redisMock.setQueryCacheAsync, + removeQueryCacheAsync: redisMock.removeQueryCacheAsync, })); vi.mock("@homarr/auth", () => ({ auth: () => ({}) as Session })); @@ -70,8 +42,8 @@ vi.mock("@homarr/auth", () => ({ auth: () => ({}) as Session })); afterEach(() => { vi.useRealTimers(); redisMock.values.clear(); - redisMock.calls.length = 0; - redisMock.createQueryCacheChannel.mockClear(); + redisMock.setQueryCacheAsync.mockClear(); + redisMock.removeQueryCacheAsync.mockClear(); }); const createSession = (userId = createId()): Session => ({ @@ -104,7 +76,7 @@ describe("isPersistableWidgetQueryKey", () => { }); describe("queryCacheRouter", () => { - test("stores, reads, and removes data for a board viewer", async () => { + test("stores and removes data for a board viewer", async () => { const db = createDb(); const session = createSession(); await insertUserAsync(db, session.user.id); @@ -112,14 +84,19 @@ describe("queryCacheRouter", () => { const caller = queryCacheRouter.createCaller({ db, deviceType: undefined, session }); - await expect(caller.setItem({ boardId: "board-1", key: "query-key", value: "cached-value" })).resolves.toEqual({ + await expect(caller.setItem({ boardId: "board-1", value: "cached-value" })).resolves.toEqual({ stored: true, }); - await expect(caller.getItem({ boardId: "board-1", key: "query-key" })).resolves.toBe("cached-value"); + expect(redisMock.setQueryCacheAsync).toHaveBeenCalledWith( + session.user.id, + "board-1", + "cached-value", + expect.any(Number), + queryCacheMaxValueBytes, + ); - await caller.removeItem({ boardId: "board-1", key: "query-key" }); - - await expect(caller.getItem({ boardId: "board-1", key: "query-key" })).resolves.toBeNull(); + await caller.removeItem({ boardId: "board-1" }); + expect(redisMock.removeQueryCacheAsync).toHaveBeenCalledWith(session.user.id, "board-1"); }); test("does not touch Redis when board access is denied", async () => { @@ -127,32 +104,32 @@ describe("queryCacheRouter", () => { await insertUserAsync(db, "owner"); await db.insert(boards).values({ id: "board-1", name: "board", creatorId: "owner", isPublic: false }); - redisMock.createQueryCacheChannel.mockClear(); + redisMock.setQueryCacheAsync.mockClear(); const caller = queryCacheRouter.createCaller({ db, deviceType: undefined, session: createSession("other-user") }); - await expect(caller.getItem({ boardId: "board-1", key: "query-key" })).rejects.toThrow("Board not found"); - expect(redisMock.createQueryCacheChannel).not.toHaveBeenCalled(); + await expect(caller.setItem({ boardId: "board-1", value: "cached-value" })).rejects.toThrow("Board not found"); + expect(redisMock.setQueryCacheAsync).not.toHaveBeenCalled(); }); - test("scopes anonymous public-board cache entries by anonymous user and board", async () => { + test("scopes anonymous public-board cache entries by anonymous user", async () => { const db = createDb(); await insertUserAsync(db, "owner"); await db.insert(boards).values({ id: "board-1", name: "board", creatorId: "owner", isPublic: true }); const caller = queryCacheRouter.createCaller({ db, deviceType: undefined, session: null }); - await caller.setItem({ boardId: "board-1", key: "query-key", value: "cached-value" }); + await caller.setItem({ boardId: "board-1", value: "cached-value" }); - expect(redisMock.calls.at(-1)).toMatchObject({ - userId: "anonymous", - boardId: "board-1", - key: "query-key", - ttlMs: queryCacheRetentionMs, - maxValueBytes: queryCacheMaxValueBytes, - }); + expect(redisMock.setQueryCacheAsync).toHaveBeenCalledWith( + "anonymous", + "board-1", + "cached-value", + expect.any(Number), + queryCacheMaxValueBytes, + ); }); - test("scopes signed-in cache entries by user and rejects oversized values", async () => { + test("rejects oversized values", async () => { const db = createDb(); const session = createSession("user-1"); await insertUserAsync(db, session.user.id); @@ -161,34 +138,9 @@ describe("queryCacheRouter", () => { const caller = queryCacheRouter.createCaller({ db, deviceType: undefined, session }); const value = "x".repeat(queryCacheMaxValueBytes + 1); - await expect(caller.setItem({ boardId: "board-1", key: "query-key", value })).resolves.toEqual({ + await expect(caller.setItem({ boardId: "board-1", value })).resolves.toEqual({ stored: false, }); - expect(redisMock.calls.at(-1)).toMatchObject({ - userId: "user-1", - boardId: "board-1", - ttlMs: queryCacheRetentionMs, - maxValueBytes: queryCacheMaxValueBytes, - }); - }); - - test("expires values through the Redis TTL path", async () => { - vi.useFakeTimers(); - vi.setSystemTime(new Date("2026-01-01T00:00:00.000Z")); - - const db = createDb(); - const session = createSession("user-1"); - await insertUserAsync(db, session.user.id); - await db.insert(boards).values({ id: "board-1", name: "board", creatorId: session.user.id, isPublic: false }); - - const caller = queryCacheRouter.createCaller({ db, deviceType: undefined, session }); - - await caller.setItem({ boardId: "board-1", key: "query-key", value: "cached-value" }); - await expect(caller.getItem({ boardId: "board-1", key: "query-key" })).resolves.toBe("cached-value"); - - await vi.advanceTimersByTimeAsync(queryCacheRetentionMs + 1); - - await expect(caller.getItem({ boardId: "board-1", key: "query-key" })).resolves.toBeNull(); }); }); diff --git a/packages/boards/src/context.tsx b/packages/boards/src/context.tsx index c2ccc690a..b482fbe0c 100644 --- a/packages/boards/src/context.tsx +++ b/packages/boards/src/context.tsx @@ -20,16 +20,7 @@ export const BoardProvider = ({ }: PropsWithChildren<{ initialBoard: RouterOutputs["board"]["getBoardByName"]; }>) => { - const { data } = clientApi.board.getBoardByName.useQuery( - { name: initialBoard.name }, - { - initialData: initialBoard, - refetchOnWindowFocus: false, - refetchOnReconnect: false, - }, - ); - - setActiveQueryCacheBoardId(data.id); + const { data } = clientApi.board.getBoardByName.useQuery({ name: initialBoard.name }, { initialData: initialBoard }); // Update the board name so it can be used within updateBoard method updateBoardName(initialBoard.name); diff --git a/packages/common/src/date.ts b/packages/common/src/date.ts index de9ecadaf..5d46e45e1 100644 --- a/packages/common/src/date.ts +++ b/packages/common/src/date.ts @@ -17,6 +17,12 @@ export function formatDuration(milliseconds: number) { return `${minutes}:${String(seconds).padStart(2, "0")}`; } +export const toValidDate = (value: unknown): Date | null => { + if (value == null || typeof value === "boolean") return null; + const date = value instanceof Date ? value : new Date(value as string | number); + return Number.isFinite(date.getTime()) ? date : null; +}; + export const isDateWithin = (date: Date, relativeDate: string): boolean => { if (relativeDate.length < 2) { throw new Error("Relative date must be at least 2 characters long"); diff --git a/packages/cron-jobs/src/index.ts b/packages/cron-jobs/src/index.ts index 1f0cb8577..d9e42493e 100644 --- a/packages/cron-jobs/src/index.ts +++ b/packages/cron-jobs/src/index.ts @@ -1,32 +1,6 @@ import { analyticsJob } from "./jobs/analytics"; -import { dockerContainersJob } from "./jobs/docker"; import { iconsUpdaterJob } from "./jobs/icons-updater"; -import { dnsHoleJob } from "./jobs/integrations/dns-hole"; -import { downloadsJob } from "./jobs/integrations/downloads"; -import { - firewallCpuJob, - firewallInterfacesJob, - firewallMemoryJob, - firewallVersionJob, -} from "./jobs/integrations/firewall"; -import { healthMonitoringJob } from "./jobs/integrations/health-monitoring"; -import { smartHomeEntityStateJob } from "./jobs/integrations/home-assistant"; -import { indexerManagerJob } from "./jobs/integrations/indexer-manager"; -import { mediaOrganizerJob } from "./jobs/integrations/media-organizer"; -import { mediaRequestListJob, mediaRequestStatsJob } from "./jobs/integrations/media-requests"; -import { mediaServerJob } from "./jobs/integrations/media-server"; -import { mediaTranscodingJob } from "./jobs/integrations/media-transcoding"; -import { networkControllerJob } from "./jobs/integrations/network-controller"; -import { refreshNotificationsJob } from "./jobs/integrations/notifications"; -import { timetableJob } from "./jobs/integrations/timetable"; -import { tracearrJob } from "./jobs/integrations/tracearr"; -import { umamiActiveVisitorsJob } from "./jobs/integrations/umami"; -import { vpnJob } from "./jobs/integrations/vpn"; -import { minecraftServerStatusJob } from "./jobs/minecraft-server-status"; import { pingJob } from "./jobs/ping"; -import { rssFeedsJob } from "./jobs/rss-feeds"; -import { updateCheckerJob } from "./jobs/update-checker"; -import { weatherJob } from "./jobs/weather"; import { createCronJobGroup } from "./lib"; const getJobGroup = () => { @@ -34,35 +8,9 @@ const getJobGroup = () => { analytics: analyticsJob, iconsUpdater: iconsUpdaterJob, ping: pingJob, - smartHomeEntityState: smartHomeEntityStateJob, - mediaServer: mediaServerJob, - mediaOrganizer: mediaOrganizerJob, - downloads: downloadsJob, - dnsHole: dnsHoleJob, - mediaRequestStats: mediaRequestStatsJob, - mediaRequestList: mediaRequestListJob, - rssFeeds: rssFeedsJob, - indexerManager: indexerManagerJob, - healthMonitoring: healthMonitoringJob, - updateChecker: updateCheckerJob, - mediaTranscoding: mediaTranscodingJob, - minecraftServerStatus: minecraftServerStatusJob, - dockerContainers: dockerContainersJob, - networkController: networkControllerJob, - firewallCpu: firewallCpuJob, - firewallMemory: firewallMemoryJob, - firewallVersion: firewallVersionJob, - firewallInterfaces: firewallInterfacesJob, - refreshNotifications: refreshNotificationsJob, - weather: weatherJob, - timetable: timetableJob, - tracearr: tracearrJob, - umamiActiveVisitors: umamiActiveVisitorsJob, - vpn: vpnJob, }); }; -// Add global type augmentation for homarr declare global { var cronJobs: ReturnType | undefined; } diff --git a/packages/cron-jobs/src/jobs/docker.ts b/packages/cron-jobs/src/jobs/docker.ts deleted file mode 100644 index 963f395bf..000000000 --- a/packages/cron-jobs/src/jobs/docker.ts +++ /dev/null @@ -1,31 +0,0 @@ -import SuperJSON from "superjson"; - -import { createLogger } from "@homarr/core/infrastructure/logs"; -import { ErrorWithMetadata } from "@homarr/core/infrastructure/logs/error"; -import { EVERY_MINUTE } from "@homarr/cron-jobs-core/expressions"; -import { db, eq } from "@homarr/db"; -import { items } from "@homarr/db/schema"; -import { dockerContainersRequestHandler } from "@homarr/request-handler/docker"; - -import type { WidgetComponentProps } from "../../../widgets"; -import { createCronJob } from "../lib"; - -const logger = createLogger({ module: "dockerJobs" }); - -export const dockerContainersJob = createCronJob("dockerContainers", EVERY_MINUTE).withCallback(async () => { - const dockerItems = await db.query.items.findMany({ - where: eq(items.kind, "dockerContainers"), - }); - - await Promise.allSettled( - dockerItems.map(async (item) => { - try { - const options = SuperJSON.parse["options"]>(item.options); - const innerHandler = dockerContainersRequestHandler.handler(options); - await innerHandler.getCachedOrUpdatedDataAsync({ forceUpdate: true }); - } catch (error) { - logger.error(new ErrorWithMetadata("Failed to update Docker container status", { item }, { cause: error })); - } - }), - ); -}); diff --git a/packages/cron-jobs/src/jobs/integrations/dns-hole.ts b/packages/cron-jobs/src/jobs/integrations/dns-hole.ts deleted file mode 100644 index a78b28e08..000000000 --- a/packages/cron-jobs/src/jobs/integrations/dns-hole.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { EVERY_MINUTE } from "@homarr/cron-jobs-core/expressions"; -import { dnsHoleRequestHandler } from "@homarr/request-handler/dns-hole"; -import { createRequestIntegrationJobHandler } from "@homarr/request-handler/lib/cached-request-integration-job-handler"; - -import { createCronJob } from "../../lib"; - -export const dnsHoleJob = createCronJob("dnsHole", EVERY_MINUTE).withCallback( - createRequestIntegrationJobHandler(dnsHoleRequestHandler.handler, { - widgetKinds: ["dnsHoleSummary", "dnsHoleControls"], - getInput: { - dnsHoleSummary: () => ({}), - dnsHoleControls: () => ({}), - }, - }), -); diff --git a/packages/cron-jobs/src/jobs/integrations/downloads.ts b/packages/cron-jobs/src/jobs/integrations/downloads.ts deleted file mode 100644 index 54eb476dc..000000000 --- a/packages/cron-jobs/src/jobs/integrations/downloads.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { EVERY_5_SECONDS } from "@homarr/cron-jobs-core/expressions"; -import { downloadClientRequestHandler } from "@homarr/request-handler/downloads"; -import { createRequestIntegrationJobHandler } from "@homarr/request-handler/lib/cached-request-integration-job-handler"; - -import { createCronJob } from "../../lib"; - -export const downloadsJob = createCronJob("downloads", EVERY_5_SECONDS).withCallback( - createRequestIntegrationJobHandler(downloadClientRequestHandler.handler, { - widgetKinds: ["downloads"], - getInput: { - downloads: (options) => ({ - limit: options.limitPerIntegration, - }), - }, - }), -); diff --git a/packages/cron-jobs/src/jobs/integrations/firewall.ts b/packages/cron-jobs/src/jobs/integrations/firewall.ts deleted file mode 100644 index 50637a642..000000000 --- a/packages/cron-jobs/src/jobs/integrations/firewall.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { EVERY_5_SECONDS, EVERY_30_SECONDS, EVERY_HOUR, EVERY_MINUTE } from "@homarr/cron-jobs-core/expressions"; -import { - firewallCpuRequestHandler, - firewallInterfacesRequestHandler, - firewallMemoryRequestHandler, - firewallVersionRequestHandler, -} from "@homarr/request-handler/firewall"; -import { createRequestIntegrationJobHandler } from "@homarr/request-handler/lib/cached-request-integration-job-handler"; - -import { createCronJob } from "../../lib"; - -export const firewallCpuJob = createCronJob("firewallCpu", EVERY_5_SECONDS).withCallback( - createRequestIntegrationJobHandler(firewallCpuRequestHandler.handler, { - widgetKinds: ["firewall"], - getInput: { - firewall: () => ({}), - }, - }), -); - -export const firewallMemoryJob = createCronJob("firewallMemory", EVERY_MINUTE).withCallback( - createRequestIntegrationJobHandler(firewallMemoryRequestHandler.handler, { - widgetKinds: ["firewall"], - getInput: { - firewall: () => ({}), - }, - }), -); - -export const firewallInterfacesJob = createCronJob("firewallInterfaces", EVERY_30_SECONDS).withCallback( - createRequestIntegrationJobHandler(firewallInterfacesRequestHandler.handler, { - widgetKinds: ["firewall"], - getInput: { - firewall: () => ({}), - }, - }), -); - -export const firewallVersionJob = createCronJob("firewallVersion", EVERY_HOUR).withCallback( - createRequestIntegrationJobHandler(firewallVersionRequestHandler.handler, { - widgetKinds: ["firewall"], - getInput: { - firewall: () => ({}), - }, - }), -); diff --git a/packages/cron-jobs/src/jobs/integrations/health-monitoring.ts b/packages/cron-jobs/src/jobs/integrations/health-monitoring.ts deleted file mode 100644 index 4d09801d2..000000000 --- a/packages/cron-jobs/src/jobs/integrations/health-monitoring.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { EVERY_5_SECONDS } from "@homarr/cron-jobs-core/expressions"; -import { coolifyRequestHandler } from "@homarr/request-handler/coolify"; -import { clusterInfoRequestHandler, systemInfoRequestHandler } from "@homarr/request-handler/health-monitoring"; -import { createRequestIntegrationJobHandler } from "@homarr/request-handler/lib/cached-request-integration-job-handler"; - -import { createCronJob } from "../../lib"; - -export const healthMonitoringJob = createCronJob("healthMonitoring", EVERY_5_SECONDS).withCallback( - createRequestIntegrationJobHandler( - (integration, itemOptions: Record) => { - const { kind } = integration; - - if (kind === "beszel") { - return null as never; - } - if (kind !== "proxmox" && kind !== "mock" && kind !== "coolify") { - return systemInfoRequestHandler.handler({ ...integration, kind }, itemOptions); - } - if (kind === "coolify") { - return coolifyRequestHandler.handler({ ...integration, kind }, itemOptions); - } - return clusterInfoRequestHandler.handler({ ...integration, kind }, itemOptions); - }, - { - widgetKinds: ["healthMonitoring", "systemResources", "coolify"], - getInput: { - healthMonitoring: () => ({}), - systemResources: () => ({}), - coolify: () => ({}), - }, - }, - ), -); diff --git a/packages/cron-jobs/src/jobs/integrations/home-assistant.ts b/packages/cron-jobs/src/jobs/integrations/home-assistant.ts deleted file mode 100644 index 2723391a7..000000000 --- a/packages/cron-jobs/src/jobs/integrations/home-assistant.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { EVERY_MINUTE } from "@homarr/cron-jobs-core/expressions"; -import { createRequestIntegrationJobHandler } from "@homarr/request-handler/lib/cached-request-integration-job-handler"; -import { smartHomeEntityStateRequestHandler } from "@homarr/request-handler/smart-home-entity-state"; - -import { createCronJob } from "../../lib"; - -export const smartHomeEntityStateJob = createCronJob("smartHomeEntityState", EVERY_MINUTE).withCallback( - createRequestIntegrationJobHandler(smartHomeEntityStateRequestHandler.handler, { - widgetKinds: ["smartHome-entityState"], - getInput: { - "smartHome-entityState": (options) => ({ - entityId: options.entityId, - }), - }, - }), -); diff --git a/packages/cron-jobs/src/jobs/integrations/indexer-manager.ts b/packages/cron-jobs/src/jobs/integrations/indexer-manager.ts deleted file mode 100644 index 11da75508..000000000 --- a/packages/cron-jobs/src/jobs/integrations/indexer-manager.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { EVERY_5_MINUTES } from "@homarr/cron-jobs-core/expressions"; -import { indexerManagerRequestHandler } from "@homarr/request-handler/indexer-manager"; -import { createRequestIntegrationJobHandler } from "@homarr/request-handler/lib/cached-request-integration-job-handler"; - -import { createCronJob } from "../../lib"; - -export const indexerManagerJob = createCronJob("indexerManager", EVERY_5_MINUTES).withCallback( - createRequestIntegrationJobHandler(indexerManagerRequestHandler.handler, { - widgetKinds: ["indexerManager"], - getInput: { - indexerManager: () => ({}), - }, - }), -); diff --git a/packages/cron-jobs/src/jobs/integrations/media-organizer.ts b/packages/cron-jobs/src/jobs/integrations/media-organizer.ts deleted file mode 100644 index a84696da9..000000000 --- a/packages/cron-jobs/src/jobs/integrations/media-organizer.ts +++ /dev/null @@ -1,36 +0,0 @@ -import dayjs from "dayjs"; - -import { EVERY_MINUTE } from "@homarr/cron-jobs-core/expressions"; -import { calendarMonthRequestHandler } from "@homarr/request-handler/calendar"; -import { createRequestIntegrationJobHandler } from "@homarr/request-handler/lib/cached-request-integration-job-handler"; - -import { createCronJob } from "../../lib"; - -export const mediaOrganizerJob = createCronJob("mediaOrganizer", EVERY_MINUTE).withCallback( - createRequestIntegrationJobHandler(calendarMonthRequestHandler.handler, { - widgetKinds: ["calendar"], - getInput: { - // Request handler will run for all specified months - calendar: (options) => { - const inputs = []; - - const startOffset = -Number(options.filterPastMonths); - const endOffset = Number(options.filterFutureMonths); - - for (let offsetMonths = startOffset; offsetMonths <= endOffset; offsetMonths++) { - const year = dayjs().subtract(offsetMonths, "months").year(); - const month = dayjs().subtract(offsetMonths, "months").month(); - - inputs.push({ - year, - month, - releaseType: options.releaseType, - showUnmonitored: options.showUnmonitored, - }); - } - - return inputs; - }, - }, - }), -); diff --git a/packages/cron-jobs/src/jobs/integrations/media-requests.ts b/packages/cron-jobs/src/jobs/integrations/media-requests.ts deleted file mode 100644 index ededb6141..000000000 --- a/packages/cron-jobs/src/jobs/integrations/media-requests.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { EVERY_MINUTE } from "@homarr/cron-jobs-core/expressions"; -import { createRequestIntegrationJobHandler } from "@homarr/request-handler/lib/cached-request-integration-job-handler"; -import { mediaRequestListRequestHandler } from "@homarr/request-handler/media-request-list"; -import { mediaRequestStatsRequestHandler } from "@homarr/request-handler/media-request-stats"; - -import { createCronJob } from "../../lib"; - -export const mediaRequestStatsJob = createCronJob("mediaRequestStats", EVERY_MINUTE).withCallback( - createRequestIntegrationJobHandler(mediaRequestStatsRequestHandler.handler, { - widgetKinds: ["mediaRequests-requestStats"], - getInput: { - "mediaRequests-requestStats": () => ({}), - }, - }), -); - -export const mediaRequestListJob = createCronJob("mediaRequestList", EVERY_MINUTE).withCallback( - createRequestIntegrationJobHandler(mediaRequestListRequestHandler.handler, { - widgetKinds: ["mediaRequests-requestList"], - getInput: { - "mediaRequests-requestList": () => ({}), - }, - }), -); diff --git a/packages/cron-jobs/src/jobs/integrations/media-server.ts b/packages/cron-jobs/src/jobs/integrations/media-server.ts deleted file mode 100644 index 099ea53ae..000000000 --- a/packages/cron-jobs/src/jobs/integrations/media-server.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { EVERY_5_SECONDS } from "@homarr/cron-jobs-core/expressions"; -import { createRequestIntegrationJobHandler } from "@homarr/request-handler/lib/cached-request-integration-job-handler"; -import { mediaServerRequestHandler } from "@homarr/request-handler/media-server"; - -import { createCronJob } from "../../lib"; - -export const mediaServerJob = createCronJob("mediaServer", EVERY_5_SECONDS).withCallback( - createRequestIntegrationJobHandler(mediaServerRequestHandler.handler, { - widgetKinds: ["mediaServer"], - getInput: { - mediaServer: ({ showOnlyPlaying }) => ({ showOnlyPlaying }), - }, - }), -); diff --git a/packages/cron-jobs/src/jobs/integrations/media-transcoding.ts b/packages/cron-jobs/src/jobs/integrations/media-transcoding.ts deleted file mode 100644 index c039dc250..000000000 --- a/packages/cron-jobs/src/jobs/integrations/media-transcoding.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { EVERY_5_MINUTES } from "@homarr/cron-jobs-core/expressions"; -import { createRequestIntegrationJobHandler } from "@homarr/request-handler/lib/cached-request-integration-job-handler"; -import { mediaTranscodingRequestHandler } from "@homarr/request-handler/media-transcoding"; - -import { createCronJob } from "../../lib"; - -export const mediaTranscodingJob = createCronJob("mediaTranscoding", EVERY_5_MINUTES).withCallback( - createRequestIntegrationJobHandler(mediaTranscodingRequestHandler.handler, { - widgetKinds: ["mediaTranscoding"], - getInput: { - mediaTranscoding: () => ({ pageOffset: 0, pageSize: 10 }), - }, - }), -); diff --git a/packages/cron-jobs/src/jobs/integrations/network-controller.ts b/packages/cron-jobs/src/jobs/integrations/network-controller.ts deleted file mode 100644 index 5d9354588..000000000 --- a/packages/cron-jobs/src/jobs/integrations/network-controller.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { EVERY_MINUTE } from "@homarr/cron-jobs-core/expressions"; -import { createRequestIntegrationJobHandler } from "@homarr/request-handler/lib/cached-request-integration-job-handler"; -import { networkControllerRequestHandler } from "@homarr/request-handler/network-controller"; - -import { createCronJob } from "../../lib"; - -export const networkControllerJob = createCronJob("networkController", EVERY_MINUTE).withCallback( - createRequestIntegrationJobHandler(networkControllerRequestHandler.handler, { - widgetKinds: ["networkControllerSummary"], - getInput: { - networkControllerSummary: () => ({}), - }, - }), -); diff --git a/packages/cron-jobs/src/jobs/integrations/notifications.ts b/packages/cron-jobs/src/jobs/integrations/notifications.ts deleted file mode 100644 index 0f544995f..000000000 --- a/packages/cron-jobs/src/jobs/integrations/notifications.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { EVERY_5_MINUTES } from "@homarr/cron-jobs-core/expressions"; -import { createRequestIntegrationJobHandler } from "@homarr/request-handler/lib/cached-request-integration-job-handler"; -import { notificationsRequestHandler } from "@homarr/request-handler/notifications"; - -import { createCronJob } from "../../lib"; - -export const refreshNotificationsJob = createCronJob("refreshNotifications", EVERY_5_MINUTES).withCallback( - createRequestIntegrationJobHandler(notificationsRequestHandler.handler, { - widgetKinds: ["notifications"], - getInput: { - notifications: (options) => options, - }, - }), -); diff --git a/packages/cron-jobs/src/jobs/integrations/timetable.ts b/packages/cron-jobs/src/jobs/integrations/timetable.ts deleted file mode 100644 index b3964dc38..000000000 --- a/packages/cron-jobs/src/jobs/integrations/timetable.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { EVERY_5_MINUTES } from "@homarr/cron-jobs-core/expressions"; -import { createRequestIntegrationJobHandler } from "@homarr/request-handler/lib/cached-request-integration-job-handler"; -import { timetableGetTimetableRequestHandler } from "@homarr/request-handler/timetable"; - -import { createCronJob } from "../../lib"; - -export const timetableJob = createCronJob("timetable", EVERY_5_MINUTES).withCallback( - createRequestIntegrationJobHandler(timetableGetTimetableRequestHandler.handler, { - widgetKinds: ["timetable"], - getInput: { - timetable: (options) => - options.station - ? { - stationId: options.station.value, - limit: 10, - } - : [], - }, - }), -); diff --git a/packages/cron-jobs/src/jobs/integrations/tracearr.ts b/packages/cron-jobs/src/jobs/integrations/tracearr.ts deleted file mode 100644 index f80130374..000000000 --- a/packages/cron-jobs/src/jobs/integrations/tracearr.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { EVERY_5_SECONDS } from "@homarr/cron-jobs-core/expressions"; -import { createRequestIntegrationJobHandler } from "@homarr/request-handler/lib/cached-request-integration-job-handler"; -import { tracearrRequestHandler } from "@homarr/request-handler/tracearr"; - -import { createCronJob } from "../../lib"; - -export const tracearrJob = createCronJob("tracearr", EVERY_5_SECONDS).withCallback( - createRequestIntegrationJobHandler(tracearrRequestHandler.handler, { - widgetKinds: ["tracearr"], - getInput: { - tracearr: () => ({}), - }, - }), -); diff --git a/packages/cron-jobs/src/jobs/integrations/umami.ts b/packages/cron-jobs/src/jobs/integrations/umami.ts deleted file mode 100644 index c50a3c604..000000000 --- a/packages/cron-jobs/src/jobs/integrations/umami.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { EVERY_30_SECONDS } from "@homarr/cron-jobs-core/expressions"; -import { createRequestIntegrationJobHandler } from "@homarr/request-handler/lib/cached-request-integration-job-handler"; -import { umamiActiveVisitorsRequestHandler } from "@homarr/request-handler/umami"; - -import { createCronJob } from "../../lib"; - -export const umamiActiveVisitorsJob = createCronJob("umamiActiveVisitors", EVERY_30_SECONDS).withCallback( - createRequestIntegrationJobHandler(umamiActiveVisitorsRequestHandler.handler, { - widgetKinds: ["umami"], - getInput: { - umami: (options) => (options.websiteId ? { websiteId: options.websiteId } : []), - }, - }), -); diff --git a/packages/cron-jobs/src/jobs/integrations/vpn.ts b/packages/cron-jobs/src/jobs/integrations/vpn.ts deleted file mode 100644 index b6495b359..000000000 --- a/packages/cron-jobs/src/jobs/integrations/vpn.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { EVERY_30_SECONDS } from "@homarr/cron-jobs-core/expressions"; -import { vpnSummaryHandler } from "@homarr/request-handler/vpn"; -import { createRequestIntegrationJobHandler } from "@homarr/request-handler/lib/cached-request-integration-job-handler"; - -import { createCronJob } from "../../lib"; - -export const vpnJob = createCronJob("vpn", EVERY_30_SECONDS).withCallback( - createRequestIntegrationJobHandler(vpnSummaryHandler.handler, { - widgetKinds: ["vpn"], - getInput: { - vpn: () => ({}), - }, - }), -); diff --git a/packages/cron-jobs/src/jobs/minecraft-server-status.ts b/packages/cron-jobs/src/jobs/minecraft-server-status.ts deleted file mode 100644 index e6c084702..000000000 --- a/packages/cron-jobs/src/jobs/minecraft-server-status.ts +++ /dev/null @@ -1,25 +0,0 @@ -import SuperJSON from "superjson"; - -import { EVERY_5_MINUTES } from "@homarr/cron-jobs-core/expressions"; -import { db, eq } from "@homarr/db"; -import { items } from "@homarr/db/schema"; -import { minecraftServerStatusRequestHandler } from "@homarr/request-handler/minecraft-server-status"; - -import type { WidgetComponentProps } from "../../../widgets/src"; -import { createCronJob } from "../lib"; - -export const minecraftServerStatusJob = createCronJob("minecraftServerStatus", EVERY_5_MINUTES).withCallback( - async () => { - const dbItems = await db.query.items.findMany({ - where: eq(items.kind, "minecraftServerStatus"), - }); - - await Promise.allSettled( - dbItems.map(async (item) => { - const options = SuperJSON.parse["options"]>(item.options); - const innerHandler = minecraftServerStatusRequestHandler.handler(options); - await innerHandler.getCachedOrUpdatedDataAsync({ forceUpdate: true }); - }), - ); - }, -); diff --git a/packages/cron-jobs/src/jobs/rss-feeds.ts b/packages/cron-jobs/src/jobs/rss-feeds.ts deleted file mode 100644 index 6a497a043..000000000 --- a/packages/cron-jobs/src/jobs/rss-feeds.ts +++ /dev/null @@ -1,39 +0,0 @@ -import SuperJSON from "superjson"; - -import { createLogger } from "@homarr/core/infrastructure/logs"; -import { ErrorWithMetadata } from "@homarr/core/infrastructure/logs/error"; -import { EVERY_10_MINUTES } from "@homarr/cron-jobs-core/expressions"; -import { db, eq } from "@homarr/db"; -import { items } from "@homarr/db/schema"; -import { rssFeedsRequestHandler } from "@homarr/request-handler/rss-feeds"; - -// This import is done that way to avoid circular dependencies. -import type { WidgetComponentProps } from "../../../widgets"; -import { createCronJob } from "../lib"; - -const logger = createLogger({ module: "rssFeedsJobs" }); - -export const rssFeedsJob = createCronJob("rssFeeds", EVERY_10_MINUTES).withCallback(async () => { - const rssItems = await db.query.items.findMany({ - where: eq(items.kind, "rssFeed"), - }); - - const itemOptions = rssItems.map((item) => SuperJSON.parse["options"]>(item.options)); - - for (const option of itemOptions) { - const maxAmountPosts = typeof option.maximumAmountPosts === "number" ? option.maximumAmountPosts : 100; - for (const url of option.feedUrls) { - try { - const innerHandler = rssFeedsRequestHandler.handler({ - url, - count: maxAmountPosts, - }); - await innerHandler.getCachedOrUpdatedDataAsync({ - forceUpdate: true, - }); - } catch (error) { - logger.error(new ErrorWithMetadata("Failed to update RSS feed", { url }, { cause: error })); - } - } - } -}); diff --git a/packages/cron-jobs/src/jobs/update-checker.ts b/packages/cron-jobs/src/jobs/update-checker.ts deleted file mode 100644 index eea68d07b..000000000 --- a/packages/cron-jobs/src/jobs/update-checker.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { EVERY_DAY } from "@homarr/cron-jobs-core/expressions"; -import { updateCheckerRequestHandler } from "@homarr/request-handler/update-checker"; - -import { createCronJob } from "../lib"; - -export const updateCheckerJob = createCronJob("updateChecker", EVERY_DAY, { - runOnStart: true, -}).withCallback(async () => { - const handler = updateCheckerRequestHandler.handler({}); - await handler.getCachedOrUpdatedDataAsync({}); -}); diff --git a/packages/cron-jobs/src/jobs/weather.ts b/packages/cron-jobs/src/jobs/weather.ts deleted file mode 100644 index 1b23ade4c..000000000 --- a/packages/cron-jobs/src/jobs/weather.ts +++ /dev/null @@ -1,36 +0,0 @@ -import SuperJSON from "superjson"; - -import { createLogger } from "@homarr/core/infrastructure/logs"; -import { ErrorWithMetadata } from "@homarr/core/infrastructure/logs/error"; -import { EVERY_10_MINUTES } from "@homarr/cron-jobs-core/expressions"; -import { db, eq } from "@homarr/db"; -import { items } from "@homarr/db/schema"; -import { weatherRequestHandler } from "@homarr/request-handler/weather"; - -import type { WidgetComponentProps } from "../../../widgets"; -import { createCronJob } from "../lib"; - -const logger = createLogger({ module: "weatherJobs" }); - -export const weatherJob = createCronJob("weather", EVERY_10_MINUTES).withCallback(async () => { - const weatherItems = await db.query.items.findMany({ - where: eq(items.kind, "weather"), - }); - - const parsedItems = weatherItems.map((item) => ({ - id: item.id, - options: SuperJSON.parse["options"]>(item.options), - })); - - for (const item of parsedItems) { - try { - const innerHandler = weatherRequestHandler.handler({ - longitude: item.options.location.longitude, - latitude: item.options.location.latitude, - }); - await innerHandler.getCachedOrUpdatedDataAsync({ forceUpdate: true }); - } catch (error) { - logger.error(new ErrorWithMetadata("Failed to update weather", { id: item.id }, { cause: error })); - } - } -}); diff --git a/packages/db/migrations/custom/0000_release_widget_provider_to_options.ts b/packages/db/migrations/custom/0000_release_widget_provider_to_options.ts index 951831482..85f39bcbc 100644 --- a/packages/db/migrations/custom/0000_release_widget_provider_to_options.ts +++ b/packages/db/migrations/custom/0000_release_widget_provider_to_options.ts @@ -1,8 +1,7 @@ import SuperJSON from "superjson"; import { createId } from "@homarr/common"; -import type { IntegrationKind } from "@homarr/definitions"; -import { getIntegrationKindsByCategory } from "@homarr/definitions"; +import { releaseProviderKinds } from "@homarr/definitions"; import { eq } from "../.."; import type { Database } from "../.."; @@ -13,19 +12,6 @@ export async function migrateReleaseWidgetProviderToOptionsAsync(db: Database) { where: (items, { eq }) => eq(items.kind, "releases"), }); - const integrationKinds = getIntegrationKindsByCategory("releasesProvider"); - const providerIntegrations = await db.query.integrations.findMany({ - where: (integrations, { inArray }) => inArray(integrations.kind, integrationKinds), - columns: { - id: true, - kind: true, - }, - }); - - const providerIntegrationMap = new Map( - providerIntegrations.map((integration) => [integration.kind, integration.id]), - ); - const updates: { id: string; options: object; @@ -37,18 +23,19 @@ export async function migrateReleaseWidgetProviderToOptionsAsync(db: Database) { if (options.repositories.length === 0) continue; if (!options.repositories.some((repository) => "providerKey" in repository)) continue; - const updatedRepositories = options.repositories.map( - ({ providerKey, ...otherFields }: { providerKey: string; [key: string]: unknown }) => { - // Ensure providerKey is camelCase + const updatedRepositories = options.repositories + .map(({ providerKey, ...otherFields }: { providerKey: string; [key: string]: unknown }) => { const provider = providerKey.charAt(0).toLowerCase() + providerKey.slice(1); + const matchedProvider = releaseProviderKinds.find((kind) => kind === provider); + if (!matchedProvider) return null; return { id: createId(), - providerIntegrationId: providerIntegrationMap.get(provider as IntegrationKind) ?? null, + provider: matchedProvider, ...otherFields, }; - }, - ); + }) + .filter((repo) => repo !== null); updates.push({ id: item.id, @@ -68,5 +55,5 @@ export async function migrateReleaseWidgetProviderToOptionsAsync(db: Database) { .where(eq(items.id, update.id)); } - console.log(`Migrated release widget providers to integrations count="${updates.length}"`); + console.log(`Migrated release widget providers to options count="${updates.length}"`); } diff --git a/packages/db/migrations/custom/0003_remove_widget_only_integrations.ts b/packages/db/migrations/custom/0003_remove_widget_only_integrations.ts new file mode 100644 index 000000000..0c7a04d6c --- /dev/null +++ b/packages/db/migrations/custom/0003_remove_widget_only_integrations.ts @@ -0,0 +1,100 @@ +import SuperJSON from "superjson"; + +import { eq, inArray } from "../.."; +import type { Database } from "../.."; +import { integrationItems, integrations, items } from "../../schema"; + +const removedIntegrationKinds = [ + "github", + "dockerHub", + "gitlab", + "npm", + "codeberg", + "linuxServerIO", + "gitHubContainerRegistry", + "quay", + "searchCh", +] as const; + +type RemovedIntegrationKind = (typeof removedIntegrationKinds)[number]; + +const releaseProviderKinds = [ + "github", + "dockerHub", + "gitlab", + "npm", + "codeberg", + "linuxServerIO", + "gitHubContainerRegistry", + "quay", +] as const; + +const isReleaseProviderKind = (kind: RemovedIntegrationKind): kind is (typeof releaseProviderKinds)[number] => + releaseProviderKinds.some((provider) => provider === kind); + +export async function migrateWidgetOnlyIntegrationsToOptionsAsync(db: Database) { + const oldIntegrations = await db.query.integrations.findMany({ + columns: { id: true, kind: true, url: true }, + }); + const oldIntegrationById = new Map( + oldIntegrations + .filter((integration) => removedIntegrationKinds.includes(integration.kind as RemovedIntegrationKind)) + .map((integration) => [ + integration.id, + { kind: integration.kind as RemovedIntegrationKind, url: integration.url }, + ]), + ); + + if (oldIntegrationById.size === 0) return; + + const oldIntegrationIds = Array.from(oldIntegrationById.keys()); + const oldIntegrationItems = await db.query.integrationItems.findMany(); + const oldIntegrationItemByItemId = new Map( + oldIntegrationItems + .filter((item) => oldIntegrationById.has(item.integrationId)) + .map((item) => [item.itemId, oldIntegrationById.get(item.integrationId)]), + ); + const existingItems = await db.query.items.findMany({ + where: (items, { inArray }) => inArray(items.kind, ["releases", "timetable"]), + }); + + const updatedItems: { id: string; options: object }[] = []; + + for (const item of existingItems) { + const options = SuperJSON.parse>(item.options); + const linkedIntegration = oldIntegrationItemByItemId.get(item.id); + + if (item.kind === "releases" && Array.isArray(options.repositories)) { + const repositories = options.repositories.map((repository) => { + if (typeof repository !== "object" || repository === null) return repository; + const { providerIntegrationId, ...rest } = repository as Record; + const provider = + typeof providerIntegrationId === "string" ? oldIntegrationById.get(providerIntegrationId) : undefined; + const existingProviderUrl = + typeof rest.providerUrl === "string" && rest.providerUrl.length > 0 ? rest.providerUrl : undefined; + return { + ...rest, + provider: provider && isReleaseProviderKind(provider.kind) ? provider.kind : rest.provider, + providerUrl: provider?.url ?? existingProviderUrl, + }; + }); + updatedItems.push({ id: item.id, options: { ...options, repositories } }); + } + + if (item.kind === "timetable" && linkedIntegration?.kind === "searchCh") { + updatedItems.push({ id: item.id, options: { ...options, baseUrl: linkedIntegration.url } }); + } + } + + for (const update of updatedItems) { + await db + .update(items) + .set({ options: SuperJSON.stringify(update.options) }) + .where(eq(items.id, update.id)); + } + + await db.delete(integrationItems).where(inArray(integrationItems.integrationId, oldIntegrationIds)); + await db.delete(integrations).where(inArray(integrations.id, oldIntegrationIds)); + + console.log(`Migrated widget-only integrations to widget options count="${updatedItems.length}"`); +} diff --git a/packages/db/migrations/custom/index.ts b/packages/db/migrations/custom/index.ts index a6a002733..8bcc93c32 100644 --- a/packages/db/migrations/custom/index.ts +++ b/packages/db/migrations/custom/index.ts @@ -2,9 +2,11 @@ import type { Database } from "../.."; import { migrateReleaseWidgetProviderToOptionsAsync } from "./0000_release_widget_provider_to_options"; import { migrateOpnsenseCredentialsAsync } from "./0001_opnsense_credentials"; import { migrateAppWidgetShowDescriptionTooltipToDisplayModeAsync } from "./0002_app_widget_show_description_tooltip_to_display_mode"; +import { migrateWidgetOnlyIntegrationsToOptionsAsync } from "./0003_remove_widget_only_integrations"; export const applyCustomMigrationsAsync = async (db: Database) => { await migrateReleaseWidgetProviderToOptionsAsync(db); await migrateOpnsenseCredentialsAsync(db); await migrateAppWidgetShowDescriptionTooltipToDisplayModeAsync(db); + await migrateWidgetOnlyIntegrationsToOptionsAsync(db); }; diff --git a/packages/db/schema/index.ts b/packages/db/schema/index.ts index 59566631f..8c3b7f4fc 100644 --- a/packages/db/schema/index.ts +++ b/packages/db/schema/index.ts @@ -51,6 +51,7 @@ export const { cronJobConfigurations, customWidgetDefinitions, customWidgetSecrets, + widgetSecrets, } = schema; export type User = InferSelectModel; diff --git a/packages/db/schema/mysql.ts b/packages/db/schema/mysql.ts index 372a2aa9f..71af5dbd2 100644 --- a/packages/db/schema/mysql.ts +++ b/packages/db/schema/mysql.ts @@ -541,6 +541,25 @@ export const customWidgetSecrets = mysqlTable( }), ); +export const widgetSecrets = mysqlTable( + "widget_secret", + { + itemId: varchar({ length: 64 }) + .notNull() + .references(() => items.id, { onDelete: "cascade" }), + kind: varchar({ length: 64 }).notNull(), + value: text().$type<`${string}.${string}`>().notNull(), + updatedAt: timestamp() + .$onUpdateFn(() => new Date()) + .notNull(), + }, + (table) => ({ + compoundKey: primaryKey({ + columns: [table.itemId, table.kind], + }), + }), +); + export const cronJobConfigurations = mysqlTable("cron_job_configuration", { name: varchar({ length: 256 }).notNull().primaryKey(), cronExpression: varchar({ length: 32 }).notNull(), @@ -746,12 +765,20 @@ export const sectionCollapseStateRelations = relations(sectionCollapseStates, ({ export const itemRelations = relations(items, ({ one, many }) => ({ integrations: many(integrationItems), layouts: many(itemLayouts), + secrets: many(widgetSecrets), board: one(boards, { fields: [items.boardId], references: [boards.id], }), })); +export const widgetSecretRelations = relations(widgetSecrets, ({ one }) => ({ + item: one(items, { + fields: [widgetSecrets.itemId], + references: [items.id], + }), +})); + export const integrationItemRelations = relations(integrationItems, ({ one }) => ({ integration: one(integrations, { fields: [integrationItems.integrationId], diff --git a/packages/db/schema/postgresql.ts b/packages/db/schema/postgresql.ts index 0e104f7f4..092ecc64b 100644 --- a/packages/db/schema/postgresql.ts +++ b/packages/db/schema/postgresql.ts @@ -540,6 +540,25 @@ export const customWidgetSecrets = pgTable( }), ); +export const widgetSecrets = pgTable( + "widget_secret", + { + itemId: varchar({ length: 64 }) + .notNull() + .references(() => items.id, { onDelete: "cascade" }), + kind: varchar({ length: 64 }).notNull(), + value: text().$type<`${string}.${string}`>().notNull(), + updatedAt: timestamp() + .$onUpdateFn(() => new Date()) + .notNull(), + }, + (table) => ({ + compoundKey: primaryKey({ + columns: [table.itemId, table.kind], + }), + }), +); + export const cronJobConfigurations = pgTable("cron_job_configuration", { name: varchar({ length: 256 }).notNull().primaryKey(), cronExpression: varchar({ length: 32 }).notNull(), @@ -745,12 +764,20 @@ export const sectionCollapseStateRelations = relations(sectionCollapseStates, ({ export const itemRelations = relations(items, ({ one, many }) => ({ integrations: many(integrationItems), layouts: many(itemLayouts), + secrets: many(widgetSecrets), board: one(boards, { fields: [items.boardId], references: [boards.id], }), })); +export const widgetSecretRelations = relations(widgetSecrets, ({ one }) => ({ + item: one(items, { + fields: [widgetSecrets.itemId], + references: [items.id], + }), +})); + export const integrationItemRelations = relations(integrationItems, ({ one }) => ({ integration: one(integrations, { fields: [integrationItems.integrationId], diff --git a/packages/db/schema/sqlite.ts b/packages/db/schema/sqlite.ts index 309d58507..b791609d4 100644 --- a/packages/db/schema/sqlite.ts +++ b/packages/db/schema/sqlite.ts @@ -530,6 +530,25 @@ export const customWidgetSecrets = sqliteTable( }), ); +export const widgetSecrets = sqliteTable( + "widget_secret", + { + itemId: text() + .notNull() + .references(() => items.id, { onDelete: "cascade" }), + kind: text().notNull(), + value: text().$type<`${string}.${string}`>().notNull(), + updatedAt: int({ mode: "timestamp" }) + .$onUpdateFn(() => new Date()) + .notNull(), + }, + (table) => ({ + compoundKey: primaryKey({ + columns: [table.itemId, table.kind], + }), + }), +); + export const cronJobConfigurations = sqliteTable("cron_job_configuration", { name: text().notNull().primaryKey(), cronExpression: text().notNull(), @@ -735,12 +754,20 @@ export const sectionCollapseStateRelations = relations(sectionCollapseStates, ({ export const itemRelations = relations(items, ({ one, many }) => ({ integrations: many(integrationItems), layouts: many(itemLayouts), + secrets: many(widgetSecrets), board: one(boards, { fields: [items.boardId], references: [boards.id], }), })); +export const widgetSecretRelations = relations(widgetSecrets, ({ one }) => ({ + item: one(items, { + fields: [widgetSecrets.itemId], + references: [items.id], + }), +})); + export const integrationItemRelations = relations(integrationItems, ({ one }) => ({ integration: one(integrations, { fields: [integrationItems.integrationId], diff --git a/packages/definitions/src/docker-integration-match.ts b/packages/definitions/src/docker-integration-match.ts index 9dcba12d4..e7e10809b 100644 --- a/packages/definitions/src/docker-integration-match.ts +++ b/packages/definitions/src/docker-integration-match.ts @@ -31,9 +31,6 @@ const integrationAliases: Partial> = audiobookshelf: ["audiobookshelf"], navidrome: ["navidrome"], paperlessNgx: ["paperless-ngx", "paperless"], - searchCh: ["search-ch"], - gitHubContainerRegistry: ["ghcr"], - linuxServerIO: ["linuxserver"], coolify: ["coolify"], truenas: ["truenas"], }; @@ -63,19 +60,7 @@ export const matchIntegrationKind = (search: string): IntegrationKind | null => return null; }; -const notDockerDiscoverable = new Set([ - "dockerHub", - "github", - "gitlab", - "npm", - "codeberg", - "linuxServerIO", - "gitHubContainerRegistry", - "quay", - "ical", - "searchCh", - "mock", -]); +const notDockerDiscoverable = new Set(["ical", "mock"]); interface ContainerMatchInput { image: string; diff --git a/packages/definitions/src/docs/homarr-docs-sitemap.ts b/packages/definitions/src/docs/homarr-docs-sitemap.ts index 4dbb8757a..6c1dd40fc 100644 --- a/packages/definitions/src/docs/homarr-docs-sitemap.ts +++ b/packages/definitions/src/docs/homarr-docs-sitemap.ts @@ -165,6 +165,7 @@ export type HomarrDocumentationPath = | "/docs/getting-started/installation/unraid" | "/docs/integrations/adguard-home" | "/docs/integrations/anchor" + | "/docs/integrations/archiveteam-warrior" | "/docs/integrations/aria2" | "/docs/integrations/audiobookshelf" | "/docs/integrations/beszel" @@ -315,14 +316,6 @@ export type HomarrDocumentationPath = | "/docs/integrations/nextcloud" | "/docs/integrations/unifi-controller" | "/docs/integrations/opnsense" - | "/docs/integrations/github" - | "/docs/integrations/docker-hub" - | "/docs/integrations/gitlab" - | "/docs/integrations/npm" - | "/docs/integrations/codeberg" - | "/docs/integrations/linux-server-io" - | "/docs/integrations/github-containerregistry" - | "/docs/integrations/quay" | "/docs/integrations/ntfy" | "/docs/integrations/gotify" | "/docs/integrations/ical" @@ -330,7 +323,6 @@ export type HomarrDocumentationPath = | "/docs/integrations/truenas" | "/docs/integrations/unraid" | "/docs/integrations/coolify" - | "/docs/integrations/search-ch" | "/docs/integrations/immich" | "/docs/integrations/paperless-ngx" | "/docs/integrations/tracearr" diff --git a/packages/definitions/src/docs/integration-doc-slugs.ts b/packages/definitions/src/docs/integration-doc-slugs.ts index 90c3cae8b..ca19d4de6 100644 --- a/packages/definitions/src/docs/integration-doc-slugs.ts +++ b/packages/definitions/src/docs/integration-doc-slugs.ts @@ -31,14 +31,6 @@ export const integrationDocSlugs: Record = { nextcloud: "nextcloud", unifiController: "unifi-controller", opnsense: "opnsense", - github: "github", - dockerHub: "docker-hub", - gitlab: "gitlab", - npm: "npm", - codeberg: "codeberg", - linuxServerIO: "linux-server-io", - gitHubContainerRegistry: "github-containerregistry", - quay: "quay", ntfy: "ntfy", gotify: "gotify", ical: "ical", @@ -46,7 +38,6 @@ export const integrationDocSlugs: Record = { truenas: "truenas", unraid: "unraid", coolify: "coolify", - searchCh: "search-ch", immich: "immich", paperlessNgx: "paperless-ngx", tracearr: "tracearr", diff --git a/packages/definitions/src/index.ts b/packages/definitions/src/index.ts index eb81e4d18..94b392ee7 100644 --- a/packages/definitions/src/index.ts +++ b/packages/definitions/src/index.ts @@ -17,3 +17,4 @@ export * from "./emptysuperjson"; export * from "./hotkeys"; export * from "./docker-integration-match"; export * from "./integration-url-template"; +export * from "./release-provider"; diff --git a/packages/definitions/src/integration.ts b/packages/definitions/src/integration.ts index abb4ffdfd..15c94f80d 100644 --- a/packages/definitions/src/integration.ts +++ b/packages/definitions/src/integration.ts @@ -283,70 +283,6 @@ export const integrationDefs = { documentationUrl: createDocumentationLink("/docs/integrations/opnsense"), defaultPort: 443, }, - github: { - name: "Github", - secretKinds: [[], ["personalAccessToken"], ["githubAppId", "githubInstallationId", "privateKey"]], - iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/github.svg", - category: ["releasesProvider"], - defaultUrl: "https://api.github.com", - documentationUrl: createDocumentationLink("/docs/integrations/github"), - }, - dockerHub: { - name: "Docker Hub", - secretKinds: [[], ["username", "personalAccessToken"]], - iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/docker.svg", - category: ["releasesProvider"], - defaultUrl: "https://hub.docker.com", - documentationUrl: createDocumentationLink("/docs/integrations/docker-hub"), - }, - gitlab: { - name: "Gitlab", - secretKinds: [[], ["personalAccessToken"]], - iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/gitlab.svg", - category: ["releasesProvider"], - defaultUrl: "https://gitlab.com", - documentationUrl: createDocumentationLink("/docs/integrations/gitlab"), - }, - npm: { - name: "NPM", - secretKinds: [[]], - iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/npm.svg", - category: ["releasesProvider"], - defaultUrl: "https://registry.npmjs.org", - documentationUrl: createDocumentationLink("/docs/integrations/npm"), - }, - codeberg: { - name: "Codeberg", - secretKinds: [[], ["personalAccessToken"]], - iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/codeberg.svg", - category: ["releasesProvider"], - defaultUrl: "https://codeberg.org", - documentationUrl: createDocumentationLink("/docs/integrations/codeberg"), - }, - linuxServerIO: { - name: "LinuxServer.io", - secretKinds: [[]], - iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/linuxserver-io.svg", - category: ["releasesProvider"], - defaultUrl: "https://api.linuxserver.io", - documentationUrl: createDocumentationLink("/docs/integrations/linux-server-io"), - }, - gitHubContainerRegistry: { - name: "GitHub Container Registry", - secretKinds: [[], ["personalAccessToken"], ["githubAppId", "githubInstallationId", "privateKey"]], - iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/github.svg", - category: ["releasesProvider"], - defaultUrl: "https://api.github.com", - documentationUrl: createDocumentationLink("/docs/integrations/github"), - }, - quay: { - name: "Quay", - secretKinds: [[], ["personalAccessToken"]], - iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/png/quay.png", - category: ["releasesProvider"], - defaultUrl: "https://quay.io", - documentationUrl: createDocumentationLink("/docs/integrations/quay"), - }, ntfy: { name: "ntfy", secretKinds: [["topic"], ["topic", "apiKey"]], @@ -401,13 +337,6 @@ export const integrationDefs = { documentationUrl: createDocumentationLink("/docs/integrations/coolify"), defaultPort: 8000, }, - searchCh: { - name: "Search.ch", - secretKinds: [[]], - iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/png/search-ch.png", - category: ["timetable"], - documentationUrl: createDocumentationLink("/docs/integrations/search-ch"), - }, immich: { name: "Immich", secretKinds: [["apiKey"]], @@ -501,8 +430,8 @@ export const integrationDefs = { secretKinds: [[], ["username", "password"]], iconUrl: "https://cdn.jsdelivr.net/gh/selfhst/icons/png/archiveteam-warrior.png", category: ["archiving"], - defaultUrl: "http://localhost:8001", documentationUrl: null, + defaultUrl: "http://localhost:8001", }, // This integration only returns mock data, it is used during development (but can also be used in production by directly going to the create page) mock: { @@ -602,7 +531,6 @@ export const integrationCategories = [ "search", "mediaTranscoding", "networkController", - "releasesProvider", "notifications", "firewall", "timetable", diff --git a/packages/definitions/src/release-provider.ts b/packages/definitions/src/release-provider.ts new file mode 100644 index 000000000..3fa53cc8f --- /dev/null +++ b/packages/definitions/src/release-provider.ts @@ -0,0 +1,78 @@ +import { objectKeys } from "@homarr/common"; + +export const releaseProviderDefs = { + github: { + name: "Github", + iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/github.svg", + defaultUrl: "https://api.github.com", + }, + dockerHub: { + name: "Docker Hub", + iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/docker.svg", + defaultUrl: "https://hub.docker.com", + }, + gitlab: { + name: "Gitlab", + iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/gitlab.svg", + defaultUrl: "https://gitlab.com", + }, + npm: { + name: "NPM", + iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/npm.svg", + defaultUrl: "https://registry.npmjs.org", + }, + codeberg: { + name: "Codeberg", + iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/codeberg.svg", + defaultUrl: "https://codeberg.org", + }, + linuxServerIO: { + name: "LinuxServer.io", + iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/linuxserver-io.svg", + defaultUrl: "https://api.linuxserver.io", + }, + gitHubContainerRegistry: { + name: "GitHub Container Registry", + iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/github.svg", + defaultUrl: "https://ghcr.io", + }, + quay: { + name: "Quay", + iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/png/quay.png", + defaultUrl: "https://quay.io", + }, +} as const; + +export const releaseProviderKinds = objectKeys(releaseProviderDefs); +export type ReleaseProviderKind = keyof typeof releaseProviderDefs; + +export const getReleaseProviderName = (provider: ReleaseProviderKind) => releaseProviderDefs[provider].name; +export const getReleaseProviderIconUrl = (provider: ReleaseProviderKind) => releaseProviderDefs[provider].iconUrl; +export const getReleaseProviderDefaultUrl = (provider: ReleaseProviderKind) => releaseProviderDefs[provider].defaultUrl; + +const releaseProviderRegistryPrefixes: Partial> = { + dockerHub: "docker.io/", + gitHubContainerRegistry: "ghcr.io/", + linuxServerIO: "lscr.io/", + quay: "quay.io/", +}; + +const removeContainerImageVersion = (identifier: string) => { + const digestIndex = identifier.indexOf("@"); + const withoutDigest = digestIndex === -1 ? identifier : identifier.slice(0, digestIndex); + const tagIndex = withoutDigest.indexOf(":", withoutDigest.lastIndexOf("/") + 1); + + return tagIndex === -1 ? withoutDigest : withoutDigest.slice(0, tagIndex); +}; + +export const normalizeReleaseProviderIdentifier = (provider: ReleaseProviderKind, identifier: string) => { + const trimmedIdentifier = identifier.trim(); + const registryPrefix = releaseProviderRegistryPrefixes[provider]; + const withoutRegistry = + registryPrefix && trimmedIdentifier.startsWith(registryPrefix) + ? trimmedIdentifier.slice(registryPrefix.length) + : trimmedIdentifier; + + // ponytail: only strip container image versions/digests for providers that use them + return registryPrefix ? removeContainerImageVersion(withoutRegistry) : withoutRegistry; +}; diff --git a/packages/definitions/src/test/release-provider.spec.ts b/packages/definitions/src/test/release-provider.spec.ts new file mode 100644 index 000000000..7e7e0b90e --- /dev/null +++ b/packages/definitions/src/test/release-provider.spec.ts @@ -0,0 +1,32 @@ +import { describe, expect, it } from "vitest"; + +import { normalizeReleaseProviderIdentifier } from "../release-provider"; + +describe("normalizeReleaseProviderIdentifier", () => { + it("normalizes full ghcr image references", () => { + expect(normalizeReleaseProviderIdentifier("gitHubContainerRegistry", "ghcr.io/muchobien/pocketbase:latest")).toBe( + "muchobien/pocketbase", + ); + expect(normalizeReleaseProviderIdentifier("gitHubContainerRegistry", "ghcr.io/homarr-labs/homarr:latest")).toBe( + "homarr-labs/homarr", + ); + }); + + it("keeps existing ghcr owner/name identifiers unchanged", () => { + expect(normalizeReleaseProviderIdentifier("gitHubContainerRegistry", "homarr-labs/homarr")).toBe( + "homarr-labs/homarr", + ); + }); + + it("normalizes container image identifiers for other registries", () => { + expect(normalizeReleaseProviderIdentifier("dockerHub", "postgres:17")).toBe("postgres"); + expect(normalizeReleaseProviderIdentifier("dockerHub", "docker.io/library/postgres:17")).toBe("library/postgres"); + expect(normalizeReleaseProviderIdentifier("dockerHub", "library/postgres@sha256:abc123")).toBe("library/postgres"); + expect(normalizeReleaseProviderIdentifier("linuxServerIO", "lscr.io/linuxserver/radarr:latest")).toBe( + "linuxserver/radarr", + ); + expect(normalizeReleaseProviderIdentifier("quay", "quay.io/prometheus/prometheus:v3.0.0")).toBe( + "prometheus/prometheus", + ); + }); +}); diff --git a/packages/definitions/src/widget-integration-map.ts b/packages/definitions/src/widget-integration-map.ts index a560f2979..deb953781 100644 --- a/packages/definitions/src/widget-integration-map.ts +++ b/packages/definitions/src/widget-integration-map.ts @@ -30,7 +30,6 @@ export const widgetIntegrationSupport: Partial([ - "searchCh", - "github", - "dockerHub", - "gitlab", - "npm", - "codeberg", - "linuxServerIO", - "gitHubContainerRegistry", - "quay", - "mock", -]); +export const hiddenFromOnboarding = new Set(["mock"]); export const defaultBookmarkApps = [ { diff --git a/packages/forms-collection/src/icon-picker/icon-picker.tsx b/packages/forms-collection/src/icon-picker/icon-picker.tsx index 43006ae60..058e75bd0 100644 --- a/packages/forms-collection/src/icon-picker/icon-picker.tsx +++ b/packages/forms-collection/src/icon-picker/icon-picker.tsx @@ -55,8 +55,6 @@ export const IconPicker = ({ const [value, setValue] = useUncontrolled({ value: propsValue, onChange }); const [query, setQuery] = useState(""); - // ponytail: reset the typed query when the committed value changes - // externally (form reset, parent re-render) so the input reverts to the URL. useEffect(() => { setQuery(""); }, [value]); @@ -65,13 +63,8 @@ export const IconPicker = ({ const tCommon = useScopedI18n("common"); const [debouncedQuery] = useDebouncedValue(query, 100); - // ponytail: only search when the typed query differs from the committed - // value. On mount the input shows the existing URL (edit mode), which - // would never match the bare filenames in the icon table. const searchText = (debouncedQuery !== (value ?? "") && debouncedQuery) || ""; - // ponytail: not useSuspenseQuery — an above Suspense boundary would - // trigger on every keystroke and the dropdown flash is bad UX. const { data, isLoading, isError, refetch } = clientApi.icon.findIcons.useQuery( { searchText }, { placeholderData: (prev) => prev }, @@ -123,11 +116,8 @@ export const IconPicker = ({ ); }); - // ponytail: show the typed query if non-empty, else fall back to the URL. const inputValue = query || value || ""; - // ponytail: show the existing icon as a left-section preview. const leftSection = shouldShowPreview(value) && ; - // ponytail: prop override -> real count -> loading text. const placeholderText = placeholder || (data && tCommon("iconPicker.header", { countIcons: String(data.countIcons) })) || diff --git a/packages/integrations/package.json b/packages/integrations/package.json index 6e0c81039..d36be1852 100644 --- a/packages/integrations/package.json +++ b/packages/integrations/package.json @@ -27,7 +27,6 @@ "@ctrl/deluge": "catalog:", "@ctrl/qbittorrent": "catalog:", "@ctrl/transmission": "catalog:", - "@gitbeaker/rest": "catalog:", "@homarr/common": "workspace:^0.1.0", "@homarr/core": "workspace:^0.1.0", "@homarr/db": "workspace:^0.1.0", @@ -39,7 +38,6 @@ "@homarr/validation": "workspace:^0.1.0", "@immich/sdk": "catalog:", "@jellyfin/sdk": "catalog:", - "@octokit/auth-app": "catalog:", "ical.js": "catalog:", "maria2": "catalog:", "octokit": "catalog:", diff --git a/packages/integrations/src/base/creator.ts b/packages/integrations/src/base/creator.ts index 13ed36537..1a84134fc 100644 --- a/packages/integrations/src/base/creator.ts +++ b/packages/integrations/src/base/creator.ts @@ -3,12 +3,9 @@ import type { IntegrationKind } from "@homarr/definitions"; import { AdGuardHomeIntegration } from "../adguard-home/adguard-home-integration"; import { BeszelIntegration } from "../beszel/beszel-integration"; import { AnchorIntegration } from "../anchor/anchor-integration"; -import { ArchiveTeamWarriorIntegration } from "../archive-team-warrior/archive-team-warrior-integration"; import { AudiobookshelfIntegration } from "../audiobookshelf/audiobookshelf-integration"; -import { CodebergIntegration } from "../codeberg/codeberg-integration"; import { CoolifyIntegration } from "../coolify/coolify-integration"; import { DashDotIntegration } from "../dashdot/dashdot-integration"; -import { DockerHubIntegration } from "../docker-hub/docker-hub-integration"; import { Aria2Integration } from "../download-client/aria2/aria2-integration"; import { DelugeIntegration } from "../download-client/deluge/deluge-integration"; import { NzbGetIntegration } from "../download-client/nzbget/nzbget-integration"; @@ -17,17 +14,14 @@ import { SabnzbdIntegration } from "../download-client/sabnzbd/sabnzbd-integrati import { SlskdIntegration } from "../download-client/slskd/slskd-integration"; import { TransmissionIntegration } from "../download-client/transmission/transmission-integration"; import { EmbyIntegration } from "../emby/emby-integration"; -import { GitHubContainerRegistryIntegration } from "../github-container-registry/github-container-registry-integration"; -import { GithubIntegration } from "../github/github-integration"; -import { GitlabIntegration } from "../gitlab/gitlab-integration"; import { GlancesIntegration } from "../glances/glances-integration"; +import { ArchiveTeamWarriorIntegration } from "../archive-team-warrior/archive-team-warrior-integration"; import { GluetunIntegration } from "../gluetun/gluetun-integration"; import { HomeAssistantIntegration } from "../homeassistant/homeassistant-integration"; import { ICalIntegration } from "../ical/ical-integration"; import { ImmichIntegration } from "../immich/immich-integration"; import { JellyfinIntegration } from "../jellyfin/jellyfin-integration"; import { JellyseerrIntegration } from "../jellyseerr/jellyseerr-integration"; -import { LinuxServerIOIntegration } from "../linuxserverio/linuxserverio-integration"; import { LidarrIntegration } from "../media-organizer/lidarr/lidarr-integration"; import { RadarrIntegration } from "../media-organizer/radarr/radarr-integration"; import { ReadarrIntegration } from "../media-organizer/readarr/readarr-integration"; @@ -36,7 +30,6 @@ import { TdarrIntegration } from "../media-transcoding/tdarr-integration"; import { MockIntegration } from "../mock/mock-integration"; import { NavidromeIntegration } from "../navidrome/navidrome-integration"; import { NextcloudIntegration } from "../nextcloud/nextcloud.integration"; -import { NPMIntegration } from "../npm/npm-integration"; import { GotifyIntegration } from "../gotify/gotify-integration"; import { NTFYIntegration } from "../ntfy/ntfy-integration"; import { OpenMediaVaultIntegration } from "../openmediavault/openmediavault-integration"; @@ -49,8 +42,6 @@ import { createTechnitiumDnsIntegrationAsync } from "../technitium/technitium-in import { PlexIntegration } from "../plex/plex-integration"; import { ProwlarrIntegration } from "../prowlarr/prowlarr-integration"; import { ProxmoxIntegration } from "../proxmox/proxmox-integration"; -import { QuayIntegration } from "../quay/quay-integration"; -import { SearchChIntegration } from "../search-ch/search-ch-integration"; import { SeerrIntegration } from "../seerr/seerr-integration"; import { SpeedtestTrackerIntegration } from "../speedtest-tracker/speedtest-tracker-integration"; import { TracearrIntegration } from "../tracearr/tracearr-integration"; @@ -114,15 +105,7 @@ export const integrationCreators = { nextcloud: NextcloudIntegration, unifiController: UnifiControllerIntegration, opnsense: OPNsenseIntegration, - github: GithubIntegration, - dockerHub: DockerHubIntegration, - gitlab: GitlabIntegration, - npm: NPMIntegration, - codeberg: CodebergIntegration, - linuxServerIO: LinuxServerIOIntegration, - gitHubContainerRegistry: GitHubContainerRegistryIntegration, ical: ICalIntegration, - quay: QuayIntegration, ntfy: NTFYIntegration, gotify: GotifyIntegration, mock: MockIntegration, @@ -131,7 +114,6 @@ export const integrationCreators = { coolify: CoolifyIntegration, tracearr: TracearrIntegration, glances: GlancesIntegration, - searchCh: SearchChIntegration, immich: ImmichIntegration, paperlessNgx: PaperlessNgxIntegration, speedtestTracker: SpeedtestTrackerIntegration, diff --git a/packages/integrations/src/beszel/beszel-integration.ts b/packages/integrations/src/beszel/beszel-integration.ts index bf6f3640c..186ed2203 100644 --- a/packages/integrations/src/beszel/beszel-integration.ts +++ b/packages/integrations/src/beszel/beszel-integration.ts @@ -13,13 +13,11 @@ import type { BeszelAlertHistory, BeszelAuthResponse, BeszelContainer, - BeszelContainerStats, BeszelContainerStatsRecord, BeszelSmartDevice, BeszelSystem, BeszelSystemDetails, BeszelSystemdService, - BeszelSystemStats, BeszelSystemStatsRecord, CreateAlertInput, PocketBaseListResponse, @@ -286,111 +284,6 @@ export class BeszelIntegration extends Integration { }); } - public async subscribeRealtimeMetrics( - systemId: string, - onMessage: (data: { stats: BeszelSystemStatsRecord; containerStats: BeszelContainerStatsRecord | null }) => void, - signal: AbortSignal, - ): Promise { - const session = await this.authenticateAsync(); - const sseUrl = this.url("/api/realtime"); - logger.debug("Opening Beszel SSE connection", { integrationId: this.integration.id, systemId }); - - const sseResponse = await fetchWithTrustedCertificatesAsync(sseUrl, { - headers: { Authorization: session.token }, - signal, - }); - - if (!sseResponse.ok || !sseResponse.body) { - logger.warn("Beszel SSE connection failed", { - integrationId: this.integration.id, - systemId, - status: sseResponse.status, - }); - throw new ResponseError(sseResponse); - } - logger.debug("Beszel SSE connection opened", { integrationId: this.integration.id, systemId }); - - const reader = sseResponse.body.getReader(); - const decoder = new TextDecoder(); - let buffer = ""; - let clientId: string | null = null; - - const processLine = (line: string) => { - if (!line.startsWith("data:")) return; - const jsonStr = line.slice(5).trim(); - if (!jsonStr) return; - - try { - const parsed = JSON.parse(jsonStr) as Record; - - if (!clientId && typeof parsed.clientId === "string") { - clientId = parsed.clientId; - this.subscribeTopic(clientId, systemId, session.token).catch((err) => { - logger.warn("Failed to subscribe to rt_metrics topic, aborting SSE", { error: err }); - reader.cancel().catch(() => {}); - }); - return; - } - - if ("stats" in parsed) { - const now = new Date().toISOString(); - const envelope = { id: "", system: systemId, type: "1m" as const, created: now, updated: now }; - const stats: BeszelSystemStatsRecord = { ...envelope, stats: parsed.stats as BeszelSystemStats }; - - let containerStats: BeszelContainerStatsRecord | null = null; - const hasContainers = - parsed.container && - Array.isArray(parsed.container) && - (parsed.container as BeszelContainerStats[]).length > 0; - if (hasContainers) { - containerStats = { ...envelope, stats: parsed.container as BeszelContainerStats[] }; - } - onMessage({ stats, containerStats }); - } - } catch { - logger.debug("Failed to parse SSE message", { line }); - } - }; - - try { - while (!signal.aborted) { - const { done, value } = await reader.read(); - if (done) break; - - buffer += decoder.decode(value, { stream: true }); - const lines = buffer.split("\n"); - buffer = lines.pop() ?? ""; - for (const line of lines) { - processLine(line); - } - } - if (!signal.aborted) { - logger.warn("Beszel SSE stream ended unexpectedly", { - integrationId: this.integration.id, - systemId, - }); - throw new Error("Beszel SSE stream ended unexpectedly"); - } - } finally { - reader.cancel().catch(() => {}); - } - } - - private async subscribeTopic(clientId: string, systemId: string, token: string): Promise { - const topic = `rt_metrics?options=${JSON.stringify({ query: { system: systemId } })}`; - const response = await fetchWithTrustedCertificatesAsync(this.url("/api/realtime"), { - method: "POST", - headers: { - Authorization: token, - "Content-Type": "application/json", - }, - body: JSON.stringify({ clientId, subscriptions: [topic] }), - }); - if (!response.ok) { - throw new ResponseError(response); - } - } - protected async testingAsync(input: IntegrationTestingInput): Promise { const response = await input.fetchAsync(this.url("/api/collections/users/auth-with-password"), { method: "POST", diff --git a/packages/integrations/src/codeberg/codeberg-integration.ts b/packages/integrations/src/codeberg/codeberg-integration.ts deleted file mode 100644 index 2396a81e0..000000000 --- a/packages/integrations/src/codeberg/codeberg-integration.ts +++ /dev/null @@ -1,144 +0,0 @@ -import type { RequestInit, Response } from "undici"; - -import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http"; -import { createLogger } from "@homarr/core/infrastructure/logs"; - -import type { IntegrationTestingInput } from "../base/integration"; -import { Integration } from "../base/integration"; -import { TestConnectionError } from "../base/test-connection/test-connection-error"; -import type { TestingResult } from "../base/test-connection/test-connection-service"; -import type { ReleasesProviderIntegration } from "../interfaces/releases-providers/releases-providers-integration"; -import { getLatestRelease } from "../interfaces/releases-providers/releases-providers-integration"; -import type { - DetailsProviderResponse, - ReleaseResponse, -} from "../interfaces/releases-providers/releases-providers-types"; -import { detailsResponseSchema, releasesResponseSchema } from "./codeberg-schemas"; - -const logger = createLogger({ module: "codebergIntegration" }); - -export class CodebergIntegration extends Integration implements ReleasesProviderIntegration { - private async withHeadersAsync(callback: (headers: RequestInit["headers"]) => Promise): Promise { - if (!this.hasSecretValue("personalAccessToken")) return await callback(undefined); - - return await callback({ - Authorization: `token ${this.getSecretValue("personalAccessToken")}`, - }); - } - - protected async testingAsync(input: IntegrationTestingInput): Promise { - const response = await this.withHeadersAsync(async (headers) => { - return await input.fetchAsync(this.url("/version"), { - headers, - }); - }); - - if (!response.ok) { - return TestConnectionError.StatusResult(response); - } - - return { - success: true, - }; - } - - private parseIdentifier(identifier: string) { - const [owner, name] = identifier.split("/"); - if (!owner || !name) { - logger.warn("Invalid identifier format. Expected 'owner/name', for identifier", { - identifier, - }); - return null; - } - return { owner, name }; - } - - public async getLatestMatchingReleaseAsync(identifier: string, versionRegex?: string): Promise { - const parsedIdentifier = this.parseIdentifier(identifier); - if (!parsedIdentifier) return { success: false, error: { code: "invalidIdentifier" } }; - - const { owner, name } = parsedIdentifier; - - const releasesResponse = await this.withHeadersAsync(async (headers) => { - return await fetchWithTrustedCertificatesAsync( - this.url(`/api/v1/repos/${encodeURIComponent(owner)}/${encodeURIComponent(name)}/releases`), - { headers }, - ); - }); - if (!releasesResponse.ok) { - return { success: false, error: { code: "unexpected", message: releasesResponse.statusText } }; - } - - const releasesResponseJson: unknown = await releasesResponse.json(); - const { data, success, error } = releasesResponseSchema.safeParse(releasesResponseJson); - if (!success) { - return { - success: false, - error: { - code: "unexpected", - message: releasesResponseJson ? JSON.stringify(releasesResponseJson, null, 2) : error.message, - }, - }; - } - - const formattedReleases = data.map((tag) => ({ - latestRelease: tag.tag_name, - latestReleaseAt: tag.published_at, - releaseUrl: tag.url, - releaseDescription: tag.body, - isPreRelease: tag.prerelease, - })); - - const latestRelease = getLatestRelease(formattedReleases, versionRegex); - if (!latestRelease) return { success: false, error: { code: "noMatchingVersion" } }; - - const details = await this.getDetailsAsync(owner, name); - - return { success: true, data: { ...details, ...latestRelease } }; - } - - protected async getDetailsAsync(owner: string, name: string): Promise { - const response = await this.withHeadersAsync(async (headers) => { - return await fetchWithTrustedCertificatesAsync( - this.url(`/api/v1/repos/${encodeURIComponent(owner)}/${encodeURIComponent(name)}`), - { - headers, - }, - ); - }); - - if (!response.ok) { - logger.warn("Failed to get details", { - owner, - name, - error: response.statusText, - }); - - return undefined; - } - - const responseJson = await response.json(); - const { data, success, error } = detailsResponseSchema.safeParse(responseJson); - - if (!success) { - logger.warn("Failed to parse details", { - owner, - name, - error, - }); - - return undefined; - } - - return { - projectUrl: data.html_url, - projectDescription: data.description, - isFork: data.fork, - isArchived: data.archived, - createdAt: data.created_at, - starsCount: data.stars_count, - openIssues: data.open_issues_count, - forksCount: data.forks_count, - }; - } -} diff --git a/packages/integrations/src/codeberg/codeberg-schemas.ts b/packages/integrations/src/codeberg/codeberg-schemas.ts deleted file mode 100644 index ff6e62a01..000000000 --- a/packages/integrations/src/codeberg/codeberg-schemas.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { z } from "zod/v4"; - -export const releasesResponseSchema = z.array( - z.object({ - tag_name: z.string(), - published_at: z.string().transform((value) => new Date(value)), - url: z.string(), - body: z.string(), - prerelease: z.boolean(), - }), -); - -export const detailsResponseSchema = z.object({ - html_url: z.string(), - description: z.string(), - fork: z.boolean(), - archived: z.boolean(), - created_at: z.string().transform((value) => new Date(value)), - stars_count: z.number(), - open_issues_count: z.number(), - forks_count: z.number(), -}); diff --git a/packages/integrations/src/docker-hub/docker-hub-integration.ts b/packages/integrations/src/docker-hub/docker-hub-integration.ts deleted file mode 100644 index b9e9ea09c..000000000 --- a/packages/integrations/src/docker-hub/docker-hub-integration.ts +++ /dev/null @@ -1,190 +0,0 @@ -import type { fetch, RequestInit, Response } from "undici"; - -import { ResponseError } from "@homarr/common/server"; -import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http"; -import { createLogger } from "@homarr/core/infrastructure/logs"; - -import type { IntegrationInput, IntegrationTestingInput } from "../base/integration"; -import { Integration } from "../base/integration"; -import type { SessionStore } from "../base/session-store"; -import { createSessionStore } from "../base/session-store"; -import { TestConnectionError } from "../base/test-connection/test-connection-error"; -import type { TestingResult } from "../base/test-connection/test-connection-service"; -import type { ReleasesProviderIntegration } from "../interfaces/releases-providers/releases-providers-integration"; -import { getLatestRelease } from "../interfaces/releases-providers/releases-providers-integration"; -import type { - DetailsProviderResponse, - ReleaseResponse, -} from "../interfaces/releases-providers/releases-providers-types"; -import { accessTokenResponseSchema, detailsResponseSchema, releasesResponseSchema } from "./docker-hub-schemas"; - -const logger = createLogger({ module: "dockerHubIntegration" }); - -export class DockerHubIntegration extends Integration implements ReleasesProviderIntegration { - private readonly sessionStore: SessionStore; - - constructor(integration: IntegrationInput) { - super(integration); - this.sessionStore = createSessionStore(integration); - } - - private async withHeadersAsync(callback: (headers: RequestInit["headers"]) => Promise): Promise { - if (!this.hasSecretValue("username") || !this.hasSecretValue("personalAccessToken")) - return await callback(undefined); - - const storedSession = await this.sessionStore.getAsync(); - - if (storedSession) { - logger.debug("Using stored session for request", { integrationId: this.integration.id }); - const response = await callback({ - Authorization: `Bearer ${storedSession}`, - }); - if (response.status !== 401) { - return response; - } - - logger.debug("Session expired, getting new session", { integrationId: this.integration.id }); - } - - const accessToken = await this.getSessionAsync(); - await this.sessionStore.setAsync(accessToken); - return await callback({ - Authorization: `Bearer ${accessToken}`, - }); - } - - protected async testingAsync(input: IntegrationTestingInput): Promise { - const hasAuth = this.hasSecretValue("username") && this.hasSecretValue("personalAccessToken"); - - if (hasAuth) { - logger.debug("Testing DockerHub connection with authentication", { integrationId: this.integration.id }); - await this.getSessionAsync(input.fetchAsync); - } else { - logger.debug("Testing DockerHub connection without authentication", { integrationId: this.integration.id }); - const response = await input.fetchAsync(this.url("/v2/repositories/library")); - if (!response.ok) { - return TestConnectionError.StatusResult(response); - } - } - - return { - success: true, - }; - } - - private parseIdentifier(identifier: string) { - if (!identifier.includes("/")) return { owner: "", name: identifier }; - const [owner, name] = identifier.split("/"); - if (!owner || !name) { - logger.warn("Invalid identifier format. Expected 'owner/name' or 'name', for identifier", { - identifier, - }); - return null; - } - return { owner, name }; - } - - public async getLatestMatchingReleaseAsync(identifier: string, versionRegex?: string): Promise { - const parsedIdentifier = this.parseIdentifier(identifier); - if (!parsedIdentifier) return { success: false, error: { code: "invalidIdentifier" } }; - - const { owner, name } = parsedIdentifier; - - const relativeUrl: `/${string}` = owner - ? `/v2/namespaces/${encodeURIComponent(owner)}/repositories/${encodeURIComponent(name)}` - : `/v2/repositories/library/${encodeURIComponent(name)}`; - - for (let page = 0; page <= 5; page++) { - const releasesResponse = await this.withHeadersAsync(async (headers) => { - return await fetchWithTrustedCertificatesAsync(this.url(`${relativeUrl}/tags?page_size=100&page=${page}`), { - headers, - }); - }); - if (!releasesResponse.ok) { - return { success: false, error: { code: "unexpected", message: releasesResponse.statusText } }; - } - - const releasesResponseJson: unknown = await releasesResponse.json(); - const releasesResult = releasesResponseSchema.safeParse(releasesResponseJson); - if (!releasesResult.success) { - return { - success: false, - error: { - code: "unexpected", - message: releasesResponseJson - ? JSON.stringify(releasesResponseJson, null, 2) - : releasesResult.error.message, - }, - }; - } - - const latestRelease = getLatestRelease(releasesResult.data.results, versionRegex); - if (!latestRelease) continue; - - const details = await this.getDetailsAsync(relativeUrl); - - return { success: true, data: { ...details, ...latestRelease } }; - } - - return { success: false, error: { code: "noMatchingVersion" } }; - } - - private async getDetailsAsync(relativeUrl: `/${string}`): Promise { - const response = await this.withHeadersAsync(async (headers) => { - return await fetchWithTrustedCertificatesAsync(this.url(`${relativeUrl}/`), { - headers, - }); - }); - - if (!response.ok) { - logger.warn("Failed to get details response", { - relativeUrl, - error: response.statusText, - }); - - return undefined; - } - - const responseJson = await response.json(); - const { data, success, error } = detailsResponseSchema.safeParse(responseJson); - - if (!success) { - logger.warn("Failed to parse details response", { - relativeUrl, - error, - }); - - return undefined; - } - - return { - projectUrl: `https://hub.docker.com/r/${data.namespace === "library" ? "_" : data.namespace}/${data.name}`, - projectDescription: data.description, - createdAt: data.date_registered, - starsCount: data.star_count, - }; - } - - private async getSessionAsync(fetchAsync: typeof fetch = fetchWithTrustedCertificatesAsync): Promise { - const response = await fetchAsync(this.url("/v2/auth/token"), { - method: "POST", - body: JSON.stringify({ - identifier: this.getSecretValue("username"), - secret: this.getSecretValue("personalAccessToken"), - }), - }); - - if (!response.ok) throw new ResponseError(response); - - const data = await response.json(); - const result = await accessTokenResponseSchema.parseAsync(data); - - if (!result.access_token) { - throw new ResponseError({ status: 401, url: response.url }); - } - - logger.info("Received session successfully", { integrationId: this.integration.id }); - - return result.access_token; - } -} diff --git a/packages/integrations/src/docker-hub/docker-hub-schemas.ts b/packages/integrations/src/docker-hub/docker-hub-schemas.ts deleted file mode 100644 index 674ae5532..000000000 --- a/packages/integrations/src/docker-hub/docker-hub-schemas.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { z } from "zod/v4"; - -export const accessTokenResponseSchema = z.object({ - access_token: z.string(), -}); - -export const releasesResponseSchema = z.object({ - results: z.array( - z.object({ name: z.string(), last_updated: z.string().transform((value) => new Date(value)) }).transform((tag) => ({ - latestRelease: tag.name, - latestReleaseAt: tag.last_updated, - })), - ), -}); - -export const detailsResponseSchema = z.object({ - name: z.string(), - namespace: z.string(), - description: z.string(), - star_count: z.number(), - date_registered: z.string().transform((value) => new Date(value)), -}); diff --git a/packages/integrations/src/github-container-registry/github-container-registry-integration.ts b/packages/integrations/src/github-container-registry/github-container-registry-integration.ts deleted file mode 100644 index 1400551a8..000000000 --- a/packages/integrations/src/github-container-registry/github-container-registry-integration.ts +++ /dev/null @@ -1,166 +0,0 @@ -import { createAppAuth } from "@octokit/auth-app"; -import { Octokit, RequestError } from "octokit"; -import type { fetch } from "undici"; - -import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http"; -import { createLogger } from "@homarr/core/infrastructure/logs"; - -import { HandleIntegrationErrors } from "../base/errors/decorator"; -import { integrationOctokitHttpErrorHandler } from "../base/errors/http"; -import type { IntegrationTestingInput } from "../base/integration"; -import { Integration } from "../base/integration"; -import type { TestingResult } from "../base/test-connection/test-connection-service"; -import type { ReleasesProviderIntegration } from "../interfaces/releases-providers/releases-providers-integration"; -import { getLatestRelease } from "../interfaces/releases-providers/releases-providers-integration"; -import type { - DetailsProviderResponse, - ReleaseProviderResponse, - ReleaseResponse, -} from "../interfaces/releases-providers/releases-providers-types"; - -const logger = createLogger({ module: "githubContainerRegistryIntegration" }); - -@HandleIntegrationErrors([integrationOctokitHttpErrorHandler]) -export class GitHubContainerRegistryIntegration extends Integration implements ReleasesProviderIntegration { - private static readonly userAgent = "Homarr-Lab/Homarr:GitHubContainerRegistryIntegration"; - - protected async testingAsync(input: IntegrationTestingInput): Promise { - const api = this.getApi(input.fetchAsync); - - if (this.hasSecretValue("personalAccessToken")) { - await api.rest.users.getAuthenticated(); - } else if (this.hasSecretValue("githubAppId")) { - await api.rest.apps.getInstallation({ - installation_id: Number(this.getSecretValue("githubInstallationId")), - }); - } else { - await api.request("GET /octocat"); - } - - return { - success: true, - }; - } - - private parseIdentifier(identifier: string) { - const [owner, name] = identifier.split("/"); - if (!owner || !name) { - logger.warn("Invalid identifier format. Expected 'owner/name', for identifier", { identifier }); - return null; - } - return { owner, name }; - } - - public async getLatestMatchingReleaseAsync(identifier: string, versionRegex?: string): Promise { - const parsedIdentifier = this.parseIdentifier(identifier); - if (!parsedIdentifier) return { success: false, error: { code: "invalidIdentifier" } }; - - const { owner, name } = parsedIdentifier; - const api = this.getApi(); - - try { - const releasesResponse = await api.rest.packages.getAllPackageVersionsForPackageOwnedByUser({ - username: owner, - package_type: "container", - package_name: name, - per_page: 100, - }); - - const releasesProviderResponse = releasesResponse.data.reduce((acc, release) => { - if (!release.metadata?.container?.tags || !(release.metadata.container.tags.length > 0)) return acc; - - release.metadata.container.tags.forEach((tag) => { - acc.push({ - latestRelease: tag, - latestReleaseAt: new Date(release.updated_at), - releaseUrl: release.html_url, - releaseDescription: release.description ?? undefined, - }); - }); - return acc; - }, []); - - const latestRelease = getLatestRelease(releasesProviderResponse, versionRegex); - if (!latestRelease) return { success: false, error: { code: "noMatchingVersion" } }; - - const details = await this.getDetailsAsync(api, owner, name); - - return { success: true, data: { ...details, ...latestRelease } }; - } catch (error) { - const errorMessage = error instanceof RequestError ? error.message : String(error); - logger.warn("Failed to get releases", { - owner, - name, - error: errorMessage, - }); - return { success: false, error: { code: "unexpected", message: errorMessage } }; - } - } - - protected async getDetailsAsync( - api: Octokit, - owner: string, - name: string, - ): Promise { - try { - const response = await api.rest.packages.getPackageForUser({ - username: owner, - package_type: "container", - package_name: name, - }); - - return { - projectUrl: response.data.repository?.html_url ?? response.data.html_url, - projectDescription: response.data.repository?.description ?? undefined, - isFork: response.data.repository?.fork, - isArchived: response.data.repository?.archived, - createdAt: new Date(response.data.created_at), - starsCount: response.data.repository?.stargazers_count, - openIssues: response.data.repository?.open_issues_count, - forksCount: response.data.repository?.forks_count, - }; - } catch (error) { - logger.warn("Failed to get details", { - owner, - name, - error: error instanceof RequestError ? error.message : String(error), - }); - return undefined; - } - } - - private getAuthProperties(): Pick { - if (this.hasSecretValue("personalAccessToken")) - return { - auth: this.getSecretValue("personalAccessToken"), - }; - - if (this.hasSecretValue("githubAppId")) - return { - authStrategy: createAppAuth, - auth: { - appId: this.getSecretValue("githubAppId"), - installationId: this.getSecretValue("githubInstallationId"), - privateKey: this.getSecretValue("privateKey"), - } satisfies Parameters[0], - }; - - return {}; - } - - private getApi(customFetch?: typeof fetch) { - return new Octokit({ - baseUrl: this.url("/").origin, - request: { - fetch: customFetch ?? fetchWithTrustedCertificatesAsync, - }, - userAgent: GitHubContainerRegistryIntegration.userAgent, - // Disable throttling for this integration, Octokit will retry by default after a set time, - // thus delaying the repsonse to the user in case of errors. Errors will be shown to the user, no need to retry the request. - throttle: { enabled: false }, - ...this.getAuthProperties(), - }); - } -} - -type OctokitOptions = Exclude[0], undefined>; diff --git a/packages/integrations/src/github/github-integration.ts b/packages/integrations/src/github/github-integration.ts deleted file mode 100644 index 01548ecc6..000000000 --- a/packages/integrations/src/github/github-integration.ts +++ /dev/null @@ -1,168 +0,0 @@ -import { createAppAuth } from "@octokit/auth-app"; -import { Octokit, RequestError as OctokitRequestError } from "octokit"; -import type { fetch } from "undici"; - -import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http"; -import { createLogger } from "@homarr/core/infrastructure/logs"; - -import { HandleIntegrationErrors } from "../base/errors/decorator"; -import { integrationOctokitHttpErrorHandler } from "../base/errors/http"; -import type { IntegrationTestingInput } from "../base/integration"; -import { Integration } from "../base/integration"; -import type { TestingResult } from "../base/test-connection/test-connection-service"; -import type { ReleasesProviderIntegration } from "../interfaces/releases-providers/releases-providers-integration"; -import { getLatestRelease } from "../interfaces/releases-providers/releases-providers-integration"; -import type { - DetailsProviderResponse, - ReleaseProviderResponse, - ReleaseResponse, -} from "../interfaces/releases-providers/releases-providers-types"; - -const logger = createLogger({ module: "githubIntegration" }); - -@HandleIntegrationErrors([integrationOctokitHttpErrorHandler]) -export class GithubIntegration extends Integration implements ReleasesProviderIntegration { - private static readonly userAgent = "Homarr-Lab/Homarr:GithubIntegration"; - - protected async testingAsync(input: IntegrationTestingInput): Promise { - const api = this.getApi(input.fetchAsync); - - if (this.hasSecretValue("personalAccessToken")) { - await api.rest.users.getAuthenticated(); - } else if (this.hasSecretValue("githubAppId")) { - await api.rest.apps.getInstallation({ - installation_id: Number(this.getSecretValue("githubInstallationId")), - }); - } else { - await api.request("GET /octocat"); - } - - return { - success: true, - }; - } - - private parseIdentifier(identifier: string) { - const [owner, name] = identifier.split("/"); - if (!owner || !name) { - logger.warn("Invalid identifier format. Expected 'owner/name' for identifier", { - identifier, - }); - return null; - } - return { owner, name }; - } - - public async getLatestMatchingReleaseAsync(identifier: string, versionRegex?: string): Promise { - const parsedIdentifier = this.parseIdentifier(identifier); - if (!parsedIdentifier) return { success: false, error: { code: "invalidIdentifier" } }; - - const { owner, name } = parsedIdentifier; - const api = this.getApi(); - - try { - const releasesResponse = await api.rest.repos.listReleases({ owner, repo: name }); - - if (releasesResponse.data.length === 0) { - logger.warn("No releases found", { - identifier: `${owner}/${name}`, - }); - return { success: false, error: { code: "noMatchingVersion" } }; - } - - const releasesProviderResponse = releasesResponse.data.reduce((acc, release) => { - if (!release.published_at) return acc; - - acc.push({ - latestRelease: release.tag_name, - latestReleaseAt: new Date(release.published_at), - releaseUrl: release.html_url, - releaseDescription: release.body ?? undefined, - isPreRelease: release.prerelease, - }); - return acc; - }, []); - - const latestRelease = getLatestRelease(releasesProviderResponse, versionRegex); - if (!latestRelease) return { success: false, error: { code: "noMatchingVersion" } }; - - const details = await this.getDetailsAsync(api, owner, name); - - return { success: true, data: { ...details, ...latestRelease } }; - } catch (error) { - const errorMessage = error instanceof OctokitRequestError ? error.message : String(error); - logger.warn("Failed to get releases", { - owner, - name, - error: errorMessage, - }); - return { success: false, error: { code: "unexpected", message: errorMessage } }; - } - } - - protected async getDetailsAsync( - api: Octokit, - owner: string, - name: string, - ): Promise { - try { - const response = await api.rest.repos.get({ - owner, - repo: name, - }); - - return { - projectUrl: response.data.html_url, - projectDescription: response.data.description ?? undefined, - isFork: response.data.fork, - isArchived: response.data.archived, - createdAt: new Date(response.data.created_at), - starsCount: response.data.stargazers_count, - openIssues: response.data.open_issues_count, - forksCount: response.data.forks_count, - }; - } catch (error) { - logger.warn("Failed to get details", { - owner, - name, - error: error instanceof OctokitRequestError ? error.message : String(error), - }); - return undefined; - } - } - - private getAuthProperties(): Pick { - if (this.hasSecretValue("personalAccessToken")) - return { - auth: this.getSecretValue("personalAccessToken"), - }; - - if (this.hasSecretValue("githubAppId")) - return { - authStrategy: createAppAuth, - auth: { - appId: this.getSecretValue("githubAppId"), - installationId: this.getSecretValue("githubInstallationId"), - privateKey: this.getSecretValue("privateKey"), - } satisfies Parameters[0], - }; - - return {}; - } - - private getApi(customFetch?: typeof fetch) { - return new Octokit({ - baseUrl: this.url("/").origin, - request: { - fetch: customFetch ?? fetchWithTrustedCertificatesAsync, - }, - userAgent: GithubIntegration.userAgent, - // Disable throttling for this integration, Octokit will retry by default after a set time, - // thus delaying the repsonse to the user in case of errors. Errors will be shown to the user, no need to retry the request. - throttle: { enabled: false }, - ...this.getAuthProperties(), - }); - } -} - -type OctokitOptions = Exclude[0], undefined>; diff --git a/packages/integrations/src/gitlab/gitlab-integration.ts b/packages/integrations/src/gitlab/gitlab-integration.ts deleted file mode 100644 index fd1ebc926..000000000 --- a/packages/integrations/src/gitlab/gitlab-integration.ts +++ /dev/null @@ -1,155 +0,0 @@ -import type { Gitlab as CoreGitlab } from "@gitbeaker/core"; -import { createRequesterFn, defaultOptionsHandler } from "@gitbeaker/requester-utils"; -import type { FormattedResponse, RequestOptions, ResourceOptions } from "@gitbeaker/requester-utils"; -import { Gitlab } from "@gitbeaker/rest"; - -import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http"; -import { createLogger } from "@homarr/core/infrastructure/logs"; - -import type { IntegrationTestingInput } from "../base/integration"; -import { Integration } from "../base/integration"; -import { TestConnectionError } from "../base/test-connection/test-connection-error"; -import type { TestingResult } from "../base/test-connection/test-connection-service"; -import type { ReleasesProviderIntegration } from "../interfaces/releases-providers/releases-providers-integration"; -import { getLatestRelease } from "../interfaces/releases-providers/releases-providers-integration"; -import type { - DetailsProviderResponse, - ReleaseProviderResponse, - ReleaseResponse, -} from "../interfaces/releases-providers/releases-providers-types"; - -const logger = createLogger({ module: "gitlabIntegration" }); - -export class GitlabIntegration extends Integration implements ReleasesProviderIntegration { - protected async testingAsync(input: IntegrationTestingInput): Promise { - const response = await input.fetchAsync(this.url("/api/v4/projects"), { - headers: { - ...(this.hasSecretValue("personalAccessToken") - ? { Authorization: `Bearer ${this.getSecretValue("personalAccessToken")}` } - : {}), - }, - }); - - if (!response.ok) { - return TestConnectionError.StatusResult(response); - } - - return { - success: true, - }; - } - - public async getLatestMatchingReleaseAsync(identifier: string, versionRegex?: string): Promise { - const api = this.getApi(); - - try { - const releasesResponse = await api.ProjectReleases.all(identifier, { - perPage: 100, - }); - - if (releasesResponse instanceof Error) { - logger.warn("No releases found", { - identifier, - error: releasesResponse.message, - }); - return { success: false, error: { code: "noReleasesFound" } }; - } - - const releasesProviderResponse = releasesResponse.reduce((acc, release) => { - if (!release.released_at) return acc; - - const releaseDate = new Date(release.released_at); - - acc.push({ - latestRelease: release.name ?? release.tag_name, - latestReleaseAt: releaseDate, - releaseUrl: release._links.self, - releaseDescription: release.description ?? undefined, - isPreRelease: releaseDate > new Date(), // For upcoming releases the `released_at` will be set to the future (https://docs.gitlab.com/api/releases/#upcoming-releases). Gitbreaker doesn't currently support the `upcoming_release` field (https://github.com/jdalrymple/gitbeaker/issues/3730) - }); - return acc; - }, []); - - const latestRelease = getLatestRelease(releasesProviderResponse, versionRegex); - if (!latestRelease) return { success: false, error: { code: "noMatchingVersion" } }; - - const details = await this.getDetailsAsync(api, identifier); - - return { success: true, data: { ...details, ...latestRelease } }; - } catch (error) { - const errorMessage = error instanceof Error ? error.message : String(error); - logger.warn("Failed to get releases", { - identifier, - error: errorMessage, - }); - return { success: false, error: { code: "unexpected", message: errorMessage } }; - } - } - - protected async getDetailsAsync(api: CoreGitlab, identifier: string): Promise { - try { - const response = await api.Projects.show(identifier); - - if (response instanceof Error) { - logger.warn("Failed to get details", { - identifier, - error: response.message, - }); - - return undefined; - } - - if (!response.web_url) { - logger.warn("No web URL found", { - identifier, - }); - return undefined; - } - - return { - projectUrl: response.web_url, - projectDescription: response.description ?? undefined, - isFork: response.forked_from_project !== null, - isArchived: response.archived, - createdAt: new Date(response.created_at), - starsCount: response.star_count, - openIssues: response.open_issues_count, - forksCount: response.forks_count, - }; - } catch (error) { - logger.warn("Failed to get details", { - identifier, - error: error instanceof Error ? error.message : String(error), - }); - - return undefined; - } - } - - private getApi() { - return new Gitlab({ - host: this.url("/").origin, - requesterFn: createRequesterFn( - async (serviceOptions: ResourceOptions, _: RequestOptions) => await defaultOptionsHandler(serviceOptions), - async (endpoint: string, options?: Record): Promise => { - if (options === undefined) { - throw new Error("Gitlab library is not configured correctly. Options must be provided."); - } - - const response = await fetchWithTrustedCertificatesAsync( - `${options.prefixUrl as string}${endpoint}`, - options, - ); - const headers = Object.fromEntries(response.headers.entries()); - - return { - status: response.status, - headers, - body: await response.json(), - } as FormattedResponse; - }, - ), - ...(this.hasSecretValue("personalAccessToken") ? { token: this.getSecretValue("personalAccessToken") } : {}), - }); - } -} diff --git a/packages/integrations/src/glances/glances-integration.ts b/packages/integrations/src/glances/glances-integration.ts index 6364ebcb7..5915b44b2 100644 --- a/packages/integrations/src/glances/glances-integration.ts +++ b/packages/integrations/src/glances/glances-integration.ts @@ -35,7 +35,7 @@ export class GlancesIntegration extends Integration implements ISystemHealthMoni if (session == null) throw new Error("Session was unexpectitly null"); - const stats = await this.getAllStatsAsync(); + const [stats, cpuTemp] = await Promise.all([this.getAllStatsAsync(), this.getCpuTempAsync()]); return { cpuUtilization: stats.cpu.total, @@ -58,7 +58,7 @@ export class GlancesIntegration extends Integration implements ISystemHealthMoni cpuModelName: stats.quicklook?.cpu_name ?? "Unknown", loadAverage: null, smart: [], - cpuTemp: undefined, + cpuTemp, gpu: stats.gpu.map((gpu) => ({ gpuId: gpu.gpu_id, name: gpu.name, @@ -80,6 +80,23 @@ export class GlancesIntegration extends Integration implements ISystemHealthMoni return await response.text(); } + private async getCpuTempAsync(): Promise { + // CPU temperature is optional data, so a transport error or payload drift on + // /api/4/sensors must not fail the whole getSystemInfoAsync() call. + try { + const response = await fetchWithTrustedCertificatesAsync(this.url("/api/4/sensors")); + + if (!response.ok) { + return undefined; + } + + const sensors = await sensorsSchema.parseAsync(await response.json()); + return parseGlancesCpuTempFromSensors(sensors); + } catch { + return undefined; + } + } + private async getAllStatsAsync() { const response = await fetchWithTrustedCertificatesAsync(this.url("/api/4/all")); @@ -173,3 +190,33 @@ const allSchema = z.object({ ) .default([]), }); + +const sensorsSchema = z.array( + z.object({ + label: z.string(), + value: z.number(), + type: z.string().optional(), + }), +); + +const cpuTempLabelPriority = ["CPU", "Package id 0"] as const; + +export const parseGlancesCpuTempFromSensors = (sensors: z.infer): number | undefined => { + const temperatureSensors = sensors.filter( + (sensor) => sensor.type === "temperature_core" || sensor.type === "temperature", + ); + + for (const label of cpuTempLabelPriority) { + const match = temperatureSensors.find((sensor) => sensor.label === label); + if (match) { + return match.value; + } + } + + const firstCore = temperatureSensors[0]; + if (firstCore) { + return firstCore.value; + } + + return undefined; +}; diff --git a/packages/integrations/src/immich/immich-integration.ts b/packages/integrations/src/immich/immich-integration.ts index 9e13ff953..c01386256 100644 --- a/packages/integrations/src/immich/immich-integration.ts +++ b/packages/integrations/src/immich/immich-integration.ts @@ -1,4 +1,13 @@ -import { getAlbumInfo, getAllAlbums, getMyUser, getServerStatistics, init, searchUsers } from "@immich/sdk"; +import { + getAlbumInfo, + getAllAlbums, + getMyUser, + getServerStatistics, + init, + searchAssets, + searchUsers, +} from "@immich/sdk"; +import type { AssetResponseDto, MetadataSearchDto } from "@immich/sdk"; import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http"; import { createLogger } from "@homarr/core/infrastructure/logs"; @@ -23,7 +32,6 @@ export interface ImmichAlbum { export interface ImmichAsset { id: string; - deviceAssetId: string; originalPath: string; thumbhash: string | null; fileModifiedAt: string; @@ -56,12 +64,16 @@ export class ImmichIntegration extends Integration { public async getAlbumAsync(albumId: string): Promise { this.initClient(); - const album = await getAlbumInfo({ id: albumId }, this.getRequestOptions()); + const requestOptions = this.getRequestOptions(); + const [album, albumAssets] = await Promise.all([ + getAlbumInfo({ id: albumId }, requestOptions), + this.searchAlbumAssetsAsync(albumId), + ]); const imageProxy = new ImageProxy(); return { albumName: album.albumName, assets: await Promise.all( - album.assets.map(async (asset) => { + albumAssets.map(async (asset) => { const publicLink = await imageProxy.createImageAsync( this.url(`/api/assets/${asset.id}/original`).toString(), { @@ -71,7 +83,6 @@ export class ImmichIntegration extends Integration { return { id: asset.id, type: asset.type, - deviceAssetId: asset.deviceAssetId, thumbhash: asset.thumbhash, fileCreatedAt: asset.fileCreatedAt, fileModifiedAt: asset.fileModifiedAt, @@ -104,6 +115,24 @@ export class ImmichIntegration extends Integration { return { success: true }; } + private async searchAlbumAssetsAsync(albumId: string): Promise { + const requestOptions = this.getRequestOptions(); + const assets: AssetResponseDto[] = []; + let nextPage: string | null = null; + + do { + const metadataSearchDto: MetadataSearchDto = { albumIds: [albumId] }; + if (nextPage !== null) { + metadataSearchDto.page = Number(nextPage); + } + const searchResult = await searchAssets({ metadataSearchDto }, requestOptions); + assets.push(...searchResult.assets.items); + nextPage = searchResult.assets.nextPage; + } while (nextPage !== null); + + return assets; + } + private getRequestOptions(fetchAsync = fetchWithTrustedCertificatesAsync) { return { // Undici and node types are not the same diff --git a/packages/integrations/src/index.ts b/packages/integrations/src/index.ts index 66cf4be51..8e20bf745 100644 --- a/packages/integrations/src/index.ts +++ b/packages/integrations/src/index.ts @@ -1,6 +1,6 @@ // General integrations -export { AdGuardHomeIntegration } from "./adguard-home/adguard-home-integration"; export { ArchiveTeamWarriorIntegration } from "./archive-team-warrior/archive-team-warrior-integration"; +export { AdGuardHomeIntegration } from "./adguard-home/adguard-home-integration"; export { TechnitiumDnsIntegration } from "./technitium/technitium-integration"; export { AnchorIntegration } from "./anchor/anchor-integration"; export { Aria2Integration } from "./download-client/aria2/aria2-integration"; @@ -64,7 +64,6 @@ export type { TdarrStatistics, TdarrWorker, } from "./interfaces/media-transcoding/media-transcoding-types"; -export type { ReleasesRepository, ReleaseResponse } from "./interfaces/releases-providers/releases-providers-types"; export type { Notification } from "./interfaces/notifications/notification-types"; export type { ImmichServerStats, ImmichAlbum, ImmichAsset } from "./immich/immich-integration"; export type { PaperlessNgxStats } from "./paperless-ngx/paperless-ngx-types"; @@ -75,17 +74,17 @@ export type { AnchorNotesListInput, AnchorNoteSummary, } from "./anchor/anchor-types"; -export type { - ArchiveTeamWarriorBandwidth, - ArchiveTeamWarriorItem, - ArchiveTeamWarriorStatus, -} from "./archive-team-warrior/archive-team-warrior-types"; export type { TracearrDashboardData } from "./tracearr/tracearr-types"; export type { SpeedtestTrackerDashboardData } from "./speedtest-tracker/speedtest-tracker-types"; export type { AudiobookshelfDashboardData } from "./audiobookshelf/audiobookshelf-types"; export type { NavidromeDashboardData } from "./navidrome/navidrome-types"; export type { UptimeKumaDashboardData } from "./uptime-kuma/uptime-kuma-types"; export type { UmamiVisitorStats } from "./umami/umami-types"; +export type { + ArchiveTeamWarriorBandwidth, + ArchiveTeamWarriorItem, + ArchiveTeamWarriorStatus, +} from "./archive-team-warrior/archive-team-warrior-types"; // Schemas export { anchorNotesListInputSchema } from "./anchor/anchor-types"; diff --git a/packages/integrations/src/interfaces/releases-providers/releases-providers-integration.ts b/packages/integrations/src/interfaces/releases-providers/releases-providers-integration.ts deleted file mode 100644 index e94f555fc..000000000 --- a/packages/integrations/src/interfaces/releases-providers/releases-providers-integration.ts +++ /dev/null @@ -1,21 +0,0 @@ -import type { ReleaseProviderResponse, ReleaseResponse } from "./releases-providers-types"; - -export const getLatestRelease = ( - releases: ReleaseProviderResponse[], - versionRegex?: string, -): ReleaseProviderResponse | null => { - const validReleases = releases.filter((result) => { - if (result.latestRelease) { - return versionRegex ? new RegExp(versionRegex).test(result.latestRelease) : true; - } - return true; - }); - - return validReleases.length === 0 - ? null - : validReleases.reduce((latest, current) => (current.latestReleaseAt > latest.latestReleaseAt ? current : latest)); -}; - -export interface ReleasesProviderIntegration { - getLatestMatchingReleaseAsync(identifier: string, versionRegex?: string): Promise; -} diff --git a/packages/integrations/src/interfaces/releases-providers/releases-providers-types.ts b/packages/integrations/src/interfaces/releases-providers/releases-providers-types.ts deleted file mode 100644 index fb89b0fcb..000000000 --- a/packages/integrations/src/interfaces/releases-providers/releases-providers-types.ts +++ /dev/null @@ -1,34 +0,0 @@ -import type { TranslationObject } from "@homarr/translation"; - -export interface ReleasesRepository extends Record { - id: string; - identifier: string; - versionRegex?: string; -} - -export interface DetailsProviderResponse { - projectUrl?: string; - projectDescription?: string; - isFork?: boolean; - isArchived?: boolean; - createdAt?: Date; - starsCount?: number; - openIssues?: number; - forksCount?: number; -} - -export interface ReleaseProviderResponse { - latestRelease: string; - latestReleaseAt: Date; - releaseUrl?: string; - releaseDescription?: string; - isPreRelease?: boolean; -} - -type ReleasesErrorKeys = keyof TranslationObject["widget"]["releases"]["error"]["messages"]; - -export type ReleaseData = DetailsProviderResponse & ReleaseProviderResponse; - -export type ReleaseError = { code: ReleasesErrorKeys } | { code: "unexpected"; message: string }; - -export type ReleaseResponse = { success: true; data: ReleaseData } | { success: false; error: ReleaseError }; diff --git a/packages/integrations/src/interfaces/timetable/timetable-integration.ts b/packages/integrations/src/interfaces/timetable/timetable-integration.ts deleted file mode 100644 index 74e9d3c02..000000000 --- a/packages/integrations/src/interfaces/timetable/timetable-integration.ts +++ /dev/null @@ -1,6 +0,0 @@ -import type { Station, Timetable, TimetableOptions } from "./timetable-types"; - -export interface TimetableIntegration { - searchStationsAsync(query: string): Promise; - getTimetableAsync(options: TimetableOptions): Promise; -} diff --git a/packages/integrations/src/interfaces/timetable/timetable-types.ts b/packages/integrations/src/interfaces/timetable/timetable-types.ts deleted file mode 100644 index 72350b5a3..000000000 --- a/packages/integrations/src/interfaces/timetable/timetable-types.ts +++ /dev/null @@ -1,33 +0,0 @@ -import z from "zod"; - -export interface Station { - id: string; - name: string; -} - -export const timetableOptionsSchema = z.object({ - stationId: z.string(), - limit: z.number().default(10), -}); - -export type TimetableOptions = z.infer; - -export interface TimetableEntry { - timestamp: Date; - delay: number; - line: { - name: string; - color: string | null; - } | null; - location: string; - platform: { - name: string; - hasChanged: boolean; - } | null; -} - -export interface Timetable { - stationId: string; - timestamp: Date; - entries: TimetableEntry[]; -} diff --git a/packages/integrations/src/linuxserverio/linuxserverio-integration.ts b/packages/integrations/src/linuxserverio/linuxserverio-integration.ts deleted file mode 100644 index 3178f2c4b..000000000 --- a/packages/integrations/src/linuxserverio/linuxserverio-integration.ts +++ /dev/null @@ -1,73 +0,0 @@ -import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http"; -import { createLogger } from "@homarr/core/infrastructure/logs"; - -import type { IntegrationTestingInput } from "../base/integration"; -import { Integration } from "../base/integration"; -import { TestConnectionError } from "../base/test-connection/test-connection-error"; -import type { TestingResult } from "../base/test-connection/test-connection-service"; -import type { ReleasesProviderIntegration } from "../interfaces/releases-providers/releases-providers-integration"; -import type { ReleaseResponse } from "../interfaces/releases-providers/releases-providers-types"; -import { releasesResponseSchema } from "./linuxserverio-schemas"; - -const logger = createLogger({ module: "linuxServerIOIntegration" }); - -export class LinuxServerIOIntegration extends Integration implements ReleasesProviderIntegration { - protected async testingAsync(input: IntegrationTestingInput): Promise { - const response = await input.fetchAsync(this.url("/health")); - - if (!response.ok) { - return TestConnectionError.StatusResult(response); - } - - return { - success: true, - }; - } - - private parseIdentifier(identifier: string) { - const [owner, name] = identifier.split("/"); - if (!owner || !name) { - logger.warn("Invalid identifier format. Expected 'owner/name' for identifier", { identifier }); - return null; - } - return { owner, name }; - } - - public async getLatestMatchingReleaseAsync(identifier: string): Promise { - const { name } = this.parseIdentifier(identifier) ?? {}; - if (!name) return { success: false, error: { code: "invalidIdentifier" } }; - - const releasesResponse = await fetchWithTrustedCertificatesAsync(this.url("/api/v1/images")); - if (!releasesResponse.ok) { - return { success: false, error: { code: "unexpected", message: releasesResponse.statusText } }; - } - - const releasesResponseJson: unknown = await releasesResponse.json(); - const { data, success, error } = releasesResponseSchema.safeParse(releasesResponseJson); - if (!success) { - return { success: false, error: { code: "unexpected", message: error.message } }; - } - - const release = data.data.repositories.linuxserver.find((repo) => repo.name === name); - if (!release) { - logger.warn("Repository not found on provider", { - name, - }); - return { success: false, error: { code: "noMatchingVersion" } }; - } - - return { - success: true, - data: { - latestRelease: release.version, - latestReleaseAt: release.version_timestamp, - releaseDescription: release.changelog?.shift()?.desc, - projectUrl: release.github_url, - projectDescription: release.description, - isArchived: release.deprecated, - createdAt: release.initial_date ? new Date(release.initial_date) : undefined, - starsCount: release.stars, - }, - }; - } -} diff --git a/packages/integrations/src/linuxserverio/linuxserverio-schemas.ts b/packages/integrations/src/linuxserverio/linuxserverio-schemas.ts deleted file mode 100644 index 3e3653697..000000000 --- a/packages/integrations/src/linuxserverio/linuxserverio-schemas.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { z } from "zod/v4"; - -export const releasesResponseSchema = z.object({ - data: z.object({ - repositories: z.object({ - linuxserver: z.array( - z.object({ - name: z.string(), - initial_date: z - .string() - .transform((value) => new Date(value)) - .optional(), - github_url: z.string(), - description: z.string(), - version: z.string(), - version_timestamp: z.string().transform((value) => new Date(value)), - stars: z.number(), - deprecated: z.boolean(), - changelog: z - .array( - z.object({ - date: z.string().transform((value) => new Date(value)), - desc: z.string(), - }), - ) - .optional(), - }), - ), - }), - }), -}); diff --git a/packages/integrations/src/media-organizer/lidarr/lidarr-integration.ts b/packages/integrations/src/media-organizer/lidarr/lidarr-integration.ts index 0bac3ec30..5b16b3ea8 100644 --- a/packages/integrations/src/media-organizer/lidarr/lidarr-integration.ts +++ b/packages/integrations/src/media-organizer/lidarr/lidarr-integration.ts @@ -69,36 +69,24 @@ export class LidarrIntegration extends Integration implements ICalendarIntegrati private getLinksForLidarrCalendarEvent = (event: z.infer) => { const links: CalendarLink[] = []; + // titleSlug is only present when Lidarr returns it; guard so a missing value + // neither breaks the link nor drops the event. + if (event.artist.titleSlug) { + links.push({ + href: this.externalUrl(`/artist/${event.artist.titleSlug}`).toString(), + name: "Lidarr", + logo: "/images/apps/lidarr.svg", + color: undefined, + isDark: true, + }); + } + for (const link of event.artist.links) { - switch (link.name) { - case "vgmdb": - links.push({ - href: link.url, - name: "VgmDB", - color: "#f5c518", - isDark: false, - logo: "/images/apps/vgmdb.svg", - }); - break; - case "imdb": - links.push({ - href: link.url, - name: "IMDb", - color: "#f5c518", - isDark: false, - logo: "/images/apps/imdb.png", - }); - break; - case "last": - links.push({ - href: link.url, - name: "LastFM", - color: "#cf222a", - isDark: false, - logo: "/images/apps/lastfm.svg", - }); - break; + const externalLink = lidarrExternalCalendarLinks[link.name]; + if (!externalLink) { + continue; } + links.push({ ...externalLink, href: link.url }); } return links; @@ -109,7 +97,7 @@ export class LidarrIntegration extends Integration implements ICalendarIntegrati ): z.infer["images"][number] | undefined => { const flatImages = [...event.images]; - const sortedImages = flatImages.sort( + const sortedImages = flatImages.toSorted( (imageA, imageB) => mediaOrganizerPriorities.indexOf(imageA.coverType) - mediaOrganizerPriorities.indexOf(imageB.coverType), ); @@ -149,6 +137,16 @@ const lidarrCalendarEventSchema = z.object({ title: z.string(), overview: z.string().optional(), images: lidarrCalendarEventImageSchema, - artist: z.object({ links: z.array(z.object({ url: z.string().url(), name: z.string() })), artistName: z.string() }), + artist: z.object({ + links: z.array(z.object({ url: z.string().url(), name: z.string() })), + artistName: z.string(), + titleSlug: z.string().optional(), + }), releaseDate: z.string().transform((value) => new Date(value)), }); + +const lidarrExternalCalendarLinks: Record> = { + vgmdb: { name: "VgmDB", color: "#f5c518", isDark: false, logo: "/images/apps/vgmdb.svg" }, + imdb: { name: "IMDb", color: "#f5c518", isDark: false, logo: "/images/apps/imdb.png" }, + last: { name: "LastFM", color: "#cf222a", isDark: false, logo: "/images/apps/lastfm.svg" }, +}; diff --git a/packages/integrations/src/npm/npm-integration.ts b/packages/integrations/src/npm/npm-integration.ts deleted file mode 100644 index b0c8c050b..000000000 --- a/packages/integrations/src/npm/npm-integration.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http"; - -import type { IntegrationTestingInput } from "../base/integration"; -import { Integration } from "../base/integration"; -import { TestConnectionError } from "../base/test-connection/test-connection-error"; -import type { TestingResult } from "../base/test-connection/test-connection-service"; -import type { ReleasesProviderIntegration } from "../interfaces/releases-providers/releases-providers-integration"; -import { getLatestRelease } from "../interfaces/releases-providers/releases-providers-integration"; -import type { ReleaseResponse } from "../interfaces/releases-providers/releases-providers-types"; -import { releasesResponseSchema } from "./npm-schemas"; - -export class NPMIntegration extends Integration implements ReleasesProviderIntegration { - protected async testingAsync(input: IntegrationTestingInput): Promise { - const response = await input.fetchAsync(this.url("/")); - - if (!response.ok) { - return TestConnectionError.StatusResult(response); - } - - return { - success: true, - }; - } - - public async getLatestMatchingReleaseAsync(identifier: string, versionRegex?: string): Promise { - if (!identifier) return { success: false, error: { code: "invalidIdentifier" } }; - - const releasesResponse = await fetchWithTrustedCertificatesAsync(this.url(`/${encodeURIComponent(identifier)}`)); - if (!releasesResponse.ok) { - return { success: false, error: { code: "unexpected", message: releasesResponse.statusText } }; - } - - const releasesResponseJson: unknown = await releasesResponse.json(); - const { data, success, error } = releasesResponseSchema.safeParse(releasesResponseJson); - if (!success) { - return { - success: false, - error: { - code: "unexpected", - message: releasesResponseJson ? JSON.stringify(releasesResponseJson, null, 2) : error.message, - }, - }; - } - - const formattedReleases = data.time.map((tag) => ({ - ...tag, - releaseUrl: `https://www.npmjs.com/package/${encodeURIComponent(data.name)}/v/${encodeURIComponent(tag.latestRelease)}`, - releaseDescription: data.versions[tag.latestRelease]?.description ?? "", - })); - - const latestRelease = getLatestRelease(formattedReleases, versionRegex); - if (!latestRelease) return { success: false, error: { code: "noMatchingVersion" } }; - - return { success: true, data: latestRelease }; - } -} diff --git a/packages/integrations/src/npm/npm-schemas.ts b/packages/integrations/src/npm/npm-schemas.ts deleted file mode 100644 index 5d21734e2..000000000 --- a/packages/integrations/src/npm/npm-schemas.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { z } from "zod/v4"; - -export const releasesResponseSchema = z.object({ - time: z - .record( - z.string(), - z.string().transform((value) => new Date(value)), - ) - .transform((version) => - Object.entries(version).map(([key, value]) => ({ - latestRelease: key, - latestReleaseAt: value, - })), - ), - versions: z.record(z.string(), z.object({ description: z.string() })), - name: z.string(), -}); diff --git a/packages/integrations/src/quay/quay-integration.ts b/packages/integrations/src/quay/quay-integration.ts deleted file mode 100644 index 21cbf797e..000000000 --- a/packages/integrations/src/quay/quay-integration.ts +++ /dev/null @@ -1,97 +0,0 @@ -import type { RequestInit, Response } from "undici"; - -import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http"; -import { createLogger } from "@homarr/core/infrastructure/logs"; - -import type { IntegrationTestingInput } from "../base/integration"; -import { Integration } from "../base/integration"; -import { TestConnectionError } from "../base/test-connection/test-connection-error"; -import type { TestingResult } from "../base/test-connection/test-connection-service"; -import type { ReleasesProviderIntegration } from "../interfaces/releases-providers/releases-providers-integration"; -import { getLatestRelease } from "../interfaces/releases-providers/releases-providers-integration"; -import type { - ReleaseProviderResponse, - ReleaseResponse, -} from "../interfaces/releases-providers/releases-providers-types"; -import { releasesResponseSchema } from "./quay-schemas"; - -const logger = createLogger({ module: "quayIntegration" }); - -export class QuayIntegration extends Integration implements ReleasesProviderIntegration { - private async withHeadersAsync(callback: (headers: RequestInit["headers"]) => Promise): Promise { - if (!this.hasSecretValue("personalAccessToken")) return await callback(undefined); - - return await callback({ - Authorization: `token ${this.getSecretValue("personalAccessToken")}`, - }); - } - - protected async testingAsync(input: IntegrationTestingInput): Promise { - const response = await this.withHeadersAsync(async (headers) => { - return await input.fetchAsync(this.url("/api/v1/discovery"), { - headers, - }); - }); - - if (!response.ok) { - return TestConnectionError.StatusResult(response); - } - - return { - success: true, - }; - } - - private parseIdentifier(identifier: string) { - const [owner, name] = identifier.split("/"); - if (!owner || !name) { - logger.warn("Invalid identifier format. Expected 'owner/name' for identifier", { - identifier, - }); - return null; - } - return { owner, name }; - } - - public async getLatestMatchingReleaseAsync(identifier: string, versionRegex?: string): Promise { - const parsedIdentifier = this.parseIdentifier(identifier); - if (!parsedIdentifier) return { success: false, error: { code: "invalidIdentifier" } }; - - const { owner, name } = parsedIdentifier; - - const releasesResponse = await this.withHeadersAsync(async (headers) => { - return await fetchWithTrustedCertificatesAsync( - this.url( - `/api/v1/repository/${encodeURIComponent(owner)}/${encodeURIComponent(name)}?includeTags=true&includeStats=true`, - ), - { - headers, - }, - ); - }); - if (!releasesResponse.ok) { - return { success: false, error: { code: "unexpected", message: releasesResponse.statusText } }; - } - - const releasesResponseJson: unknown = await releasesResponse.json(); - const { data, success, error } = releasesResponseSchema.safeParse(releasesResponseJson); - if (!success) { - return { success: false, error: { code: "unexpected", message: error.message } }; - } - - const releasesProviderResponse = Object.entries(data.tags).reduce((acc, [_, tag]) => { - if (!tag.name || !tag.last_modified) return acc; - acc.push({ - latestRelease: tag.name, - latestReleaseAt: new Date(tag.last_modified), - releaseUrl: `https://quay.io/repository/${encodeURIComponent(owner)}/${encodeURIComponent(name)}/tag/${encodeURIComponent(tag.name)}`, - }); - return acc; - }, []); - - const latestRelease = getLatestRelease(releasesProviderResponse, versionRegex); - if (!latestRelease) return { success: false, error: { code: "noMatchingVersion" } }; - - return { success: true, data: { projectDescription: data.description, ...latestRelease } }; - } -} diff --git a/packages/integrations/src/quay/quay-schemas.ts b/packages/integrations/src/quay/quay-schemas.ts deleted file mode 100644 index e24fe2d83..000000000 --- a/packages/integrations/src/quay/quay-schemas.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { z } from "zod/v4"; - -export const releasesResponseSchema = z.object({ - description: z.string().optional(), - tags: z.record( - z.string(), - z.object({ - name: z.string(), - last_modified: z.string(), - }), - ), -}); diff --git a/packages/integrations/src/search-ch/search-ch-integration.ts b/packages/integrations/src/search-ch/search-ch-integration.ts deleted file mode 100644 index cbaf8e80d..000000000 --- a/packages/integrations/src/search-ch/search-ch-integration.ts +++ /dev/null @@ -1,141 +0,0 @@ -import z from "zod"; - -import { ResponseError } from "@homarr/common/server"; -import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http"; - -import type { IntegrationTestingInput } from "../base/integration"; -import { Integration } from "../base/integration"; -import { TestConnectionError } from "../base/test-connection/test-connection-error"; -import type { TestingResult } from "../base/test-connection/test-connection-service"; -import type { TimetableIntegration } from "../interfaces/timetable/timetable-integration"; -import type { Station, Timetable, TimetableOptions } from "../interfaces/timetable/timetable-types"; - -const supportedStationTypes = ["bus", "tram", "train", "ship", "cablecar", "funicular", "chairlift"]; - -export class SearchChIntegration extends Integration implements TimetableIntegration { - protected async testingAsync(input: IntegrationTestingInput): Promise { - const response = await input.fetchAsync(this.url("/timetable/api/completion.json", { term: "Zürich" })); - - if (!response.ok) { - return TestConnectionError.StatusResult(response); - } - - return { - success: true, - }; - } - - /** - * Searches for stations matching the given query. - * @param query The search query for station names. - * @returns A list of stations matching the search query. - * @docs https://search.ch/fahrplan/api/help - */ - public async searchStationsAsync(query: string): Promise { - const response = await fetchWithTrustedCertificatesAsync( - this.url("/timetable/api/completion.json", { - term: query, - show_ids: 1, - nofavorites: 1, - }), - ); - - if (!response.ok) { - throw new ResponseError(response); - } - - const data = await searchSchema.parseAsync(await response.json()); - - return ( - data - // Remove completions like addresses, we only want stations. - .filter((item) => supportedStationTypes.some((type) => item.iconclass.endsWith(type))) - .map((item) => (item.id !== undefined ? { id: item.id, name: item.label } : null)) - .filter((item) => item !== null) - ); - } - - /** - * Gets the departure timetable for a given station. - * @param options Timetable options including station ID and limit. - * @returns timetable for the given station. - * @docs https://search.ch/fahrplan/api/help - */ - public async getTimetableAsync(options: TimetableOptions): Promise { - const now = new Date(); - // The following format is used [01.01.2000, 23:59:00]. The API allows usage of DD.MM.YYYY format and HH:mm format - const [date, time] = now.toLocaleString("en-CH", { timeZone: "Europe/Zurich" }).split(", ") as [string, string]; - - const response = await fetchWithTrustedCertificatesAsync( - this.url("/timetable/api/stationboard.json", { - stop: options.stationId, - limit: options.limit, - show_delays: 1, - show_tracks: 1, - date, - time: time.substring(0, 5), // Only keep the HH:mm part of the time, as that's what the API expects - }), - ); - - if (!response.ok) { - throw new ResponseError(response); - } - - const data = await timetableSchema.parseAsync(await response.json()); - - return { - stationId: options.stationId, - timestamp: now, - entries: data.connections.map((connection) => { - // Color is in format A35~F10~ for foreground and background color. Some only have ~~ and therefore no color - const color = connection.color.split("~")[0]; - - return { - timestamp: connection.time, - line: connection.line - ? { - name: connection.line, - color: color && color.length >= 1 ? `#${color}` : null, - } - : null, - location: connection.terminal.name, - delay: connection.dep_delay, - platform: connection.track - ? { - // If the track name contains "!", it indicates a change in the track - // so we remove the "!" from the name and set hasChanged to true - name: connection.track.replace("!", ""), - hasChanged: connection.track.includes("!"), - } - : null, - }; - }), - }; - } -} - -const timetableSchema = z.object({ - connections: z.array( - z.object({ - time: z.string().transform((str) => new Date(str)), - line: z.string().optional(), - color: z.string(), - terminal: z.object({ - name: z.string(), - }), - track: z.string().optional(), - dep_delay: z - .string() - .optional() - .transform((str) => (str ? parseInt(str) : 0)), - }), - ), -}); - -const searchSchema = z.array( - z.object({ - id: z.string().optional(), - label: z.string(), - iconclass: z.string(), // This is in format sl-icon-type-{type} - }), -); diff --git a/packages/integrations/src/types.ts b/packages/integrations/src/types.ts index afeaf1db2..273b5b2d2 100644 --- a/packages/integrations/src/types.ts +++ b/packages/integrations/src/types.ts @@ -15,7 +15,6 @@ export * from "./opnsense/opnsense-types"; export * from "./interfaces/media-releases"; export * from "./coolify/coolify-types"; export * from "./anchor/anchor-types"; -export * from "./interfaces/timetable/timetable-types"; export * from "./tracearr/tracearr-types"; export * from "./speedtest-tracker/speedtest-tracker-types"; export * from "./uptime-kuma/uptime-kuma-types"; diff --git a/packages/integrations/test/glances-unit.spec.ts b/packages/integrations/test/glances-unit.spec.ts index 121ab594a..07f908e9e 100644 --- a/packages/integrations/test/glances-unit.spec.ts +++ b/packages/integrations/test/glances-unit.spec.ts @@ -1,11 +1,11 @@ // @vitest-environment node -import { describe, expect, test, vi } from "vitest"; +import { beforeEach, describe, expect, test, vi } from "vitest"; import { createDb } from "@homarr/db/test"; import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http"; -import { GlancesIntegration } from "../src/glances/glances-integration"; +import { GlancesIntegration, parseGlancesCpuTempFromSensors } from "../src/glances/glances-integration"; // ─── module mocks ──────────────────────────────────────────────────────────── @@ -64,6 +64,19 @@ function makeResponse(body: unknown) { } as never; } +function mockGlancesFetch(statsBody: unknown, sensorsBody: unknown) { + mockFetch.mockImplementation(((url: string | URL) => { + const urlString = url.toString(); + if (urlString.endsWith("/api/4/all")) { + return Promise.resolve(makeResponse(statsBody)); + } + if (urlString.endsWith("/api/4/sensors")) { + return Promise.resolve(makeResponse(sensorsBody)); + } + return Promise.reject(new Error(`Unexpected fetch URL: ${urlString}`)); + }) as typeof fetchWithTrustedCertificatesAsync); +} + const integrationInput = { id: "test-glances", name: "Glances", @@ -75,8 +88,12 @@ const integrationInput = { // ─── tests ─────────────────────────────────────────────────────────────────── describe("GlancesIntegration schema", () => { + beforeEach(() => { + mockFetch.mockReset(); + }); + test("parses stats when quicklook is present", async () => { - mockFetch.mockResolvedValueOnce(makeResponse({ ...baseStats, quicklook: { cpu_name: "Apple M1" } })); + mockGlancesFetch({ ...baseStats, quicklook: { cpu_name: "Apple M1" } }, []); const integration = new GlancesIntegration(integrationInput); const result = await integration.getSystemInfoAsync(); @@ -86,7 +103,7 @@ describe("GlancesIntegration schema", () => { }); test("returns Unknown cpuModelName when quicklook is absent (macOS scenario)", async () => { - mockFetch.mockResolvedValueOnce(makeResponse(baseStats)); + mockGlancesFetch(baseStats, []); const integration = new GlancesIntegration(integrationInput); const result = await integration.getSystemInfoAsync(); @@ -95,9 +112,41 @@ describe("GlancesIntegration schema", () => { }); test("does not throw a Zod parse error when quicklook is missing", async () => { - mockFetch.mockResolvedValueOnce(makeResponse(baseStats)); + mockGlancesFetch(baseStats, []); const integration = new GlancesIntegration(integrationInput); await expect(integration.getSystemInfoAsync()).resolves.not.toThrow(); }); + + test("reads CPU temperature from sensors", async () => { + mockGlancesFetch(baseStats, [ + { label: "CPU", unit: "C", value: 58, type: "temperature_core" }, + { label: "Core 0", unit: "C", value: 54, type: "temperature_core" }, + ]); + + const integration = new GlancesIntegration(integrationInput); + const result = await integration.getSystemInfoAsync(); + + expect(result.cpuTemp).toBe(58); + }); +}); + +describe("parseGlancesCpuTempFromSensors", () => { + test("prefers CPU label over core sensors", () => { + const result = parseGlancesCpuTempFromSensors([ + { label: "Core 0", value: 54, type: "temperature_core" }, + { label: "CPU", value: 58, type: "temperature_core" }, + ]); + + expect(result).toBe(58); + }); + + test("falls back to Package id 0", () => { + const result = parseGlancesCpuTempFromSensors([ + { label: "Core 0", value: 54, type: "temperature_core" }, + { label: "Package id 0", value: 62, type: "temperature_core" }, + ]); + + expect(result).toBe(62); + }); }); diff --git a/packages/integrations/test/lidarr-unit.spec.ts b/packages/integrations/test/lidarr-unit.spec.ts new file mode 100644 index 000000000..b788565a6 --- /dev/null +++ b/packages/integrations/test/lidarr-unit.spec.ts @@ -0,0 +1,76 @@ +// @vitest-environment node + +import { describe, expect, test, vi } from "vitest"; + +import { createDb } from "@homarr/db/test"; +import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http"; + +import { LidarrIntegration } from "../src/media-organizer/lidarr/lidarr-integration"; + +vi.mock("@homarr/db", async (importActual) => { + const actual = await importActual(); + return { ...actual, db: createDb() }; +}); + +vi.mock("@homarr/core/infrastructure/certificates", async (importActual) => { + const actual = await importActual(); + return { + ...actual, + getTrustedCertificateHostnamesAsync: vi.fn().mockResolvedValue([]), + }; +}); + +vi.mock("@homarr/core/infrastructure/http", () => ({ + fetchWithTrustedCertificatesAsync: vi.fn(), +})); + +const mockFetch = vi.mocked(fetchWithTrustedCertificatesAsync); + +const integrationInput = { + id: "test-lidarr", + name: "Lidarr", + url: "http://localhost:8686", + decryptedSecrets: [{ kind: "apiKey" as const, value: "test-key" }], + externalUrl: null, +}; + +describe("LidarrIntegration calendar", () => { + test("includes Lidarr artist link in calendar events", async () => { + mockFetch.mockResolvedValueOnce({ + ok: true, + json: () => + Promise.resolve([ + { + title: "Test Album", + overview: "Overview", + images: [], + artist: { + artistName: "Test Artist", + titleSlug: "test-artist", + links: [{ name: "last", url: "https://last.fm/music/Test+Artist" }], + }, + releaseDate: "2026-01-01T00:00:00Z", + }, + ]), + } as never); + + const integration = new LidarrIntegration(integrationInput); + const events = await integration.getCalendarEventsAsync(new Date("2026-01-01"), new Date("2026-01-31")); + + expect(events[0]?.links[0]).toMatchObject({ + name: "Lidarr", + href: "http://localhost:8686/artist/test-artist", + logo: "/images/apps/lidarr.svg", + }); + // The existing external artist link must survive the Lidarr-link prepend. + expect(events[0]?.links).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + name: "LastFM", + href: "https://last.fm/music/Test+Artist", + logo: "/images/apps/lastfm.svg", + }), + ]), + ); + }); +}); diff --git a/packages/redis/src/index.ts b/packages/redis/src/index.ts index 6c99c11b6..02258a2e0 100644 --- a/packages/redis/src/index.ts +++ b/packages/redis/src/index.ts @@ -1,42 +1,22 @@ import type { LogLevel } from "@homarr/core/infrastructure/logs/constants"; -import { createListChannel, createQueueChannel, createSubPubChannel } from "./lib/channel"; +import { createListChannel, createSubPubChannel } from "./lib/channel"; export { - createCacheChannel, - createItemAndIntegrationChannel, - createItemChannel, - createIntegrationOptionsChannel, - createWidgetOptionsChannel, - createChannelWithLatestAndEvents, - createChannelEventHistory, handshakeAsync, createSubPubChannel, createGetSetChannel, - createQueryCacheChannel, - getAllQueryCacheAsync, + setQueryCacheAsync, + getQueryCacheAsync, + removeQueryCacheAsync, invalidateIntegrationCacheAsync, } from "./lib/channel"; -export { createIntegrationHistoryChannel } from "./lib/channels/history-channel"; - -export const exampleChannel = createSubPubChannel<{ message: string }>("example"); export const pingChannel = createSubPubChannel< { url: string; statusCode: number; durationMs: number } | { url: string; error: string } >("ping"); export const pingUrlChannel = createListChannel("ping-url"); -export const homeAssistantEntityState = createSubPubChannel<{ - entityId: string; - state: string; -}>("home-assistant/entity-state"); - -export const queueChannel = createQueueChannel<{ - name: string; - executionDate: Date; - data: unknown; -}>("common-queue"); - export interface LoggerMessage { message: string; level: LogLevel; diff --git a/packages/redis/src/lib/channel.ts b/packages/redis/src/lib/channel.ts index d04d1499a..1b1e582c1 100644 --- a/packages/redis/src/lib/channel.ts +++ b/packages/redis/src/lib/channel.ts @@ -1,8 +1,7 @@ import superjson from "superjson"; -import { createId, hashObjectBase64 } from "@homarr/common"; +import { createId } from "@homarr/common"; import { createLogger } from "@homarr/core/infrastructure/logs"; -import type { WidgetKind } from "@homarr/definitions"; import { ChannelSubscriptionTracker } from "./channel-subscription-tracker"; import { createRedisConnection } from "./connection"; @@ -129,218 +128,38 @@ export const createGetSetChannel = (name: string) => { }; }; -interface QueryCacheChannelOptions { - userId: string; - boardId: string; - key: string; - ttlMs: number; - maxValueBytes: number; -} +const queryCacheKey = (userId: string, boardId: string) => `qc:${userId}:${boardId}`; -const queryCacheHashKey = (userId: string, boardId: string) => `qc:${userId}:${boardId}`; - -export const createQueryCacheChannel = ({ userId, boardId, key, ttlMs, maxValueBytes }: QueryCacheChannelOptions) => { - const hkey = queryCacheHashKey(userId, boardId); - - return { - name: `${hkey}:${key}`, - getAsync: async () => { - return await getSetClient.hget(hkey, key); - }, - setAsync: async (value: string) => { - if (Buffer.byteLength(value, "utf8") > maxValueBytes) { - logger.warn("Query cache value exceeded maximum size", { - channel: hkey, - valueBytes: Buffer.byteLength(value, "utf8"), - maxValueBytes, - }); - return false; - } - - await getSetClient.hset(hkey, key, value); - await getSetClient.pexpire(hkey, ttlMs); - return true; - }, - removeAsync: async () => { - await getSetClient.hdel(hkey, key); - }, - }; -}; - -export const getAllQueryCacheAsync = async (userId: string, boardId: string): Promise> => { - const hkey = queryCacheHashKey(userId, boardId); - return await getSetClient.hgetall(hkey); -}; - -/** - * Creates a new cache channel. - * @param name name of the channel - * @param cacheDurationMs duration in milliseconds to cache - * @returns cache channel object - */ -export const createCacheChannel = (name: string, cacheDurationMs: number = 5 * 60 * 1000) => { - const cacheChannelName = `cache:${name}`; - - return { - /** - * Get the data from the cache channel. - * @returns data or null if not found or expired - */ - getAsync: async () => { - const data = await getSetClient.get(cacheChannelName); - if (!data) return null; - - const parsedData = superjson.parse<{ data: TData; timestamp: Date }>(data); - const now = new Date(); - const diff = now.getTime() - parsedData.timestamp.getTime(); - if (diff > cacheDurationMs) return null; - - return parsedData; - }, - /** - * Consume the data from the cache channel, if not present or expired, it will call the callback to get new data. - * @param callback callback function to get new data if not present or expired - * @returns data or new data if not present or expired - */ - consumeAsync: async (callback: () => Promise) => { - const data = await getSetClient.get(cacheChannelName); - - const getNewDataAsync = async () => { - logger.debug(`Cache miss for channel '${cacheChannelName}'`); - const newData = await callback(); - const result = { data: newData, timestamp: new Date() }; - await getSetClient.set(cacheChannelName, superjson.stringify(result)); - logger.debug(`Cache updated for channel '${cacheChannelName}'`); - return result; - }; - - if (!data) { - return await getNewDataAsync(); - } - - const parsedData = superjson.parse<{ data: TData; timestamp: Date }>(data); - const now = new Date(); - const diff = now.getTime() - parsedData.timestamp.getTime(); - - if (diff > cacheDurationMs) { - return await getNewDataAsync(); - } - - logger.debug(`Cache hit for channel '${cacheChannelName}'`); - - return parsedData; - }, - /** - * Invalidate the cache channels data. - */ - invalidateAsync: async () => { - await getSetClient.del(cacheChannelName); - }, - /** - * Set the data in the cache channel. - * @param data data to be stored in the cache channel - */ - setAsync: async (data: TData) => { - await getSetClient.set(cacheChannelName, superjson.stringify({ data, timestamp: new Date() })); - }, - }; -}; - -export const createItemAndIntegrationChannel = (kind: WidgetKind, integrationId: string) => { - const channelName = `item:${kind}:integration:${integrationId}`; - return createChannelWithLatestAndEvents(channelName); -}; - -export const createIntegrationOptionsChannel = ( - integrationId: string, - queryKey: string, - options: Record, +export const setQueryCacheAsync = async ( + userId: string, + boardId: string, + value: string, + ttlMs: number, + maxValueBytes: number, ) => { - const optionsKey = hashObjectBase64(options); - const channelName = `integration:${integrationId}:${queryKey}:options:${optionsKey}`; - return createChannelWithLatestAndEvents(channelName); + if (Buffer.byteLength(value, "utf8") > maxValueBytes) { + logger.warn("Query cache value exceeded maximum size", { + key: queryCacheKey(userId, boardId), + valueBytes: Buffer.byteLength(value, "utf8"), + maxValueBytes, + }); + return false; + } + await getSetClient.set(queryCacheKey(userId, boardId), value, "PX", ttlMs); + return true; +}; + +export const getQueryCacheAsync = async (userId: string, boardId: string) => + await getSetClient.get(queryCacheKey(userId, boardId)); + +export const removeQueryCacheAsync = async (userId: string, boardId: string) => { + await getSetClient.del(queryCacheKey(userId, boardId)); }; -/** - * Invalidates all cached data for a given integration by deleting every Redis key - * that starts with `integration:${integrationId}:` as well as the integration's - * session store entry. This ensures that stale data from a misconfigured or - * updated integration is never served from cache. - */ export const invalidateIntegrationCacheAsync = async (integrationId: string): Promise => { const client = getSetClient as typeof getSetClient | null; - if (!client) { - return; - } - - const patterns = [`integration:${integrationId}:*`, `session-store:${integrationId}`]; - let deletedCount = 0; - for (const pattern of patterns) { - let cursor = "0"; - do { - const [nextCursor, keys] = await client.scan(cursor, "MATCH", pattern, "COUNT", 200); - cursor = nextCursor; - if (keys.length > 0) { - await client.del(...keys); - deletedCount += keys.length; - } - } while (cursor !== "0"); - } - if (deletedCount > 0) { - logger.info("Invalidated integration cache", { integrationId, deletedKeys: deletedCount }); - } -}; - -export const createWidgetOptionsChannel = ( - widgetKind: WidgetKind, - queryKey: string, - options: Record, -) => { - const optionsKey = hashObjectBase64(options); - const channelName = `widget:${widgetKind}:${queryKey}:options:${optionsKey}`; - return createChannelWithLatestAndEvents(channelName); -}; - -export const createItemChannel = (itemId: string) => { - return createChannelWithLatestAndEvents(`item:${itemId}`); -}; - -export const createChannelEventHistory = (channelName: string, maxElements = 32) => { - return { - subscribe: (callback: (data: TData) => void) => { - return ChannelSubscriptionTracker.subscribe(channelName, (message) => { - callback(superjson.parse(message)); - }); - }, - pushAsync: async (data: TData, options = { publish: false }) => { - if (options.publish) await publisher.publish(channelName, superjson.stringify(data)); - await getSetClient.lpush(channelName, superjson.stringify({ data, timestamp: new Date() })); - await getSetClient.ltrim(channelName, 0, maxElements); - }, - clearAsync: async () => { - await getSetClient.del(channelName); - }, - /** - * Returns a slice of the available data in the channel. - * If any of the indexes are out of range (or -range), returned data will be clamped. - * @param startIndex Start index of the slice, negative values are counted from the end, defaults at beginning of range. - * @param endIndex End index of the slice, negative values are counted from the end, defaults at end of range. - */ - getSliceAsync: async (startIndex = 0, endIndex = -1) => { - const range = await getSetClient.lrange(channelName, startIndex, endIndex); - return range.map((item) => superjson.parse<{ data: TData; timestamp: Date }>(item)); - }, - getSliceUntilTimeAsync: async (time: Date) => { - const itemsInCollection = await getSetClient.lrange(channelName, 0, -1); - return itemsInCollection - .map((item) => superjson.parse<{ data: TData; timestamp: Date }>(item)) - .filter((item) => item.timestamp < time); - }, - getLengthAsync: async () => { - return await getSetClient.llen(channelName); - }, - name: channelName, - }; + if (!client) return; + await client.del(`session-store:${integrationId}`); }; /** @@ -407,40 +226,6 @@ export const createChannelEventHistoryOld = (channelName: string, maxElem }; }; -export const createChannelWithLatestAndEvents = (channelName: string) => { - return { - subscribe: (callback: (data: TData) => void) => { - return ChannelSubscriptionTracker.subscribe(channelName, (message) => { - callback(superjson.parse(message)); - }); - }, - publishAndUpdateLastStateAsync: async (data: TData) => { - // Set first, then publish. If publish fails the storage is still consistent - // and subscribers will catch up on the next event. - const payload = superjson.stringify({ data, timestamp: new Date() }); - await getSetClient.set(channelName, payload); - try { - await publisher.publish(channelName, superjson.stringify(data)); - } catch (error) { - logger.warn("Failed to publish cache update (storage is correct, subscribers will catch up)", { - channel: channelName, - error: error instanceof Error ? error.message : String(error), - }); - } - }, - setAsync: async (data: TData) => { - await getSetClient.set(channelName, superjson.stringify({ data, timestamp: new Date() })); - }, - getAsync: async () => { - const data = await getSetClient.get(channelName); - if (!data) return null; - - return superjson.parse<{ data: TData; timestamp: Date }>(data); - }, - name: channelName, - }; -}; - const queueClient = createRedisConnection(); type WithId = TItem & { _id: string }; diff --git a/packages/redis/src/lib/channels/history-channel.ts b/packages/redis/src/lib/channels/history-channel.ts deleted file mode 100644 index 541830464..000000000 --- a/packages/redis/src/lib/channels/history-channel.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { createChannelEventHistory } from "../channel"; - -export const createIntegrationHistoryChannel = (integrationId: string, queryKey: string, maxElements = 32) => { - const channelName = `integration:${integrationId}:history:${queryKey}`; - return createChannelEventHistory(channelName, maxElements); -}; diff --git a/packages/request-handler/src/anchor-notes.ts b/packages/request-handler/src/anchor-notes.ts index bd6f19e44..75677b8ba 100644 --- a/packages/request-handler/src/anchor-notes.ts +++ b/packages/request-handler/src/anchor-notes.ts @@ -1,30 +1,22 @@ -import dayjs from "dayjs"; - import type { AnchorNote, AnchorNotesListInput, AnchorNoteSummary } from "@homarr/integrations"; import { createIntegrationAsync } from "@homarr/integrations"; -import { createCachedIntegrationRequestHandler } from "./lib/cached-integration-request-handler"; +import { createIntegrationRequestHandler } from "./lib/integration-request-handler"; -export const anchorNotesListRequestHandler = createCachedIntegrationRequestHandler< +export const anchorNotesListRequestHandler = createIntegrationRequestHandler< AnchorNoteSummary[], "anchor", AnchorNotesListInput >({ - queryKey: "anchorNotesList", - cacheDuration: dayjs.duration(15, "seconds"), async requestAsync(integration, input) { const instance = await createIntegrationAsync(integration); return instance.listNotesAsync(input); }, }); -export const anchorNoteRequestHandler = createCachedIntegrationRequestHandler( - { - queryKey: "anchorNote", - cacheDuration: dayjs.duration(15, "seconds"), - async requestAsync(integration, input) { - const instance = await createIntegrationAsync(integration); - return instance.getNoteAsync(input.noteId); - }, +export const anchorNoteRequestHandler = createIntegrationRequestHandler({ + async requestAsync(integration, input) { + const instance = await createIntegrationAsync(integration); + return instance.getNoteAsync(input.noteId); }, -); +}); diff --git a/packages/request-handler/src/archive-team-warrior.ts b/packages/request-handler/src/archive-team-warrior.ts index 4038ec9dc..e02391f19 100644 --- a/packages/request-handler/src/archive-team-warrior.ts +++ b/packages/request-handler/src/archive-team-warrior.ts @@ -1,12 +1,10 @@ -import dayjs from "dayjs"; - import type { IntegrationKindByCategory } from "@homarr/definitions"; import type { ArchiveTeamWarriorStatus } from "@homarr/integrations"; import { createIntegrationAsync } from "@homarr/integrations"; -import { createCachedIntegrationRequestHandler } from "./lib/cached-integration-request-handler"; +import { createIntegrationRequestHandler } from "./lib/integration-request-handler"; -export const archiveTeamWarriorRequestHandler = createCachedIntegrationRequestHandler< +export const archiveTeamWarriorRequestHandler = createIntegrationRequestHandler< ArchiveTeamWarriorStatus, IntegrationKindByCategory<"archiving">, Record @@ -15,6 +13,4 @@ export const archiveTeamWarriorRequestHandler = createCachedIntegrationRequestHa const integrationInstance = await createIntegrationAsync(integration); return await integrationInstance.getStatusAsync(); }, - cacheDuration: dayjs.duration(5, "minute"), - queryKey: "archiveTeamWarriorStatus", }); diff --git a/packages/request-handler/src/audiobookshelf.ts b/packages/request-handler/src/audiobookshelf.ts index bb4b2d83c..12562342c 100644 --- a/packages/request-handler/src/audiobookshelf.ts +++ b/packages/request-handler/src/audiobookshelf.ts @@ -1,11 +1,9 @@ -import dayjs from "dayjs"; - import { createIntegrationAsync } from "@homarr/integrations"; import type { AudiobookshelfDashboardData } from "@homarr/integrations/types"; -import { createCachedIntegrationRequestHandler } from "./lib/cached-integration-request-handler"; +import { createIntegrationRequestHandler } from "./lib/integration-request-handler"; -export const audiobookshelfRequestHandler = createCachedIntegrationRequestHandler< +export const audiobookshelfRequestHandler = createIntegrationRequestHandler< AudiobookshelfDashboardData, "audiobookshelf", Record @@ -14,6 +12,4 @@ export const audiobookshelfRequestHandler = createCachedIntegrationRequestHandle const integrationInstance = await createIntegrationAsync(integration); return await integrationInstance.getDashboardDataAsync(); }, - cacheDuration: dayjs.duration(10, "minutes"), - queryKey: "audiobookshelfDashboard", }); diff --git a/packages/request-handler/src/beszel.ts b/packages/request-handler/src/beszel.ts index 6dbe20ae0..4a60a3a3a 100644 --- a/packages/request-handler/src/beszel.ts +++ b/packages/request-handler/src/beszel.ts @@ -1,5 +1,3 @@ -import dayjs from "dayjs"; - import { createLogger } from "@homarr/core/infrastructure/logs"; import { createIntegrationAsync } from "@homarr/integrations"; import type { @@ -12,7 +10,7 @@ import type { BeszelSystemStatsRecord, } from "@homarr/integrations/types"; -import { createCachedIntegrationRequestHandler } from "./lib/cached-integration-request-handler"; +import { createIntegrationRequestHandler } from "./lib/integration-request-handler"; const logger = createLogger({ module: "beszelRequestHandler" }); @@ -53,7 +51,7 @@ function mapToSystemRow(system: BeszelSystem, details: BeszelSystemDetails | nul }; } -export const beszelSystemsRequestHandler = createCachedIntegrationRequestHandler< +export const beszelSystemsRequestHandler = createIntegrationRequestHandler< BeszelSystemRow[], "beszel" | "mock", Record @@ -64,7 +62,10 @@ export const beszelSystemsRequestHandler = createCachedIntegrationRequestHandler const systems = await instance.getSystemsAsync(); const enriched = await Promise.all( systems.map(async (system) => { - const details = await instance.getSystemDetailsAsync(system.id).catch(() => null); + const details = await instance.getSystemDetailsAsync(system.id).catch((error) => { + logger.warn("Failed to fetch Beszel system details", { systemId: system.id, error: String(error) }); + return null; + }); return mapToSystemRow(system, details); }), ); @@ -75,10 +76,6 @@ export const beszelSystemsRequestHandler = createCachedIntegrationRequestHandler }); return enriched; }, - cacheDuration: dayjs.duration(1, "second"), - fallbackToStaleOnError: true, - retry: { attempts: 2, delayMs: 500 }, - queryKey: "beszelSystems", }); export interface BeszelAlertsData { @@ -86,7 +83,7 @@ export interface BeszelAlertsData { history: BeszelAlertHistory[]; } -export const beszelAlertsRequestHandler = createCachedIntegrationRequestHandler< +export const beszelAlertsRequestHandler = createIntegrationRequestHandler< BeszelAlertsData, "beszel" | "mock", { includeHistory: boolean; maxHistoryItems: number } @@ -104,10 +101,6 @@ export const beszelAlertsRequestHandler = createCachedIntegrationRequestHandler< }); return { alerts, history }; }, - cacheDuration: dayjs.duration(1, "second"), - fallbackToStaleOnError: true, - retry: { attempts: 2, delayMs: 500 }, - queryKey: "beszelAlerts", }); const timePeriodConfig: Record = { @@ -124,7 +117,7 @@ export interface BeszelStatsData { containerStats: BeszelContainerStatsRecord[]; } -export const beszelStatsRequestHandler = createCachedIntegrationRequestHandler< +export const beszelStatsRequestHandler = createIntegrationRequestHandler< BeszelStatsData, "beszel" | "mock", { systemId: string; timePeriod: string; includeDocker: boolean } @@ -135,7 +128,10 @@ export const beszelStatsRequestHandler = createCachedIntegrationRequestHandler< const instance = await createIntegrationAsync(integration); const systemStats = await instance.getSystemStatsAsync(input.systemId, config.type, config.perPage); const containerStats = input.includeDocker - ? await instance.getContainerStatsAsync(input.systemId, config.type, config.perPage).catch(() => []) + ? await instance.getContainerStatsAsync(input.systemId, config.type, config.perPage).catch((error) => { + logger.warn("Failed to fetch Beszel container stats", { systemId: input.systemId, error: String(error) }); + return []; + }) : []; logger.debug("beszelStats fetch completed", { integrationId: integration.id, @@ -147,8 +143,4 @@ export const beszelStatsRequestHandler = createCachedIntegrationRequestHandler< }); return { systemStats, containerStats }; }, - cacheDuration: dayjs.duration(1, "second"), - fallbackToStaleOnError: true, - retry: { attempts: 2, delayMs: 500 }, - queryKey: "beszelStats", }); diff --git a/packages/request-handler/src/calendar.ts b/packages/request-handler/src/calendar.ts index dd198912a..a358d13e8 100644 --- a/packages/request-handler/src/calendar.ts +++ b/packages/request-handler/src/calendar.ts @@ -4,9 +4,9 @@ import type { IntegrationKindByCategory } from "@homarr/definitions"; import { createIntegrationAsync } from "@homarr/integrations"; import type { CalendarEvent, RadarrReleaseType } from "@homarr/integrations/types"; -import { createCachedIntegrationRequestHandler } from "./lib/cached-integration-request-handler"; +import { createIntegrationRequestHandler } from "./lib/integration-request-handler"; -export const calendarMonthRequestHandler = createCachedIntegrationRequestHandler< +export const calendarMonthRequestHandler = createIntegrationRequestHandler< CalendarEvent[], IntegrationKindByCategory<"calendar">, { year: number; month: number; releaseType: RadarrReleaseType[]; showUnmonitored: boolean } @@ -23,6 +23,4 @@ export const calendarMonthRequestHandler = createCachedIntegrationRequestHandler input.showUnmonitored, ); }, - cacheDuration: dayjs.duration(1, "minute"), - queryKey: "calendarMonth", }); diff --git a/packages/request-handler/src/coolify.ts b/packages/request-handler/src/coolify.ts index d307a780f..25ddfe962 100644 --- a/packages/request-handler/src/coolify.ts +++ b/packages/request-handler/src/coolify.ts @@ -1,11 +1,9 @@ -import dayjs from "dayjs"; - import { createIntegrationAsync } from "@homarr/integrations"; import type { CoolifyInstanceInfo } from "@homarr/integrations/types"; -import { createCachedIntegrationRequestHandler } from "./lib/cached-integration-request-handler"; +import { createIntegrationRequestHandler } from "./lib/integration-request-handler"; -export const coolifyRequestHandler = createCachedIntegrationRequestHandler< +export const coolifyRequestHandler = createIntegrationRequestHandler< CoolifyInstanceInfo, "coolify", Record @@ -14,6 +12,4 @@ export const coolifyRequestHandler = createCachedIntegrationRequestHandler< const integrationInstance = await createIntegrationAsync(integration); return await integrationInstance.getInstanceInfoAsync(); }, - cacheDuration: dayjs.duration(30, "seconds"), - queryKey: "coolifyInfo", }); diff --git a/packages/request-handler/src/dns-hole.ts b/packages/request-handler/src/dns-hole.ts index a8c920801..85188c740 100644 --- a/packages/request-handler/src/dns-hole.ts +++ b/packages/request-handler/src/dns-hole.ts @@ -1,12 +1,10 @@ -import dayjs from "dayjs"; - import type { IntegrationKindByCategory } from "@homarr/definitions"; import { createIntegrationAsync } from "@homarr/integrations"; import type { DnsHoleSummary } from "@homarr/integrations/types"; -import { createCachedIntegrationRequestHandler } from "./lib/cached-integration-request-handler"; +import { createIntegrationRequestHandler } from "./lib/integration-request-handler"; -export const dnsHoleRequestHandler = createCachedIntegrationRequestHandler< +export const dnsHoleRequestHandler = createIntegrationRequestHandler< DnsHoleSummary, IntegrationKindByCategory<"dnsHole">, Record @@ -15,6 +13,4 @@ export const dnsHoleRequestHandler = createCachedIntegrationRequestHandler< const integrationInstance = await createIntegrationAsync(integration); return await integrationInstance.getSummaryAsync(); }, - cacheDuration: dayjs.duration(5, "seconds"), - queryKey: "dnsHoleSummary", }); diff --git a/packages/request-handler/src/docker.ts b/packages/request-handler/src/docker.ts index 2d228a598..12a78c05a 100644 --- a/packages/request-handler/src/docker.ts +++ b/packages/request-handler/src/docker.ts @@ -1,5 +1,4 @@ import type { Readable } from "node:stream"; -import dayjs from "dayjs"; import type { Container, ContainerInfo, ContainerStats } from "dockerode"; import type Dockerode from "dockerode"; @@ -13,7 +12,7 @@ import type { ContainerState, Port } from "@homarr/docker"; import { dockerLabels, DockerSingleton } from "@homarr/docker"; import { createDockerLogStreamProcessor, decodeDockerLogs } from "./docker-log-decode"; -import { createCachedWidgetRequestHandler } from "./lib/cached-widget-request-handler"; +import { createWidgetRequestHandler } from "./lib/widget-request-handler"; const logger = createLogger({ module: "dockerRequestHandler" }); @@ -171,16 +170,13 @@ const mockContainers: { }, ]; -export const dockerContainersRequestHandler = createCachedWidgetRequestHandler({ - queryKey: "dockerContainersResult", - widgetKind: "dockerContainers", +export const dockerContainersRequestHandler = createWidgetRequestHandler({ async requestAsync() { if (isDemoMode) { return mockContainers; } return await getContainersWithStatsAsync(); }, - cacheDuration: dayjs.duration(20, "seconds"), }); const extractImage = (container: ContainerInfo) => extractContainerImageName(container.Image); diff --git a/packages/request-handler/src/downloads.ts b/packages/request-handler/src/downloads.ts index 06d4e9c64..12106b07a 100644 --- a/packages/request-handler/src/downloads.ts +++ b/packages/request-handler/src/downloads.ts @@ -1,12 +1,10 @@ -import dayjs from "dayjs"; - import type { IntegrationKindByCategory } from "@homarr/definitions"; import type { DownloadClientJobsAndStatus } from "@homarr/integrations"; import { createIntegrationAsync } from "@homarr/integrations"; -import { createCachedIntegrationRequestHandler } from "./lib/cached-integration-request-handler"; +import { createIntegrationRequestHandler } from "./lib/integration-request-handler"; -export const downloadClientRequestHandler = createCachedIntegrationRequestHandler< +export const downloadClientRequestHandler = createIntegrationRequestHandler< DownloadClientJobsAndStatus, IntegrationKindByCategory<"downloadClient">, { limit: number } @@ -15,6 +13,4 @@ export const downloadClientRequestHandler = createCachedIntegrationRequestHandle const integrationInstance = await createIntegrationAsync(integration); return await integrationInstance.getClientJobsAndStatusAsync(input); }, - cacheDuration: dayjs.duration(5, "seconds"), - queryKey: "downloadClientJobStatus", }); diff --git a/packages/request-handler/src/duckduckgo-bangs.ts b/packages/request-handler/src/duckduckgo-bangs.ts index a51bf5327..628e125a1 100644 --- a/packages/request-handler/src/duckduckgo-bangs.ts +++ b/packages/request-handler/src/duckduckgo-bangs.ts @@ -1,12 +1,10 @@ -import dayjs from "dayjs"; import { z } from "zod"; import { env } from "@homarr/common/env"; import { ResponseError } from "@homarr/common/server"; import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http"; import { withTimeoutAsync } from "@homarr/core/infrastructure/http/timeout"; -import { createChannelWithLatestAndEvents } from "@homarr/redis"; -import { createCachedRequestHandler } from "@homarr/request-handler/lib/cached-request-handler"; +import { createRequestHandler } from "@homarr/request-handler/lib/request-handler"; export type DuckDuckGoBang = z.infer; @@ -38,9 +36,7 @@ const normalizeBangToken = (token: string) => token.toLowerCase().trim(); const compareBangTokenAsc = (left: DuckDuckGoBang, right: DuckDuckGoBang) => left.t.localeCompare(right.t); -export const duckDuckGoBangsRequestHandler = createCachedRequestHandler({ - queryKey: "duckduckgo-bangs", - cacheDuration: dayjs.duration(1, "day"), +export const duckDuckGoBangsRequestHandler = createRequestHandler({ async requestAsync(_) { if (env.NO_EXTERNAL_CONNECTION) { return []; @@ -73,7 +69,4 @@ export const duckDuckGoBangsRequestHandler = createCachedRequestHandler({ return normalized; }, - createRedisChannel() { - return createChannelWithLatestAndEvents("homarr:duckduckgo-bangs"); - }, }); diff --git a/packages/request-handler/src/firewall.ts b/packages/request-handler/src/firewall.ts index 3754f1099..ad4f25b70 100644 --- a/packages/request-handler/src/firewall.ts +++ b/packages/request-handler/src/firewall.ts @@ -1,5 +1,3 @@ -import dayjs from "dayjs"; - import type { IntegrationKindByCategory } from "@homarr/definitions"; import { createIntegrationAsync } from "@homarr/integrations"; import type { @@ -9,9 +7,9 @@ import type { FirewallVersionSummary, } from "@homarr/integrations/types"; -import { createCachedIntegrationRequestHandler } from "./lib/cached-integration-request-handler"; +import { createIntegrationRequestHandler } from "./lib/integration-request-handler"; -export const firewallCpuRequestHandler = createCachedIntegrationRequestHandler< +export const firewallCpuRequestHandler = createIntegrationRequestHandler< FirewallCpuSummary, IntegrationKindByCategory<"firewall">, Record @@ -20,11 +18,9 @@ export const firewallCpuRequestHandler = createCachedIntegrationRequestHandler< const integrationInstance = await createIntegrationAsync(integration); return integrationInstance.getFirewallCpuAsync(); }, - cacheDuration: dayjs.duration(5, "seconds"), - queryKey: "firewallCpuSummary", }); -export const firewallMemoryRequestHandler = createCachedIntegrationRequestHandler< +export const firewallMemoryRequestHandler = createIntegrationRequestHandler< FirewallMemorySummary, IntegrationKindByCategory<"firewall">, Record @@ -33,11 +29,9 @@ export const firewallMemoryRequestHandler = createCachedIntegrationRequestHandle const integrationInstance = await createIntegrationAsync(integration); return await integrationInstance.getFirewallMemoryAsync(); }, - cacheDuration: dayjs.duration(15, "seconds"), - queryKey: "firewallMemorySummary", }); -export const firewallInterfacesRequestHandler = createCachedIntegrationRequestHandler< +export const firewallInterfacesRequestHandler = createIntegrationRequestHandler< FirewallInterfacesSummary[], IntegrationKindByCategory<"firewall">, Record @@ -46,11 +40,9 @@ export const firewallInterfacesRequestHandler = createCachedIntegrationRequestHa const integrationInstance = await createIntegrationAsync(integration); return await integrationInstance.getFirewallInterfacesAsync(); }, - cacheDuration: dayjs.duration(30, "seconds"), - queryKey: "firewallInterfacesSummary", }); -export const firewallVersionRequestHandler = createCachedIntegrationRequestHandler< +export const firewallVersionRequestHandler = createIntegrationRequestHandler< FirewallVersionSummary, IntegrationKindByCategory<"firewall">, Record @@ -59,6 +51,4 @@ export const firewallVersionRequestHandler = createCachedIntegrationRequestHandl const integrationInstance = await createIntegrationAsync(integration); return await integrationInstance.getFirewallVersionAsync(); }, - cacheDuration: dayjs.duration(1, "hour"), - queryKey: "firewallVersionSummary", }); diff --git a/packages/request-handler/src/health-monitoring.ts b/packages/request-handler/src/health-monitoring.ts index 640eb6b97..604fff9da 100644 --- a/packages/request-handler/src/health-monitoring.ts +++ b/packages/request-handler/src/health-monitoring.ts @@ -1,12 +1,10 @@ -import dayjs from "dayjs"; - import type { IntegrationKindByCategory } from "@homarr/definitions"; import { createIntegrationAsync } from "@homarr/integrations"; import type { ProxmoxClusterInfo, SystemHealthMonitoring } from "@homarr/integrations/types"; -import { createCachedIntegrationRequestHandler } from "./lib/cached-integration-request-handler"; +import { createIntegrationRequestHandler } from "./lib/integration-request-handler"; -export const systemInfoRequestHandler = createCachedIntegrationRequestHandler< +export const systemInfoRequestHandler = createIntegrationRequestHandler< SystemHealthMonitoring, Exclude, "proxmox" | "coolify" | "beszel">, Record @@ -15,11 +13,9 @@ export const systemInfoRequestHandler = createCachedIntegrationRequestHandler< const integrationInstance = await createIntegrationAsync(integration); return await integrationInstance.getSystemInfoAsync(); }, - cacheDuration: dayjs.duration(5, "seconds"), - queryKey: "systemInfo", }); -export const clusterInfoRequestHandler = createCachedIntegrationRequestHandler< +export const clusterInfoRequestHandler = createIntegrationRequestHandler< ProxmoxClusterInfo, "proxmox" | "mock", Record @@ -28,6 +24,4 @@ export const clusterInfoRequestHandler = createCachedIntegrationRequestHandler< const integrationInstance = await createIntegrationAsync(integration); return await integrationInstance.getClusterInfoAsync(); }, - cacheDuration: dayjs.duration(5, "seconds"), - queryKey: "clusterInfo", }); diff --git a/packages/request-handler/src/immich.ts b/packages/request-handler/src/immich.ts index e95cb32b8..56fc89bd0 100644 --- a/packages/request-handler/src/immich.ts +++ b/packages/request-handler/src/immich.ts @@ -1,12 +1,10 @@ -import dayjs from "dayjs"; - import type { IntegrationKindByCategory } from "@homarr/definitions"; import { createIntegrationAsync } from "@homarr/integrations"; import type { ImmichAlbum, ImmichServerStats } from "@homarr/integrations"; -import { createCachedIntegrationRequestHandler } from "./lib/cached-integration-request-handler"; +import { createIntegrationRequestHandler } from "./lib/integration-request-handler"; -export const immichStatsRequestHandler = createCachedIntegrationRequestHandler< +export const immichStatsRequestHandler = createIntegrationRequestHandler< ImmichServerStats, IntegrationKindByCategory<"photoService">, Record @@ -15,11 +13,9 @@ export const immichStatsRequestHandler = createCachedIntegrationRequestHandler< const integrationInstance = await createIntegrationAsync(integration); return await integrationInstance.getServerStatsAsync(); }, - cacheDuration: dayjs.duration(15, "minute"), - queryKey: "immich-server-stats", }); -export const immichAlbumsRequestHandler = createCachedIntegrationRequestHandler< +export const immichAlbumsRequestHandler = createIntegrationRequestHandler< { id: string; albumName: string; @@ -32,11 +28,9 @@ export const immichAlbumsRequestHandler = createCachedIntegrationRequestHandler< const integrationInstance = await createIntegrationAsync(integration); return await integrationInstance.getAlbumsAsync(); }, - cacheDuration: dayjs.duration(15, "minute"), - queryKey: "immich-albums", }); -export const immichAlbumRequestHandler = createCachedIntegrationRequestHandler< +export const immichAlbumRequestHandler = createIntegrationRequestHandler< ImmichAlbum, IntegrationKindByCategory<"photoService">, { albumId: string } @@ -45,6 +39,4 @@ export const immichAlbumRequestHandler = createCachedIntegrationRequestHandler< const integrationInstance = await createIntegrationAsync(integration); return await integrationInstance.getAlbumAsync(input.albumId); }, - cacheDuration: dayjs.duration(15, "minute"), - queryKey: "immich-album", }); diff --git a/packages/request-handler/src/indexer-manager.ts b/packages/request-handler/src/indexer-manager.ts index a957b32ad..f21a61f8f 100644 --- a/packages/request-handler/src/indexer-manager.ts +++ b/packages/request-handler/src/indexer-manager.ts @@ -1,12 +1,10 @@ -import dayjs from "dayjs"; - import type { IntegrationKindByCategory } from "@homarr/definitions"; import { createIntegrationAsync } from "@homarr/integrations"; import type { Indexer } from "@homarr/integrations/types"; -import { createCachedIntegrationRequestHandler } from "./lib/cached-integration-request-handler"; +import { createIntegrationRequestHandler } from "./lib/integration-request-handler"; -export const indexerManagerRequestHandler = createCachedIntegrationRequestHandler< +export const indexerManagerRequestHandler = createIntegrationRequestHandler< Indexer[], IntegrationKindByCategory<"indexerManager">, Record @@ -15,6 +13,4 @@ export const indexerManagerRequestHandler = createCachedIntegrationRequestHandle const integrationInstance = await createIntegrationAsync(integration); return await integrationInstance.getIndexersAsync(); }, - cacheDuration: dayjs.duration(5, "minutes"), - queryKey: "indexerManager", }); diff --git a/packages/request-handler/src/lib/cached-integration-request-handler.ts b/packages/request-handler/src/lib/cached-integration-request-handler.ts deleted file mode 100644 index aee69e39c..000000000 --- a/packages/request-handler/src/lib/cached-integration-request-handler.ts +++ /dev/null @@ -1,49 +0,0 @@ -import type { Duration } from "dayjs/plugin/duration"; - -import type { Modify } from "@homarr/common/types"; -import type { Integration, IntegrationSecret } from "@homarr/db/schema"; -import type { IntegrationKind } from "@homarr/definitions"; -import { createIntegrationOptionsChannel } from "@homarr/redis"; - -import { createCachedRequestHandler } from "./cached-request-handler"; - -type IntegrationOfKind = Omit & { - kind: TKind; - decryptedSecrets: Modify, { value: string }>[]; - externalUrl: string | null; -}; - -interface Options> { - queryKey: string; - requestAsync: (integration: IntegrationOfKind, input: TInput) => Promise; - cacheDuration: Duration; - cacheDurationForInput?: (input: TInput) => Duration | undefined; - fallbackToStaleOnError?: boolean; - retry?: { attempts?: number; delayMs?: number }; - isValid?: (data: TData) => boolean; -} - -export const createCachedIntegrationRequestHandler = < - TData, - TKind extends IntegrationKind, - TInput extends Record, ->( - options: Options, -) => { - return { - handler: (integration: IntegrationOfKind, itemOptions: TInput) => - createCachedRequestHandler({ - queryKey: options.queryKey, - requestAsync: async (input: { options: TInput; integration: IntegrationOfKind }) => { - return await options.requestAsync(input.integration, input.options); - }, - cacheDuration: options.cacheDurationForInput?.(itemOptions) ?? options.cacheDuration, - fallbackToStaleOnError: options.fallbackToStaleOnError, - retry: options.retry, - isValid: options.isValid, - createRedisChannel(input, handlerOptions) { - return createIntegrationOptionsChannel(input.integration.id, handlerOptions.queryKey, input.options); - }, - }).handler({ options: itemOptions, integration }), - }; -}; diff --git a/packages/request-handler/src/lib/cached-request-handler.ts b/packages/request-handler/src/lib/cached-request-handler.ts deleted file mode 100644 index c2342eed3..000000000 --- a/packages/request-handler/src/lib/cached-request-handler.ts +++ /dev/null @@ -1,160 +0,0 @@ -import dayjs from "dayjs"; -import type { Duration } from "dayjs/plugin/duration"; - -import { ErrorWithMetadata } from "@homarr/core/infrastructure/logs/error"; -import { createLogger } from "@homarr/core/infrastructure/logs"; -import type { createChannelWithLatestAndEvents } from "@homarr/redis"; - -const logger = createLogger({ module: "cachedRequestHandler" }); - -type FetchResult = { data: TData; timestamp: Date }; -type InFlightEntry = Promise>; - -const inFlightFetches = new Map(); - -const isAbortedError = (error: unknown): boolean => { - if (!(error instanceof Error)) return false; - return error.name === "AbortError" || error.name === "TimeoutError"; -}; - -interface Options> { - // Unique key for this request handler - queryKey: string; - requestAsync: (input: TInput) => Promise; - createRedisChannel: ( - input: TInput, - options: Options, - ) => ReturnType>; - cacheDuration: Duration; - fallbackToStaleOnError?: boolean; - // Retry the upstream call on failure. attempts=1 means no retry (default). - // AbortError / TimeoutError are never retried. - retry?: { attempts?: number; delayMs?: number }; - // Predicate to decide whether a fetched response is worth caching. - // Return false to skip the cache write (next call will refetch). - isValid?: (data: TData) => boolean; -} - -const defaultRetry = { attempts: 1, delayMs: 0 }; -const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); - -export const createCachedRequestHandler = >( - options: Options, -) => { - return { - handler: (input: TInput) => { - const channel = options.createRedisChannel(input, options); - const retryConfig = { ...defaultRetry, ...options.retry }; - - return { - async getCachedOrUpdatedDataAsync({ forceUpdate = false }) { - const channelData = await channel.getAsync(); - - const requestNewDataAsync = (): Promise> => { - const inflight = inFlightFetches.get(channel.name); - if (inflight) { - logger.debug("Cached request handler joining in-flight fetch", { - channel: channel.name, - queryKey: options.queryKey, - }); - return inflight as Promise>; - } - - const promise = (async (): Promise> => { - let lastError: unknown; - for (let attempt = 1; attempt <= retryConfig.attempts; attempt++) { - try { - const data = await options.requestAsync(input); - if (options.isValid && !options.isValid(data)) { - logger.warn("Cached request handler received invalid data, not caching", { - channel: channel.name, - queryKey: options.queryKey, - attempt, - }); - return { data, timestamp: new Date() }; - } - await channel.publishAndUpdateLastStateAsync(data); - return { data, timestamp: new Date() }; - } catch (error) { - lastError = error; - if (isAbortedError(error) || attempt === retryConfig.attempts) { - break; - } - logger.warn("Cached request handler fetch failed, retrying", { - channel: channel.name, - queryKey: options.queryKey, - attempt, - nextDelayMs: retryConfig.delayMs, - error: error instanceof Error ? `${error.name}: ${error.message}` : String(error), - }); - await sleep(retryConfig.delayMs); - } - } - - if (options.fallbackToStaleOnError && channelData) { - logger.warn( - new ErrorWithMetadata( - "Cached request handler using stale cache after fetch failure", - { - channel: channel.name, - queryKey: options.queryKey, - attempts: retryConfig.attempts, - }, - { cause: lastError }, - ), - ); - return channelData; - } - - throw lastError; - })().finally(() => { - inFlightFetches.delete(channel.name); - }); - - inFlightFetches.set(channel.name, promise as InFlightEntry); - return promise; - }; - - if (forceUpdate) { - logger.debug("Cached request handler forced update", { - channel: channel.name, - queryKey: options.queryKey, - }); - return await requestNewDataAsync(); - } - - const shouldRequestNewData = - !channelData || - dayjs().diff(channelData.timestamp, "milliseconds") > options.cacheDuration.asMilliseconds(); - - if (shouldRequestNewData) { - logger.debug("Cached request handler cache miss", { - channel: channel.name, - queryKey: options.queryKey, - reason: !channelData ? "no data" : "cache expired", - }); - return await requestNewDataAsync(); - } - - logger.debug("Cached request handler cache hit", { - channel: channel.name, - queryKey: options.queryKey, - expiresAt: dayjs(channelData.timestamp).add(options.cacheDuration).toISOString(), - }); - - return channelData; - }, - async invalidateAsync() { - logger.debug("Cached request handler invalidating cache", { - channel: channel.name, - queryKey: options.queryKey, - }); - await this.getCachedOrUpdatedDataAsync({ forceUpdate: true }); - }, - subscribe(callback: (data: TData) => void) { - return channel.subscribe(callback); - }, - }; - }, - }; -}; diff --git a/packages/request-handler/src/lib/cached-request-integration-job-handler.ts b/packages/request-handler/src/lib/cached-request-integration-job-handler.ts deleted file mode 100644 index 63e7682c2..000000000 --- a/packages/request-handler/src/lib/cached-request-integration-job-handler.ts +++ /dev/null @@ -1,115 +0,0 @@ -import SuperJSON from "superjson"; - -import { hashObjectBase64, Stopwatch } from "@homarr/common"; -import { decryptSecret } from "@homarr/common/server"; -import type { MaybeArray } from "@homarr/common/types"; -import { createLogger } from "@homarr/core/infrastructure/logs"; -import { ErrorWithMetadata } from "@homarr/core/infrastructure/logs/error"; -import { db } from "@homarr/db"; -import { getItemsWithIntegrationsAsync, getServerSettingsAsync } from "@homarr/db/queries"; -import type { WidgetKind } from "@homarr/definitions"; - -// This imports are done that way to avoid circular dependencies. -import type { inferSupportedIntegrationsStrict } from "../../../widgets/src"; -import { reduceWidgetOptionsWithDefaultValues } from "../../../widgets/src"; -import type { WidgetComponentProps } from "../../../widgets/src/definition"; -import type { createCachedIntegrationRequestHandler } from "./cached-integration-request-handler"; - -const logger = createLogger({ module: "cachedRequestIntegrationJobHandler" }); - -export const createRequestIntegrationJobHandler = < - TWidgetKind extends WidgetKind, - TIntegrationKind extends inferSupportedIntegrationsStrict, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - THandler extends ReturnType>["handler"], ->( - handler: THandler, - { - widgetKinds, - getInput, - }: { - widgetKinds: TWidgetKind[]; - getInput: { - [key in TWidgetKind]: (options: WidgetComponentProps["options"]) => MaybeArray[1]>; - }; - }, -) => { - return async () => { - const serverSettings = await getServerSettingsAsync(db); - const itemsForIntegration = await getItemsWithIntegrationsAsync(db, { - kinds: widgetKinds, - }); - - logger.debug("Found items for integration", { - widgetKinds: widgetKinds.join(","), - count: itemsForIntegration.length, - }); - - const distinctIntegrations: { - integrationId: string; - inputHash: string; - integration: (typeof itemsForIntegration)[number]["integrations"][number]["integration"]; - input: Parameters[1]; - }[] = []; - - for (const itemForIntegration of itemsForIntegration) { - const oneOrMultipleInputs = getInput[itemForIntegration.kind]( - reduceWidgetOptionsWithDefaultValues( - itemForIntegration.kind, - { - enableStatusByDefault: serverSettings.board.enableStatusByDefault, - forceDisableStatus: serverSettings.board.forceDisableStatus, - }, - SuperJSON.parse>(itemForIntegration.options), - ) as never, - ); - for (const { integration } of itemForIntegration.integrations) { - const inputArray = Array.isArray(oneOrMultipleInputs) ? oneOrMultipleInputs : [oneOrMultipleInputs]; - - for (const input of inputArray) { - const inputHash = hashObjectBase64(input); - if ( - distinctIntegrations.some( - (distinctIntegration) => - distinctIntegration.integrationId === integration.id && distinctIntegration.inputHash === inputHash, - ) - ) { - continue; - } - - distinctIntegrations.push({ integrationId: integration.id, inputHash, integration, input }); - } - } - } - - for (const { integrationId, integration, input, inputHash } of distinctIntegrations) { - try { - const decryptedSecrets = integration.secrets.map((secret) => ({ - ...secret, - value: decryptSecret(secret.value), - })); - - const innerHandler = handler( - { - ...integration, - kind: integration.kind as TIntegrationKind, - decryptedSecrets, - externalUrl: integration.app?.href ?? null, - }, - input, - ); - const stopWatch = new Stopwatch(); - await innerHandler.getCachedOrUpdatedDataAsync({ forceUpdate: true }); - logger.debug("Ran integration job", { - integration: integrationId, - inputHash, - elapsed: stopWatch.getElapsedInHumanWords(), - }); - } catch (error) { - logger.error( - new ErrorWithMetadata("Failed to run integration job", { integrationId, inputHash }, { cause: error }), - ); - } - } - }; -}; diff --git a/packages/request-handler/src/lib/cached-widget-request-handler.ts b/packages/request-handler/src/lib/cached-widget-request-handler.ts deleted file mode 100644 index 4016bc5b4..000000000 --- a/packages/request-handler/src/lib/cached-widget-request-handler.ts +++ /dev/null @@ -1,36 +0,0 @@ -import type { Duration } from "dayjs/plugin/duration"; - -import type { WidgetKind } from "@homarr/definitions"; -import { createWidgetOptionsChannel } from "@homarr/redis"; - -import { createCachedRequestHandler } from "./cached-request-handler"; - -interface Options> { - // Unique key for this request handler - queryKey: string; - requestAsync: (input: TInput) => Promise; - cacheDuration: Duration; - widgetKind: TKind; -} - -export const createCachedWidgetRequestHandler = < - TData, - TKind extends WidgetKind, - TInput extends Record, ->( - requestHandlerOptions: Options, -) => { - return { - handler: (widgetOptions: TInput) => - createCachedRequestHandler({ - queryKey: requestHandlerOptions.queryKey, - requestAsync: async (input: TInput) => { - return await requestHandlerOptions.requestAsync(input); - }, - cacheDuration: requestHandlerOptions.cacheDuration, - createRedisChannel(input, options) { - return createWidgetOptionsChannel(requestHandlerOptions.widgetKind, options.queryKey, input); - }, - }).handler(widgetOptions), - }; -}; diff --git a/packages/request-handler/src/lib/integration-request-handler.ts b/packages/request-handler/src/lib/integration-request-handler.ts new file mode 100644 index 000000000..d2fa0f1a4 --- /dev/null +++ b/packages/request-handler/src/lib/integration-request-handler.ts @@ -0,0 +1,30 @@ +import type { Modify } from "@homarr/common/types"; +import type { Integration, IntegrationSecret } from "@homarr/db/schema"; +import type { IntegrationKind } from "@homarr/definitions"; + +import { createRequestHandler } from "./request-handler"; + +type IntegrationOfKind = Omit & { + kind: TKind; + decryptedSecrets: Modify, { value: string }>[]; + externalUrl: string | null; +}; + +interface Options> { + requestAsync: (integration: IntegrationOfKind, input: TInput) => Promise; +} + +export const createIntegrationRequestHandler = < + TData, + TKind extends IntegrationKind, + TInput extends Record, +>( + options: Options, +) => ({ + handler: (integration: IntegrationOfKind, itemOptions: TInput) => { + const inner = createRequestHandler({ + requestAsync: async (input) => options.requestAsync(integration, input.options), + }); + return inner.handler({ integrationId: integration.id, options: itemOptions }); + }, +}); diff --git a/packages/request-handler/src/lib/request-handler.ts b/packages/request-handler/src/lib/request-handler.ts new file mode 100644 index 000000000..7018dd4ee --- /dev/null +++ b/packages/request-handler/src/lib/request-handler.ts @@ -0,0 +1,60 @@ +interface Options> { + requestAsync: (input: TInput) => Promise; + cacheTtlMs?: number; +} + +type CacheEntry = { data: TData; timestamp: Date; expiresAt: number }; + +const MAX_CACHE_SIZE = 1000; +const cache = new Map>(); +const inflight = new Map>>(); + +const DEFAULT_TTL_MS = 10_000; + +const evictExpired = () => { + const now = Date.now(); + for (const [key, entry] of cache) { + if (now >= entry.expiresAt) cache.delete(key); + } +}; + +export const createRequestHandler = >( + options: Options, +) => ({ + handler: (input: TInput) => ({ + async getDataAsync(): Promise<{ data: TData; timestamp: Date }> { + const ttl = options.cacheTtlMs ?? DEFAULT_TTL_MS; + const key = JSON.stringify(input); + + const cached = cache.get(key) as CacheEntry | undefined; + if (cached && Date.now() < cached.expiresAt) { + return { data: cached.data, timestamp: cached.timestamp }; + } + if (cached) cache.delete(key); + + const existing = inflight.get(key); + if (existing) return existing as Promise>; + + const promise = options + .requestAsync(input) + .then((data) => { + if (cache.size >= MAX_CACHE_SIZE) evictExpired(); + if (cache.size >= MAX_CACHE_SIZE) { + const oldest = cache.keys().next().value; + if (oldest) cache.delete(oldest); + } + const entry: CacheEntry = { data, timestamp: new Date(), expiresAt: Date.now() + ttl }; + cache.set(key, entry as CacheEntry); + inflight.delete(key); + return entry; + }) + .catch((err) => { + inflight.delete(key); + throw err; + }); + + inflight.set(key, promise as Promise>); + return promise; + }, + }), +}); diff --git a/packages/request-handler/src/lib/widget-request-handler.ts b/packages/request-handler/src/lib/widget-request-handler.ts new file mode 100644 index 000000000..8649791af --- /dev/null +++ b/packages/request-handler/src/lib/widget-request-handler.ts @@ -0,0 +1,14 @@ +import { createRequestHandler } from "./request-handler"; + +interface Options> { + requestAsync: (input: TInput) => Promise; +} + +export const createWidgetRequestHandler = >( + options: Options, +) => ({ + handler: (widgetOptions: TInput) => + createRequestHandler({ + requestAsync: options.requestAsync, + }).handler(widgetOptions), +}); diff --git a/packages/request-handler/src/media-release.ts b/packages/request-handler/src/media-release.ts index c0ffe48f3..02209f68f 100644 --- a/packages/request-handler/src/media-release.ts +++ b/packages/request-handler/src/media-release.ts @@ -1,12 +1,10 @@ -import dayjs from "dayjs"; - import type { IntegrationKindByCategory } from "@homarr/definitions"; import { createIntegrationAsync } from "@homarr/integrations"; import type { MediaRelease } from "@homarr/integrations/types"; -import { createCachedIntegrationRequestHandler } from "./lib/cached-integration-request-handler"; +import { createIntegrationRequestHandler } from "./lib/integration-request-handler"; -export const mediaReleaseRequestHandler = createCachedIntegrationRequestHandler< +export const mediaReleaseRequestHandler = createIntegrationRequestHandler< MediaRelease[], IntegrationKindByCategory<"mediaRelease">, Record @@ -15,6 +13,4 @@ export const mediaReleaseRequestHandler = createCachedIntegrationRequestHandler< const integrationInstance = await createIntegrationAsync(integration); return await integrationInstance.getMediaReleasesAsync(); }, - cacheDuration: dayjs.duration(5, "minutes"), - queryKey: "mediaReleases", }); diff --git a/packages/request-handler/src/media-request-list.ts b/packages/request-handler/src/media-request-list.ts index fd4e02234..62cf29a18 100644 --- a/packages/request-handler/src/media-request-list.ts +++ b/packages/request-handler/src/media-request-list.ts @@ -1,12 +1,10 @@ -import dayjs from "dayjs"; - import type { IntegrationKindByCategory } from "@homarr/definitions"; import { createIntegrationAsync } from "@homarr/integrations"; import type { MediaRequest } from "@homarr/integrations/types"; -import { createCachedIntegrationRequestHandler } from "./lib/cached-integration-request-handler"; +import { createIntegrationRequestHandler } from "./lib/integration-request-handler"; -export const mediaRequestListRequestHandler = createCachedIntegrationRequestHandler< +export const mediaRequestListRequestHandler = createIntegrationRequestHandler< MediaRequest[], IntegrationKindByCategory<"mediaRequest">, Record @@ -15,6 +13,4 @@ export const mediaRequestListRequestHandler = createCachedIntegrationRequestHand const integrationInstance = await createIntegrationAsync(integration); return await integrationInstance.getRequestsAsync(); }, - cacheDuration: dayjs.duration(1, "minute"), - queryKey: "mediaRequestList", }); diff --git a/packages/request-handler/src/media-request-stats.ts b/packages/request-handler/src/media-request-stats.ts index a6568aecd..61ff810b8 100644 --- a/packages/request-handler/src/media-request-stats.ts +++ b/packages/request-handler/src/media-request-stats.ts @@ -1,12 +1,10 @@ -import dayjs from "dayjs"; - import type { IntegrationKindByCategory } from "@homarr/definitions"; import { createIntegrationAsync } from "@homarr/integrations"; import type { MediaRequestStats } from "@homarr/integrations/types"; -import { createCachedIntegrationRequestHandler } from "./lib/cached-integration-request-handler"; +import { createIntegrationRequestHandler } from "./lib/integration-request-handler"; -export const mediaRequestStatsRequestHandler = createCachedIntegrationRequestHandler< +export const mediaRequestStatsRequestHandler = createIntegrationRequestHandler< MediaRequestStats, IntegrationKindByCategory<"mediaRequest">, Record @@ -18,6 +16,4 @@ export const mediaRequestStatsRequestHandler = createCachedIntegrationRequestHan users: await integrationInstance.getUsersAsync(), }; }, - cacheDuration: dayjs.duration(1, "minute"), - queryKey: "mediaRequestStats", }); diff --git a/packages/request-handler/src/media-server.ts b/packages/request-handler/src/media-server.ts index b13b90eb0..55a7850e0 100644 --- a/packages/request-handler/src/media-server.ts +++ b/packages/request-handler/src/media-server.ts @@ -1,12 +1,10 @@ -import dayjs from "dayjs"; - import type { IntegrationKindByCategory } from "@homarr/definitions"; import type { StreamSession } from "@homarr/integrations"; import { createIntegrationAsync } from "@homarr/integrations"; -import { createCachedIntegrationRequestHandler } from "./lib/cached-integration-request-handler"; +import { createIntegrationRequestHandler } from "./lib/integration-request-handler"; -export const mediaServerRequestHandler = createCachedIntegrationRequestHandler< +export const mediaServerRequestHandler = createIntegrationRequestHandler< StreamSession[], IntegrationKindByCategory<"mediaService">, { @@ -17,6 +15,4 @@ export const mediaServerRequestHandler = createCachedIntegrationRequestHandler< const integrationInstance = await createIntegrationAsync(integration); return await integrationInstance.getCurrentSessionsAsync({ showOnlyPlaying: input.showOnlyPlaying }); }, - cacheDuration: dayjs.duration(5, "seconds"), - queryKey: "mediaServerSessions", }); diff --git a/packages/request-handler/src/media-transcoding.ts b/packages/request-handler/src/media-transcoding.ts index 698d7347b..ba6455ccd 100644 --- a/packages/request-handler/src/media-transcoding.ts +++ b/packages/request-handler/src/media-transcoding.ts @@ -1,18 +1,14 @@ -import dayjs from "dayjs"; - import type { IntegrationKindByCategory } from "@homarr/definitions"; import { createIntegrationAsync } from "@homarr/integrations"; import type { TdarrQueue, TdarrStatistics, TdarrWorker } from "@homarr/integrations"; -import { createCachedIntegrationRequestHandler } from "./lib/cached-integration-request-handler"; +import { createIntegrationRequestHandler } from "./lib/integration-request-handler"; -export const mediaTranscodingRequestHandler = createCachedIntegrationRequestHandler< +export const mediaTranscodingRequestHandler = createIntegrationRequestHandler< MediaTranscoding, IntegrationKindByCategory<"mediaTranscoding">, { pageOffset: number; pageSize: number } >({ - queryKey: "mediaTranscoding", - cacheDuration: dayjs.duration(5, "minutes"), async requestAsync(integration, input) { const integrationInstance = await createIntegrationAsync(integration); return { diff --git a/packages/request-handler/src/minecraft-server-status.ts b/packages/request-handler/src/minecraft-server-status.ts index 98d850171..664a0a7f3 100644 --- a/packages/request-handler/src/minecraft-server-status.ts +++ b/packages/request-handler/src/minecraft-server-status.ts @@ -1,14 +1,11 @@ -import dayjs from "dayjs"; import { z } from "zod/v4"; import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http"; import { withTimeoutAsync } from "@homarr/core/infrastructure/http/timeout"; -import { createCachedWidgetRequestHandler } from "./lib/cached-widget-request-handler"; +import { createWidgetRequestHandler } from "./lib/widget-request-handler"; -export const minecraftServerStatusRequestHandler = createCachedWidgetRequestHandler({ - queryKey: "minecraftServerStatusApiResult", - widgetKind: "minecraftServerStatus", +export const minecraftServerStatusRequestHandler = createWidgetRequestHandler({ async requestAsync(input: { domain: string; isBedrockServer: boolean }) { const path = `${input.isBedrockServer ? "/bedrock" : ""}/3/${input.domain}`; @@ -17,7 +14,6 @@ export const minecraftServerStatusRequestHandler = createCachedWidgetRequestHand ); return responseSchema.parse(await response.json()); }, - cacheDuration: dayjs.duration(5, "minutes"), }); const responseSchema = z diff --git a/packages/request-handler/src/navidrome.ts b/packages/request-handler/src/navidrome.ts index b59f852a8..0ad39ed9a 100644 --- a/packages/request-handler/src/navidrome.ts +++ b/packages/request-handler/src/navidrome.ts @@ -1,11 +1,9 @@ -import dayjs from "dayjs"; - import { createIntegrationAsync } from "@homarr/integrations"; import type { NavidromeDashboardData } from "@homarr/integrations/types"; -import { createCachedIntegrationRequestHandler } from "./lib/cached-integration-request-handler"; +import { createIntegrationRequestHandler } from "./lib/integration-request-handler"; -export const navidromeRequestHandler = createCachedIntegrationRequestHandler< +export const navidromeRequestHandler = createIntegrationRequestHandler< NavidromeDashboardData, "navidrome", Record @@ -14,6 +12,4 @@ export const navidromeRequestHandler = createCachedIntegrationRequestHandler< const integrationInstance = await createIntegrationAsync(integration); return await integrationInstance.getDashboardDataAsync(); }, - cacheDuration: dayjs.duration(10, "minutes"), - queryKey: "navidromeDashboard", }); diff --git a/packages/request-handler/src/network-controller.ts b/packages/request-handler/src/network-controller.ts index f5975acf2..123261025 100644 --- a/packages/request-handler/src/network-controller.ts +++ b/packages/request-handler/src/network-controller.ts @@ -1,12 +1,10 @@ -import dayjs from "dayjs"; - import type { IntegrationKindByCategory } from "@homarr/definitions"; import { createIntegrationAsync } from "@homarr/integrations"; import type { NetworkControllerSummary } from "@homarr/integrations/types"; -import { createCachedIntegrationRequestHandler } from "./lib/cached-integration-request-handler"; +import { createIntegrationRequestHandler } from "./lib/integration-request-handler"; -export const networkControllerRequestHandler = createCachedIntegrationRequestHandler< +export const networkControllerRequestHandler = createIntegrationRequestHandler< NetworkControllerSummary, IntegrationKindByCategory<"networkController">, Record @@ -15,6 +13,4 @@ export const networkControllerRequestHandler = createCachedIntegrationRequestHan const integrationInstance = await createIntegrationAsync(integration); return await integrationInstance.getNetworkSummaryAsync(); }, - cacheDuration: dayjs.duration(5, "minutes"), - queryKey: "networkControllerSummary", }); diff --git a/packages/request-handler/src/notifications.ts b/packages/request-handler/src/notifications.ts index 41098e660..479ff81fa 100644 --- a/packages/request-handler/src/notifications.ts +++ b/packages/request-handler/src/notifications.ts @@ -1,12 +1,10 @@ -import dayjs from "dayjs"; - import type { IntegrationKindByCategory } from "@homarr/definitions"; import type { Notification } from "@homarr/integrations"; import { createIntegrationAsync } from "@homarr/integrations"; -import { createCachedIntegrationRequestHandler } from "./lib/cached-integration-request-handler"; +import { createIntegrationRequestHandler } from "./lib/integration-request-handler"; -export const notificationsRequestHandler = createCachedIntegrationRequestHandler< +export const notificationsRequestHandler = createIntegrationRequestHandler< Notification[], IntegrationKindByCategory<"notifications">, Record @@ -15,6 +13,4 @@ export const notificationsRequestHandler = createCachedIntegrationRequestHandler const integrationInstance = await createIntegrationAsync(integration); return await integrationInstance.getNotificationsAsync(); }, - cacheDuration: dayjs.duration(5, "minutes"), - queryKey: "notificationsJobStatus", }); diff --git a/packages/request-handler/src/paperless-ngx.ts b/packages/request-handler/src/paperless-ngx.ts index a3793658f..e6b088006 100644 --- a/packages/request-handler/src/paperless-ngx.ts +++ b/packages/request-handler/src/paperless-ngx.ts @@ -1,11 +1,9 @@ -import dayjs from "dayjs"; - import { createIntegrationAsync } from "@homarr/integrations"; import type { PaperlessNgxStats } from "@homarr/integrations"; -import { createCachedIntegrationRequestHandler } from "./lib/cached-integration-request-handler"; +import { createIntegrationRequestHandler } from "./lib/integration-request-handler"; -export const paperlessNgxStatsRequestHandler = createCachedIntegrationRequestHandler< +export const paperlessNgxStatsRequestHandler = createIntegrationRequestHandler< PaperlessNgxStats, "paperlessNgx", Record @@ -14,6 +12,4 @@ export const paperlessNgxStatsRequestHandler = createCachedIntegrationRequestHan const integrationInstance = await createIntegrationAsync(integration); return await integrationInstance.getStatsAsync(); }, - cacheDuration: dayjs.duration(15, "minute"), - queryKey: "paperless-ngx-stats", }); diff --git a/packages/request-handler/src/release-providers.ts b/packages/request-handler/src/release-providers.ts new file mode 100644 index 000000000..f4a647668 --- /dev/null +++ b/packages/request-handler/src/release-providers.ts @@ -0,0 +1,829 @@ +import { Octokit, RequestError as OctokitRequestError } from "octokit"; +import { z } from "zod/v4"; + +import type { ReleaseProviderKind } from "@homarr/definitions"; +import { getReleaseProviderDefaultUrl, normalizeReleaseProviderIdentifier } from "@homarr/definitions"; +import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http"; +import { createLogger } from "@homarr/core/infrastructure/logs"; + +const logger = createLogger({ module: "releaseProviders" }); + +const PROVIDER_TIMEOUT_MS = 10_000; +const fetchProvider: typeof fetchWithTrustedCertificatesAsync = (url, options) => + fetchWithTrustedCertificatesAsync(url, { timeout: PROVIDER_TIMEOUT_MS, ...options }); + +export interface ReleasesRepositoryRequest extends Record { + id: string; + provider: ReleaseProviderKind; + identifier: string; + versionRegex?: string; + providerUrl?: string; + token?: string; +} + +export interface DetailsProviderResponse { + projectUrl?: string; + projectDescription?: string; + isFork?: boolean; + isArchived?: boolean; + createdAt?: Date; + starsCount?: number; + openIssues?: number; + forksCount?: number; +} + +export interface ReleaseProviderResponse { + latestRelease: string; + latestReleaseAt: Date; + releaseUrl?: string; + releaseDescription?: string; + isPreRelease?: boolean; +} + +export type ReleaseData = DetailsProviderResponse & ReleaseProviderResponse; +export type ReleaseError = { + code: "invalidIdentifier" | "noMatchingVersion" | "noReleasesFound" | "unexpected"; + message: string; +}; +export type ReleaseResponse = { success: true; data: ReleaseData } | { success: false; error: ReleaseError }; + +const compileVersionRegex = (versionRegex?: string): RegExp | null => { + if (!versionRegex) return null; + try { + return new RegExp(versionRegex); + } catch { + return null; + } +}; + +export const getLatestRelease = ( + releases: ReleaseProviderResponse[], + versionRegex?: string, +): { release: ReleaseProviderResponse } | { noMatch: string } => { + const regex = compileVersionRegex(versionRegex); + if (versionRegex && !regex) { + return { noMatch: `Invalid regex: /${versionRegex}/` }; + } + + const validReleases = releases.filter((result) => { + if (!result.latestRelease) return false; + return regex ? regex.test(result.latestRelease) : true; + }); + + if (validReleases.length > 0) { + return { + release: validReleases.reduce((latest, current) => + current.latestReleaseAt > latest.latestReleaseAt ? current : latest, + ), + }; + } + + const sampleTags = releases + .slice(0, 5) + .map((r) => r.latestRelease) + .join(", "); + const reason = versionRegex + ? `Regex /${versionRegex}/ matched 0/${releases.length} tags. Sample tags: [${sampleTags}]` + : `Found ${releases.length} releases but none had a valid tag`; + return { noMatch: reason }; +}; + +export const getLatestMatchingReleaseAsync = async (input: ReleasesRepositoryRequest): Promise => { + const baseUrl = removeTrailingSlash(input.providerUrl ?? getReleaseProviderDefaultUrl(input.provider)); + const identifier = normalizeReleaseProviderIdentifier(input.provider, input.identifier); + + switch (input.provider) { + case "github": + return await getGithubReleaseAsync(baseUrl, identifier, input.versionRegex, input.token); + case "gitHubContainerRegistry": + return await getGitHubContainerRegistryReleaseAsync(baseUrl, identifier, input.versionRegex, input.token); + case "dockerHub": + return await getDockerHubReleaseAsync(baseUrl, identifier, input.versionRegex, input.token); + case "gitlab": + return await getGitlabReleaseAsync(baseUrl, identifier, input.versionRegex, input.token); + case "npm": + return await getNpmReleaseAsync(baseUrl, identifier, input.versionRegex, input.token); + case "codeberg": + return await getCodebergReleaseAsync(baseUrl, identifier, input.versionRegex, input.token); + case "linuxServerIO": + return await getLinuxServerIOReleaseAsync(baseUrl, identifier, input.versionRegex); + case "quay": + return await getQuayReleaseAsync(baseUrl, identifier, input.versionRegex); + } +}; + +const parseOwnerName = (identifier: string) => { + const [owner, name] = identifier.split("/"); + if (!owner || !name) return null; + return { owner, name }; +}; + +const parseOwnerPackageName = (identifier: string) => { + const [owner, ...nameParts] = identifier.split("/"); + const name = nameParts.join("/"); + if (!owner || !name) return null; + return { owner, name }; +}; + +const removeTrailingSlash = (url: string) => url.replace(/\/$/, ""); + +const buildUrl = (baseUrl: string, path: `/${string}`) => new URL(`${baseUrl}${path}`).toString(); + +const getGithubApi = (baseUrl: string, userAgent: string, token?: string) => + new Octokit({ + baseUrl, + auth: token, + request: { fetch: fetchWithTrustedCertificatesAsync }, + throttle: { enabled: false }, + userAgent, + }); + +const getGithubReleaseAsync = async ( + baseUrl: string, + identifier: string, + versionRegex?: string, + token?: string, +): Promise => { + const parsed = parseOwnerName(identifier); + if (!parsed) + return { + success: false, + error: { code: "invalidIdentifier", message: `Cannot parse "${identifier}" as owner/repo` }, + }; + + const api = getGithubApi(baseUrl, "Homarr-Lab/Homarr:GithubReleaseProvider", token); + try { + const allReleases = await api.paginate(api.rest.repos.listReleases, { + owner: parsed.owner, + repo: parsed.name, + per_page: 100, + }); + if (allReleases.length === 0) { + return { success: false, error: { code: "noReleasesFound", message: `${identifier} has no GitHub releases` } }; + } + + const releases = allReleases.reduce((acc, release) => { + if (!release.published_at) return acc; + acc.push({ + latestRelease: release.tag_name, + latestReleaseAt: new Date(release.published_at), + releaseUrl: release.html_url, + releaseDescription: release.body ?? undefined, + isPreRelease: release.prerelease, + }); + return acc; + }, []); + const result = getLatestRelease(releases, versionRegex); + if ("noMatch" in result) { + return { success: false, error: { code: "noMatchingVersion", message: result.noMatch } }; + } + + const details = await getGithubDetailsAsync(api, parsed.owner, parsed.name); + return { success: true, data: { ...details, ...result.release } }; + } catch (error) { + const message = error instanceof OctokitRequestError ? error.message : String(error); + logger.warn("Failed to get GitHub releases", { identifier, error: message }); + return { success: false, error: { code: "unexpected", message } }; + } +}; + +const getGithubDetailsAsync = async (api: Octokit, owner: string, name: string): Promise => { + try { + const response = await api.rest.repos.get({ owner, repo: name }); + return { + projectUrl: response.data.html_url, + projectDescription: response.data.description ?? undefined, + isFork: response.data.fork, + isArchived: response.data.archived, + createdAt: new Date(response.data.created_at), + starsCount: response.data.stargazers_count, + openIssues: response.data.open_issues_count, + forksCount: response.data.forks_count, + }; + } catch (error) { + logger.warn("Failed to get GitHub details", { owner, name, error: String(error) }); + return {}; + } +}; + +const getGitHubContainerRegistryReleaseAsync = async ( + baseUrl: string, + identifier: string, + versionRegex?: string, + token?: string, +): Promise => { + const parsed = parseOwnerPackageName(identifier); + if (!parsed) + return { + success: false, + error: { code: "invalidIdentifier", message: `Cannot parse "${identifier}" as owner/package` }, + }; + + try { + const repositoryName = `${parsed.owner}/${parsed.name}`; + const regex = compileVersionRegex(versionRegex); + if (versionRegex && !regex) { + return { success: false, error: { code: "noMatchingVersion", message: `Invalid regex: /${versionRegex}/` } }; + } + const registryUrl = new URL(baseUrl); + const registryToken = token ?? (await getGitHubContainerRegistryTokenAsync(registryUrl, repositoryName)); + const tags = await getGitHubContainerRegistryTagsAsync(registryUrl, repositoryName, registryToken); + if (tags.length === 0) { + return { success: false, error: { code: "noReleasesFound", message: `${repositoryName} has no container tags` } }; + } + const matchingTags = regex ? tags.filter((tag) => regex.test(tag)) : tags; + const tagsToCheck = versionRegex + ? matchingTags.slice(-100) + : matchingTags.includes("latest") + ? ["latest"] + : matchingTags.slice(-100); + if (tagsToCheck.length === 0) { + const sampleTags = tags.slice(0, 5).join(", "); + const msg = versionRegex + ? `Regex /${versionRegex}/ matched 0/${tags.length} tags. Sample: [${sampleTags}]` + : `No "latest" tag found among ${tags.length} tags. Sample: [${sampleTags}]`; + return { success: false, error: { code: "noMatchingVersion", message: msg } }; + } + + const releases = await Promise.all( + tagsToCheck.map(async (tag) => ({ + latestRelease: tag, + latestReleaseAt: await getGitHubContainerRegistryTagCreatedAtAsync( + registryUrl, + repositoryName, + tag, + registryToken, + ), + releaseUrl: `https://github.com/${repositoryName}/pkgs/container/${encodeURIComponent(parsed.name)}`, + })), + ); + const result = getLatestRelease(releases, versionRegex); + if ("noMatch" in result) { + return { success: false, error: { code: "noMatchingVersion", message: result.noMatch } }; + } + + return { + success: true, + data: { + projectUrl: `https://github.com/${repositoryName}`, + ...result.release, + }, + }; + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + logger.warn("Failed to get GitHub container registry releases", { identifier, error: message }); + return { success: false, error: { code: "unexpected", message } }; + } +}; + +const gitHubContainerRegistryTokenSchema = z.object({ token: z.string() }); +const gitHubContainerRegistryTagsSchema = z.object({ tags: z.array(z.string()).optional() }); +const gitHubContainerRegistryManifestSchema = z.object({ + config: z.object({ digest: z.string() }).optional(), + manifests: z.array(z.object({ digest: z.string() })).optional(), +}); +const gitHubContainerRegistryConfigSchema = z.object({ + created: z.string().transform((value) => new Date(value)), +}); + +const getGitHubContainerRegistryTokenAsync = async (registryUrl: URL, repositoryName: string) => { + const response = await fetchProvider( + buildUrl( + registryUrl.origin, + `/token?service=${registryUrl.host}&scope=${encodeURIComponent(`repository:${repositoryName}:pull`)}` as `/${string}`, + ), + ); + if (!response.ok) throw new Error(response.statusText); + + const result = gitHubContainerRegistryTokenSchema.safeParse(await response.json()); + if (!result.success) throw new Error(result.error.message); + + return result.data.token; +}; + +const getGitHubContainerRegistryTagsAsync = async (registryUrl: URL, repositoryName: string, token: string) => { + const response = await fetchProvider(buildUrl(registryUrl.origin, `/v2/${repositoryName}/tags/list`), { + headers: { Authorization: `Bearer ${token}` }, + }); + if (!response.ok) throw new Error(response.statusText); + + const result = gitHubContainerRegistryTagsSchema.safeParse(await response.json()); + if (!result.success) throw new Error(result.error.message); + + return result.data.tags ?? []; +}; + +const getGitHubContainerRegistryTagCreatedAtAsync = async ( + registryUrl: URL, + repositoryName: string, + tag: string, + token: string, +) => { + const manifest = await getGitHubContainerRegistryManifestAsync(registryUrl, repositoryName, tag, token); + const imageManifest = manifest.config + ? manifest + : await getGitHubContainerRegistryManifestAsync( + registryUrl, + repositoryName, + manifest.manifests?.[0]?.digest ?? "", + token, + ); + const configDigest = imageManifest.config?.digest; + if (!configDigest) throw new Error("Missing image config digest"); + + const response = await fetchProvider(buildUrl(registryUrl.origin, `/v2/${repositoryName}/blobs/${configDigest}`), { + headers: { Authorization: `Bearer ${token}` }, + }); + if (!response.ok) throw new Error(response.statusText); + + const result = gitHubContainerRegistryConfigSchema.safeParse(await response.json()); + if (!result.success) throw new Error(result.error.message); + + return result.data.created; +}; + +const getGitHubContainerRegistryManifestAsync = async ( + registryUrl: URL, + repositoryName: string, + reference: string, + token: string, +) => { + if (!reference) throw new Error("Missing image manifest reference"); + + const response = await fetchProvider(buildUrl(registryUrl.origin, `/v2/${repositoryName}/manifests/${reference}`), { + headers: { + Authorization: `Bearer ${token}`, + Accept: [ + "application/vnd.oci.image.index.v1+json", + "application/vnd.docker.distribution.manifest.list.v2+json", + "application/vnd.oci.image.manifest.v1+json", + "application/vnd.docker.distribution.manifest.v2+json", + ].join(", "), + }, + }); + if (!response.ok) throw new Error(response.statusText); + + const result = gitHubContainerRegistryManifestSchema.safeParse(await response.json()); + if (!result.success) throw new Error(result.error.message); + + return result.data; +}; + +const dockerHubReleasesSchema = z.object({ + results: z.array( + z.object({ name: z.string(), last_updated: z.string().transform((value) => new Date(value)) }).transform((tag) => ({ + latestRelease: tag.name, + latestReleaseAt: tag.last_updated, + })), + ), +}); + +const dockerHubDetailsSchema = z.object({ + name: z.string(), + namespace: z.string(), + description: z.string(), + star_count: z.number(), + date_registered: z.string().transform((value) => new Date(value)), +}); + +const getDockerHubReleaseAsync = async ( + baseUrl: string, + identifier: string, + versionRegex?: string, + token?: string, +): Promise => { + const parsed = identifier.includes("/") ? parseOwnerName(identifier) : { owner: "", name: identifier }; + if (!parsed) return { success: false, error: { code: "invalidIdentifier", message: `Cannot parse "${identifier}"` } }; + + const relativeUrl = parsed.owner + ? `/v2/namespaces/${encodeURIComponent(parsed.owner)}/repositories/${encodeURIComponent(parsed.name)}` + : `/v2/repositories/library/${encodeURIComponent(parsed.name)}`; + + const headers: Record = {}; + if (token) headers.Authorization = `Bearer ${token}`; + + let lastNoMatch = ""; + for (let page = 0; page <= 5; page++) { + const response = await fetchProvider( + buildUrl(baseUrl, `${relativeUrl}/tags?page_size=100&page=${page}` as `/${string}`), + headers.Authorization ? { headers } : undefined, + ); + if (!response.ok) + return { + success: false, + error: { code: "unexpected", message: `Docker Hub ${response.status}: ${response.statusText}` }, + }; + + const json: unknown = await response.json(); + const result = dockerHubReleasesSchema.safeParse(json); + if (!result.success) return { success: false, error: { code: "unexpected", message: result.error.message } }; + + const matched = getLatestRelease(result.data.results, versionRegex); + if ("noMatch" in matched) { + lastNoMatch = matched.noMatch; + continue; + } + + const details = await getDockerHubDetailsAsync(baseUrl, relativeUrl as `/${string}`, token); + return { success: true, data: { ...details, ...matched.release } }; + } + + return { + success: false, + error: { code: "noMatchingVersion", message: lastNoMatch || `No matching tags found for ${identifier}` }, + }; +}; + +const getDockerHubDetailsAsync = async ( + baseUrl: string, + relativeUrl: `/${string}`, + token?: string, +): Promise => { + const headers: Record = {}; + if (token) headers.Authorization = `Bearer ${token}`; + + const response = await fetchProvider( + buildUrl(baseUrl, `${relativeUrl}/` as `/${string}`), + headers.Authorization ? { headers } : undefined, + ); + if (!response.ok) return {}; + + const result = dockerHubDetailsSchema.safeParse(await response.json()); + if (!result.success) return {}; + + return { + projectUrl: `https://hub.docker.com/r/${result.data.namespace}/${result.data.name}`, + projectDescription: result.data.description, + createdAt: result.data.date_registered, + starsCount: result.data.star_count, + }; +}; + +const gitlabReleasesSchema = z.array( + z.object({ + name: z.string().nullable().optional(), + tag_name: z.string(), + released_at: z.string().nullable().optional(), + description: z.string().nullable().optional(), + _links: z.object({ self: z.string().optional() }).optional(), + }), +); + +const gitlabDetailsSchema = z.object({ + web_url: z.string().optional(), + description: z.string().nullable().optional(), + forked_from_project: z.unknown().nullable().optional(), + archived: z.boolean(), + created_at: z.string(), + star_count: z.number(), + open_issues_count: z.number().optional(), + forks_count: z.number(), +}); + +const getGitlabReleaseAsync = async ( + baseUrl: string, + identifier: string, + versionRegex?: string, + token?: string, +): Promise => { + const encodedIdentifier = encodeURIComponent(identifier); + const headers: Record = {}; + if (token) headers["PRIVATE-TOKEN"] = token; + + const response = await fetchProvider( + buildUrl(baseUrl, `/api/v4/projects/${encodedIdentifier}/releases?per_page=100`), + headers["PRIVATE-TOKEN"] ? { headers } : undefined, + ); + if (!response.ok) + return { + success: false, + error: { code: "unexpected", message: `GitLab ${response.status}: ${response.statusText}` }, + }; + + const result = gitlabReleasesSchema.safeParse(await response.json()); + if (!result.success) return { success: false, error: { code: "unexpected", message: result.error.message } }; + if (result.data.length === 0) + return { success: false, error: { code: "noReleasesFound", message: `${identifier} has no GitLab releases` } }; + + const releases = result.data.reduce((acc, release) => { + if (!release.released_at) return acc; + const releaseDate = new Date(release.released_at); + acc.push({ + latestRelease: release.tag_name, + latestReleaseAt: releaseDate, + releaseUrl: release._links?.self, + releaseDescription: release.description ?? undefined, + isPreRelease: releaseDate > new Date(), + }); + return acc; + }, []); + const matched = getLatestRelease(releases, versionRegex); + if ("noMatch" in matched) return { success: false, error: { code: "noMatchingVersion", message: matched.noMatch } }; + + const details = await getGitlabDetailsAsync(baseUrl, encodedIdentifier, token); + return { success: true, data: { ...details, ...matched.release } }; +}; + +const getGitlabDetailsAsync = async ( + baseUrl: string, + encodedIdentifier: string, + token?: string, +): Promise => { + const headers: Record = {}; + if (token) headers["PRIVATE-TOKEN"] = token; + + const response = await fetchProvider( + buildUrl(baseUrl, `/api/v4/projects/${encodedIdentifier}`), + headers["PRIVATE-TOKEN"] ? { headers } : undefined, + ); + if (!response.ok) return {}; + + const result = gitlabDetailsSchema.safeParse(await response.json()); + if (!result.success || !result.data.web_url) return {}; + + return { + projectUrl: result.data.web_url, + projectDescription: result.data.description ?? undefined, + isFork: result.data.forked_from_project !== null, + isArchived: result.data.archived, + createdAt: new Date(result.data.created_at), + starsCount: result.data.star_count, + openIssues: result.data.open_issues_count, + forksCount: result.data.forks_count, + }; +}; + +const npmTimeMetadataKeys = new Set(["created", "modified"]); + +const npmReleasesSchema = z.object({ + time: z + .record( + z.string(), + z.string().transform((value) => new Date(value)), + ) + .transform((entries) => + Object.entries(entries) + .filter(([key]) => !npmTimeMetadataKeys.has(key)) + .map(([key, value]) => ({ latestRelease: key, latestReleaseAt: value })), + ), + versions: z.record(z.string(), z.object({ description: z.string().optional() })), + name: z.string(), +}); + +const getNpmReleaseAsync = async ( + baseUrl: string, + identifier: string, + versionRegex?: string, + token?: string, +): Promise => { + if (!identifier) return { success: false, error: { code: "invalidIdentifier", message: "Empty npm package name" } }; + + const headers: Record = {}; + if (token) headers.Authorization = `Bearer ${token}`; + + const response = await fetchProvider( + buildUrl(baseUrl, `/${encodeURIComponent(identifier)}`), + headers.Authorization ? { headers } : undefined, + ); + if (!response.ok) + return { success: false, error: { code: "unexpected", message: `npm ${response.status}: ${response.statusText}` } }; + + const result = npmReleasesSchema.safeParse(await response.json()); + if (!result.success) return { success: false, error: { code: "unexpected", message: result.error.message } }; + + const releases = result.data.time.map((tag) => ({ + ...tag, + releaseUrl: `https://www.npmjs.com/package/${encodeURIComponent(result.data.name)}/v/${encodeURIComponent(tag.latestRelease)}`, + releaseDescription: result.data.versions[tag.latestRelease]?.description ?? "", + })); + const matched = getLatestRelease(releases, versionRegex); + if ("noMatch" in matched) return { success: false, error: { code: "noMatchingVersion", message: matched.noMatch } }; + + return { success: true, data: matched.release }; +}; + +const codebergReleasesSchema = z.array( + z.object({ + tag_name: z.string(), + published_at: z.string().transform((value) => new Date(value)), + url: z.string(), + body: z.string(), + prerelease: z.boolean(), + }), +); + +const codebergDetailsSchema = z.object({ + html_url: z.string(), + description: z.string(), + fork: z.boolean(), + archived: z.boolean(), + created_at: z.string().transform((value) => new Date(value)), + stars_count: z.number(), + open_issues_count: z.number(), + forks_count: z.number(), +}); + +const getCodebergReleaseAsync = async ( + baseUrl: string, + identifier: string, + versionRegex?: string, + token?: string, +): Promise => { + const parsed = parseOwnerName(identifier); + if (!parsed) + return { + success: false, + error: { code: "invalidIdentifier", message: `Cannot parse "${identifier}" as owner/repo` }, + }; + + const headers: Record = {}; + if (token) headers.Authorization = `token ${token}`; + + const response = await fetchProvider( + buildUrl(baseUrl, `/api/v1/repos/${encodeURIComponent(parsed.owner)}/${encodeURIComponent(parsed.name)}/releases`), + headers.Authorization ? { headers } : undefined, + ); + if (!response.ok) + return { + success: false, + error: { code: "unexpected", message: `Codeberg ${response.status}: ${response.statusText}` }, + }; + + const result = codebergReleasesSchema.safeParse(await response.json()); + if (!result.success) return { success: false, error: { code: "unexpected", message: result.error.message } }; + + const releases = result.data.map((tag) => ({ + latestRelease: tag.tag_name, + latestReleaseAt: tag.published_at, + releaseUrl: tag.url, + releaseDescription: tag.body, + isPreRelease: tag.prerelease, + })); + const matched = getLatestRelease(releases, versionRegex); + if ("noMatch" in matched) return { success: false, error: { code: "noMatchingVersion", message: matched.noMatch } }; + + const details = await getCodebergDetailsAsync(baseUrl, parsed.owner, parsed.name, token); + return { success: true, data: { ...details, ...matched.release } }; +}; + +const getCodebergDetailsAsync = async ( + baseUrl: string, + owner: string, + name: string, + token?: string, +): Promise => { + const headers: Record = {}; + if (token) headers.Authorization = `token ${token}`; + + const response = await fetchProvider( + buildUrl(baseUrl, `/api/v1/repos/${encodeURIComponent(owner)}/${encodeURIComponent(name)}`), + headers.Authorization ? { headers } : undefined, + ); + if (!response.ok) return {}; + + const result = codebergDetailsSchema.safeParse(await response.json()); + if (!result.success) return {}; + + return { + projectUrl: result.data.html_url, + projectDescription: result.data.description, + isFork: result.data.fork, + isArchived: result.data.archived, + createdAt: result.data.created_at, + starsCount: result.data.stars_count, + openIssues: result.data.open_issues_count, + forksCount: result.data.forks_count, + }; +}; + +const linuxServerIOReleasesSchema = z.object({ + data: z.object({ + repositories: z.object({ + linuxserver: z.array( + z.object({ + name: z.string(), + initial_date: z + .string() + .transform((value) => new Date(value)) + .optional(), + github_url: z.string(), + description: z.string(), + version: z.string(), + version_timestamp: z.string().transform((value) => new Date(value)), + stars: z.number(), + deprecated: z.boolean(), + changelog: z.array(z.object({ date: z.string(), desc: z.string() })).optional(), + }), + ), + }), + }), +}); + +const getLinuxServerIOReleaseAsync = async ( + baseUrl: string, + identifier: string, + versionRegex?: string, +): Promise => { + const parsed = parseOwnerName(identifier); + if (!parsed) + return { + success: false, + error: { code: "invalidIdentifier", message: `Cannot parse "${identifier}" as owner/name` }, + }; + + const response = await fetchProvider(buildUrl(baseUrl, "/api/v1/images")); + if (!response.ok) + return { + success: false, + error: { code: "unexpected", message: `LSIO ${response.status}: ${response.statusText}` }, + }; + + const result = linuxServerIOReleasesSchema.safeParse(await response.json()); + if (!result.success) return { success: false, error: { code: "unexpected", message: result.error.message } }; + + const release = result.data.data.repositories.linuxserver.find((repo) => repo.name === parsed.name); + if (!release) + return { + success: false, + error: { code: "noReleasesFound", message: `Image "${parsed.name}" not found in LinuxServer.io registry` }, + }; + + const regex = compileVersionRegex(versionRegex); + if (versionRegex && !regex) { + return { + success: false, + error: { code: "noMatchingVersion", message: `Invalid regex: /${versionRegex}/` }, + }; + } + + if (regex && !regex.test(release.version)) { + return { + success: false, + error: { + code: "noMatchingVersion", + message: `Regex /${versionRegex}/ does not match LSIO version "${release.version}"`, + }, + }; + } + + return { + success: true, + data: { + latestRelease: release.version, + latestReleaseAt: release.version_timestamp, + releaseDescription: release.changelog?.shift()?.desc, + projectUrl: release.github_url, + projectDescription: release.description, + isArchived: release.deprecated, + createdAt: release.initial_date, + starsCount: release.stars, + }, + }; +}; + +const quayReleasesSchema = z.object({ + description: z.string().optional(), + tags: z.record(z.string(), z.object({ name: z.string(), last_modified: z.string() })), +}); + +const getQuayReleaseAsync = async ( + baseUrl: string, + identifier: string, + versionRegex?: string, +): Promise => { + const parsed = parseOwnerName(identifier); + if (!parsed) + return { + success: false, + error: { code: "invalidIdentifier", message: `Cannot parse "${identifier}" as owner/repo` }, + }; + + const response = await fetchProvider( + buildUrl( + baseUrl, + `/api/v1/repository/${encodeURIComponent(parsed.owner)}/${encodeURIComponent(parsed.name)}?includeTags=true&includeStats=true`, + ), + ); + if (!response.ok) + return { + success: false, + error: { code: "unexpected", message: `Quay ${response.status}: ${response.statusText}` }, + }; + + const result = quayReleasesSchema.safeParse(await response.json()); + if (!result.success) return { success: false, error: { code: "unexpected", message: result.error.message } }; + + const releases = Object.values(result.data.tags).reduce((acc, tag) => { + if (!tag.name || !tag.last_modified) return acc; + acc.push({ + latestRelease: tag.name, + latestReleaseAt: new Date(tag.last_modified), + releaseUrl: `https://quay.io/repository/${encodeURIComponent(parsed.owner)}/${encodeURIComponent(parsed.name)}/tag/${encodeURIComponent(tag.name)}`, + }); + return acc; + }, []); + const matched = getLatestRelease(releases, versionRegex); + if ("noMatch" in matched) return { success: false, error: { code: "noMatchingVersion", message: matched.noMatch } }; + + return { success: true, data: { projectDescription: result.data.description, ...matched.release } }; +}; diff --git a/packages/request-handler/src/releases.ts b/packages/request-handler/src/releases.ts index 7bfd29348..e089bc661 100644 --- a/packages/request-handler/src/releases.ts +++ b/packages/request-handler/src/releases.ts @@ -1,20 +1,10 @@ -import dayjs from "dayjs"; +import type { ReleaseResponse, ReleasesRepositoryRequest } from "./release-providers"; +import { getLatestMatchingReleaseAsync } from "./release-providers"; +import { createRequestHandler } from "./lib/request-handler"; -import type { IntegrationKindByCategory } from "@homarr/definitions"; -import type { ReleaseResponse, ReleasesRepository } from "@homarr/integrations"; -import { createIntegrationAsync } from "@homarr/integrations"; - -import { createCachedIntegrationRequestHandler } from "./lib/cached-integration-request-handler"; - -export const releasesRequestHandler = createCachedIntegrationRequestHandler< - ReleaseResponse, - IntegrationKindByCategory<"releasesProvider">, - ReleasesRepository ->({ - requestAsync: async (integration, input) => { - const instance = await createIntegrationAsync(integration); - return instance.getLatestMatchingReleaseAsync(input.identifier, input.versionRegex); +export const releasesRequestHandler = createRequestHandler({ + async requestAsync(input) { + return await getLatestMatchingReleaseAsync(input); }, - cacheDuration: dayjs.duration(5, "minutes"), - queryKey: "repositoriesReleases", + cacheTtlMs: 5 * 60 * 1000, }); diff --git a/packages/request-handler/src/rss-feeds.ts b/packages/request-handler/src/rss-feeds.ts index 9eaf026ea..0597a325f 100644 --- a/packages/request-handler/src/rss-feeds.ts +++ b/packages/request-handler/src/rss-feeds.ts @@ -1,18 +1,15 @@ import type { FeedData, FeedEntry } from "@extractus/feed-extractor"; import { extract } from "@extractus/feed-extractor"; -import dayjs from "dayjs"; import { z } from "zod/v4"; import type { Modify } from "@homarr/common/types"; import { createLogger } from "@homarr/core/infrastructure/logs"; -import { createCachedWidgetRequestHandler } from "./lib/cached-widget-request-handler"; +import { createWidgetRequestHandler } from "./lib/widget-request-handler"; const logger = createLogger({ module: "rssFeedsRequestHandler" }); -export const rssFeedsRequestHandler = createCachedWidgetRequestHandler({ - queryKey: "rssFeedList", - widgetKind: "rssFeed", +export const rssFeedsRequestHandler = createWidgetRequestHandler({ async requestAsync(input: { url: string; count: number }) { const result = (await extract(input.url, { getExtraEntryFields: (feedEntry) => { @@ -31,7 +28,6 @@ export const rssFeedsRequestHandler = createCachedWidgetRequestHandler({ entries: result.entries?.map((entry) => ({ ...entry, feedUrl: input.url })).slice(0, input.count) ?? [], }; }, - cacheDuration: dayjs.duration(5, "minutes"), }); const attemptGetImageFromEntry = (feedUrl: string, entry: object) => { diff --git a/packages/request-handler/src/smart-home-entity-state.ts b/packages/request-handler/src/smart-home-entity-state.ts index 5fe02b2ac..3c8851522 100644 --- a/packages/request-handler/src/smart-home-entity-state.ts +++ b/packages/request-handler/src/smart-home-entity-state.ts @@ -1,11 +1,9 @@ -import dayjs from "dayjs"; - import type { IntegrationKindByCategory } from "@homarr/definitions"; import { createIntegrationAsync } from "@homarr/integrations"; -import { createCachedIntegrationRequestHandler } from "./lib/cached-integration-request-handler"; +import { createIntegrationRequestHandler } from "./lib/integration-request-handler"; -export const smartHomeEntityStateRequestHandler = createCachedIntegrationRequestHandler< +export const smartHomeEntityStateRequestHandler = createIntegrationRequestHandler< string, IntegrationKindByCategory<"smartHomeServer">, { entityId: string } @@ -20,6 +18,4 @@ export const smartHomeEntityStateRequestHandler = createCachedIntegrationRequest return result.data.state; }, - cacheDuration: dayjs.duration(1, "minute"), - queryKey: "smartHome-entityState", }); diff --git a/packages/request-handler/src/speedtest-tracker.ts b/packages/request-handler/src/speedtest-tracker.ts index 799d37f16..3f03f7187 100644 --- a/packages/request-handler/src/speedtest-tracker.ts +++ b/packages/request-handler/src/speedtest-tracker.ts @@ -1,11 +1,9 @@ -import dayjs from "dayjs"; - import { createIntegrationAsync } from "@homarr/integrations"; import type { SpeedtestTrackerDashboardData } from "@homarr/integrations/types"; -import { createCachedIntegrationRequestHandler } from "./lib/cached-integration-request-handler"; +import { createIntegrationRequestHandler } from "./lib/integration-request-handler"; -export const speedtestTrackerRequestHandler = createCachedIntegrationRequestHandler< +export const speedtestTrackerRequestHandler = createIntegrationRequestHandler< SpeedtestTrackerDashboardData, "speedtestTracker", Record @@ -14,6 +12,4 @@ export const speedtestTrackerRequestHandler = createCachedIntegrationRequestHand const integrationInstance = await createIntegrationAsync(integration); return await integrationInstance.getDashboardDataAsync(); }, - cacheDuration: dayjs.duration(10, "minutes"), - queryKey: "speedtestTrackerDashboard", }); diff --git a/packages/request-handler/src/stock-price.ts b/packages/request-handler/src/stock-price.ts index b0ca15e47..177b223aa 100644 --- a/packages/request-handler/src/stock-price.ts +++ b/packages/request-handler/src/stock-price.ts @@ -1,14 +1,11 @@ -import dayjs from "dayjs"; import { z } from "zod/v4"; import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http"; import { withTimeoutAsync } from "@homarr/core/infrastructure/http/timeout"; -import { createCachedWidgetRequestHandler } from "./lib/cached-widget-request-handler"; +import { createWidgetRequestHandler } from "./lib/widget-request-handler"; -export const fetchStockPriceHandler = createCachedWidgetRequestHandler({ - queryKey: "fetchStockPriceResult", - widgetKind: "stockPrice", +export const fetchStockPriceHandler = createWidgetRequestHandler({ async requestAsync(input: { stock: string; timeRange: string; timeInterval: string }) { const response = await withTimeoutAsync(async (signal) => { return await fetchWithTrustedCertificatesAsync( @@ -42,7 +39,6 @@ export const fetchStockPriceHandler = createCachedWidgetRequestHandler({ shortName: firstResult.meta.shortName, }; }, - cacheDuration: dayjs.duration(5, "minutes"), }); const dataSchema = z diff --git a/packages/request-handler/src/test/release-providers.spec.ts b/packages/request-handler/src/test/release-providers.spec.ts new file mode 100644 index 000000000..67ed3d71c --- /dev/null +++ b/packages/request-handler/src/test/release-providers.spec.ts @@ -0,0 +1,192 @@ +import { beforeEach, describe, expect, test, vi } from "vitest"; +import { Response } from "undici"; + +import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http"; + +import { getLatestMatchingReleaseAsync } from "../release-providers"; + +vi.mock("@homarr/core/infrastructure/http", () => ({ + fetchWithTrustedCertificatesAsync: vi.fn(), +})); +vi.mock("@homarr/core/infrastructure/logs", () => ({ + createLogger: () => ({ + warn: vi.fn(), + }), +})); + +const mockedFetch = vi.mocked(fetchWithTrustedCertificatesAsync); + +describe("getLatestMatchingReleaseAsync for GitHub Container Registry", () => { + beforeEach(() => { + mockedFetch.mockReset(); + }); + + test("normalizes full image refs and returns latest from anonymous OCI registry metadata", async () => { + mockGhcrResponses({ + repositoryName: "homarr-labs/homarr", + tags: ["v1.0.0", "latest"], + createdAtByTag: { + latest: "2026-06-19T20:30:59.853Z", + }, + }); + + const result = await getLatestMatchingReleaseAsync({ + id: "homarr", + provider: "gitHubContainerRegistry", + identifier: "ghcr.io/homarr-labs/homarr:latest", + }); + + expect(result).toEqual({ + success: true, + data: { + projectUrl: "https://github.com/homarr-labs/homarr", + latestRelease: "latest", + latestReleaseAt: new Date("2026-06-19T20:30:59.853Z"), + releaseUrl: "https://github.com/homarr-labs/homarr/pkgs/container/homarr", + }, + }); + expectUrl(0, "https://ghcr.io/token"); + expect(new URL(mockedFetch.mock.calls[0]?.[0].toString() ?? "").searchParams.get("service")).toBe("ghcr.io"); + expect(new URL(mockedFetch.mock.calls[0]?.[0].toString() ?? "").searchParams.get("scope")).toBe( + "repository:homarr-labs/homarr:pull", + ); + }); + + test("uses custom registry base URL for GHCR-compatible registries", async () => { + mockGhcrResponses({ + registryOrigin: "https://registry.example.com", + repositoryName: "homarr-labs/homarr", + tags: ["latest"], + createdAtByTag: { + latest: "2026-06-19T20:30:59.853Z", + }, + }); + + await getLatestMatchingReleaseAsync({ + id: "homarr", + provider: "gitHubContainerRegistry", + identifier: "homarr-labs/homarr", + providerUrl: "https://registry.example.com/", + }); + + expectUrl(0, "https://registry.example.com/token"); + expect(new URL(mockedFetch.mock.calls[0]?.[0].toString() ?? "").searchParams.get("service")).toBe( + "registry.example.com", + ); + expectUrl(1, "https://registry.example.com/v2/homarr-labs/homarr/tags/list"); + }); + + test("chooses the newest matching version tag", async () => { + mockGhcrResponses({ + repositoryName: "muchobien/pocketbase", + tags: ["0.22.0", "0.23.0", "latest"], + createdAtByTag: { + "0.22.0": "2026-01-01T00:00:00.000Z", + "0.23.0": "2026-02-01T00:00:00.000Z", + }, + }); + + const result = await getLatestMatchingReleaseAsync({ + id: "pocketbase", + provider: "gitHubContainerRegistry", + identifier: "ghcr.io/muchobien/pocketbase:latest", + versionRegex: "^0\\.[0-9]+\\.[0-9]+$", + }); + + expect(result.success).toBe(true); + if (!result.success) return; + expect(result.data.latestRelease).toBe("0.23.0"); + expect(result.data.latestReleaseAt).toEqual(new Date("2026-02-01T00:00:00.000Z")); + }); + + test("returns invalidIdentifier for missing package name", async () => { + const result = await getLatestMatchingReleaseAsync({ + id: "invalid", + provider: "gitHubContainerRegistry", + identifier: "ghcr.io/homarr-labs", + }); + + expect(result).toEqual({ + success: false, + error: expect.objectContaining({ code: "invalidIdentifier" }), + }); + expect(mockedFetch).not.toHaveBeenCalled(); + }); + + test("returns unexpected with provider error message", async () => { + mockedFetch.mockResolvedValueOnce(createJsonResponse({ errors: [] }, { status: 500, statusText: "Registry down" })); + + const result = await getLatestMatchingReleaseAsync({ + id: "homarr", + provider: "gitHubContainerRegistry", + identifier: "homarr-labs/homarr", + }); + + expect(result).toEqual({ success: false, error: { code: "unexpected", message: "Registry down" } }); + }); +}); + +interface MockGhcrResponsesInput { + registryOrigin?: string; + repositoryName: string; + tags: string[]; + createdAtByTag: Record; +} + +const mockGhcrResponses = ({ + registryOrigin = "https://ghcr.io", + repositoryName, + tags, + createdAtByTag, +}: MockGhcrResponsesInput) => { + mockedFetch.mockImplementation(async (input) => { + const url = new URL(input.toString()); + const path = url.pathname; + + if (url.origin !== registryOrigin) { + return createJsonResponse({ error: "unexpected origin" }, { status: 404, statusText: "Not Found" }); + } + + if (path === "/token") return createJsonResponse({ token: "token" }); + if (path === `/v2/${repositoryName}/tags/list`) return createJsonResponse({ name: repositoryName, tags }); + + const manifestMatch = new RegExp(`^/v2/${escapeRegExp(repositoryName)}/manifests/(.+)$`).exec(path); + if (manifestMatch) { + const reference = decodeURIComponent(manifestMatch[1] ?? ""); + if (reference.startsWith("sha256:")) { + return createJsonResponse({ config: { digest: `sha256:config-${reference.slice("sha256:".length)}` } }); + } + return createJsonResponse({ + manifests: [{ digest: `sha256:${reference}` }], + }); + } + + const blobMatch = new RegExp(`^/v2/${escapeRegExp(repositoryName)}/blobs/sha256:config-(.+)$`).exec(path); + if (blobMatch) { + const tag = decodeURIComponent(blobMatch[1] ?? ""); + return createJsonResponse({ created: createdAtByTag[tag] ?? "2026-01-01T00:00:00.000Z" }); + } + + return createJsonResponse({ error: "not found" }, { status: 404, statusText: "Not Found" }); + }); +}; + +interface JsonResponseInit { + status?: number; + statusText?: string; +} + +const createJsonResponse = (body: unknown, init: JsonResponseInit = {}) => + new Response(JSON.stringify(body), { + status: init.status ?? 200, + statusText: init.statusText, + headers: { + "content-type": "application/json", + }, + }); + +const expectUrl = (callIndex: number, expectedUrl: string) => { + expect(mockedFetch.mock.calls[callIndex]?.[0].toString().split("?")[0]).toBe(expectedUrl); +}; + +const escapeRegExp = (value: string) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); diff --git a/packages/request-handler/src/timetable.ts b/packages/request-handler/src/timetable.ts index 0946ac67d..2be7355d8 100644 --- a/packages/request-handler/src/timetable.ts +++ b/packages/request-handler/src/timetable.ts @@ -1,36 +1,154 @@ -import dayjs from "dayjs"; +import { z } from "zod/v4"; -import type { IntegrationKindByCategory } from "@homarr/definitions"; -import { createIntegrationAsync } from "@homarr/integrations"; -import type { Station, Timetable } from "@homarr/integrations/types"; +import { ResponseError } from "@homarr/common/server"; +import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http"; -import { createCachedIntegrationRequestHandler } from "./lib/cached-integration-request-handler"; +import { createRequestHandler } from "./lib/request-handler"; -export const timetableSearchStationsRequestHandler = createCachedIntegrationRequestHandler< +export interface Station { + id: string; + name: string; +} + +export interface TimetableEntry { + timestamp: Date; + delay: number; + line: { + name: string; + color: string | null; + } | null; + location: string; + platform: { + name: string; + hasChanged: boolean; + } | null; +} + +export interface Timetable { + stationId: string; + timestamp: Date; + entries: TimetableEntry[]; +} + +const supportedStationTypes = ["bus", "tram", "train", "ship", "cablecar", "funicular", "chairlift"]; + +export const timetableSearchStationsRequestHandler = createRequestHandler< Station[], - IntegrationKindByCategory<"timetable">, - { query: string } + { baseUrl: string; query: string } >({ - async requestAsync(integration, input) { - const integrationInstance = await createIntegrationAsync(integration); - return await integrationInstance.searchStationsAsync(input.query); + async requestAsync(input) { + return await searchStationsAsync(input.baseUrl, input.query); }, - queryKey: "timetableSearchStations", - cacheDuration: dayjs.duration(1, "day"), + cacheTtlMs: 24 * 60 * 60 * 1000, }); -export const timetableGetTimetableRequestHandler = createCachedIntegrationRequestHandler< +export const timetableGetTimetableRequestHandler = createRequestHandler< Timetable, - IntegrationKindByCategory<"timetable">, - { stationId: string; limit: number } + { baseUrl: string; stationId: string; limit: number } >({ - async requestAsync(integration, input) { - const integrationInstance = await createIntegrationAsync(integration); - return await integrationInstance.getTimetableAsync({ - stationId: input.stationId, - limit: input.limit, - }); + async requestAsync(input) { + return await getTimetableAsync(input.baseUrl, { stationId: input.stationId, limit: input.limit }); }, - queryKey: "timetableGetTimetable", - cacheDuration: dayjs.duration(1, "minute"), + cacheTtlMs: 60_000, }); + +const buildUrl = (baseUrl: string, path: `/${string}`, queryParams: Record) => { + const url = new URL(`${baseUrl.replace(/\/$/, "")}${path}`); + for (const [key, value] of Object.entries(queryParams)) { + url.searchParams.set(key, value.toString()); + } + return url.toString(); +}; + +const searchStationsAsync = async (baseUrl: string, query: string): Promise => { + const response = await fetchWithTrustedCertificatesAsync( + buildUrl(baseUrl, "/timetable/api/completion.json", { term: query, show_ids: 1, nofavorites: 1 }), + ); + if (!response.ok) throw new ResponseError(response); + + const data = await searchSchema.parseAsync(await response.json()); + return data + .filter((item) => supportedStationTypes.some((type) => item.iconclass.endsWith(type))) + .map((item) => (item.id !== undefined ? { id: item.id, name: item.label } : null)) + .filter((item) => item !== null); +}; + +const getTimetableAsync = async ( + baseUrl: string, + options: { stationId: string; limit: number }, +): Promise => { + const now = new Date(); + const date = new Intl.DateTimeFormat("en-US", { + timeZone: "Europe/Zurich", + month: "2-digit", + day: "2-digit", + year: "numeric", + }).format(now); + const time = new Intl.DateTimeFormat("en-GB", { + timeZone: "Europe/Zurich", + hour: "2-digit", + minute: "2-digit", + hour12: false, + }).format(now); + const response = await fetchWithTrustedCertificatesAsync( + buildUrl(baseUrl, "/timetable/api/stationboard.json", { + stop: options.stationId, + limit: options.limit, + show_delays: 1, + show_tracks: 1, + date, + time, + }), + ); + if (!response.ok) throw new ResponseError(response); + + const data = await timetableSchema.parseAsync(await response.json()); + return { + stationId: options.stationId, + timestamp: now, + entries: data.connections.map((connection) => { + const color = connection.color.split("~")[0]; + return { + timestamp: connection.time, + line: connection.line + ? { + name: connection.line, + color: color && color.length >= 1 ? `#${color}` : null, + } + : null, + location: connection.terminal.name, + delay: connection.dep_delay, + platform: connection.track + ? { + name: connection.track.replace("!", ""), + hasChanged: connection.track.includes("!"), + } + : null, + }; + }), + }; +}; + +const timetableSchema = z.object({ + connections: z.array( + z.object({ + time: z.string().transform((str) => new Date(str)), + line: z.string().optional(), + color: z.string(), + terminal: z.object({ name: z.string() }), + track: z.string().optional(), + dep_delay: z + .string() + .optional() + .transform((str) => (str ? parseInt(str) : 0)), + }), + ), +}); + +const searchSchema = z.array( + z.object({ + id: z.string().optional(), + label: z.string(), + iconclass: z.string(), + }), +); diff --git a/packages/request-handler/src/tracearr.ts b/packages/request-handler/src/tracearr.ts index 8f593ac03..641bf2922 100644 --- a/packages/request-handler/src/tracearr.ts +++ b/packages/request-handler/src/tracearr.ts @@ -1,11 +1,9 @@ -import dayjs from "dayjs"; - import { createIntegrationAsync } from "@homarr/integrations"; import type { TracearrDashboardData } from "@homarr/integrations/types"; -import { createCachedIntegrationRequestHandler } from "./lib/cached-integration-request-handler"; +import { createIntegrationRequestHandler } from "./lib/integration-request-handler"; -export const tracearrRequestHandler = createCachedIntegrationRequestHandler< +export const tracearrRequestHandler = createIntegrationRequestHandler< TracearrDashboardData, "tracearr", Record @@ -14,6 +12,4 @@ export const tracearrRequestHandler = createCachedIntegrationRequestHandler< const integrationInstance = await createIntegrationAsync(integration); return await integrationInstance.getDashboardDataAsync(); }, - cacheDuration: dayjs.duration(5, "seconds"), - queryKey: "tracearrDashboard", }); diff --git a/packages/request-handler/src/umami.ts b/packages/request-handler/src/umami.ts index e943c568a..02ef71140 100644 --- a/packages/request-handler/src/umami.ts +++ b/packages/request-handler/src/umami.ts @@ -1,11 +1,9 @@ -import dayjs from "dayjs"; - import { createIntegrationAsync } from "@homarr/integrations"; import type { UmamiEventSeries, UmamiMetricItem, UmamiVisitorStats } from "@homarr/integrations/types"; -import { createCachedIntegrationRequestHandler } from "./lib/cached-integration-request-handler"; +import { createIntegrationRequestHandler } from "./lib/integration-request-handler"; -export const umamiRequestHandler = createCachedIntegrationRequestHandler< +export const umamiRequestHandler = createIntegrationRequestHandler< UmamiVisitorStats, "umami", { websiteId: string; timeFrame: string; eventName?: string } @@ -14,24 +12,16 @@ export const umamiRequestHandler = createCachedIntegrationRequestHandler< const integrationInstance = await createIntegrationAsync(integration); return await integrationInstance.getVisitorStatsAsync(input.websiteId, input.timeFrame, input.eventName); }, - cacheDuration: dayjs.duration(5, "minutes"), - queryKey: "umamiVisitorStats", }); -export const umamiEventNamesRequestHandler = createCachedIntegrationRequestHandler< - string[], - "umami", - { websiteId: string } ->({ +export const umamiEventNamesRequestHandler = createIntegrationRequestHandler({ async requestAsync(integration, input) { const integrationInstance = await createIntegrationAsync(integration); return await integrationInstance.getEventNamesAsync(input.websiteId); }, - cacheDuration: dayjs.duration(5, "minutes"), - queryKey: "umamiEventNames", }); -export const umamiTopPagesRequestHandler = createCachedIntegrationRequestHandler< +export const umamiTopPagesRequestHandler = createIntegrationRequestHandler< UmamiMetricItem[], "umami", { websiteId: string; timeFrame: string; limit: number } @@ -40,11 +30,9 @@ export const umamiTopPagesRequestHandler = createCachedIntegrationRequestHandler const integrationInstance = await createIntegrationAsync(integration); return await integrationInstance.getTopPagesAsync(input.websiteId, input.timeFrame, input.limit); }, - cacheDuration: dayjs.duration(5, "minutes"), - queryKey: "umamiTopPages", }); -export const umamiTopReferrersRequestHandler = createCachedIntegrationRequestHandler< +export const umamiTopReferrersRequestHandler = createIntegrationRequestHandler< UmamiMetricItem[], "umami", { websiteId: string; timeFrame: string; limit: number } @@ -53,11 +41,9 @@ export const umamiTopReferrersRequestHandler = createCachedIntegrationRequestHan const integrationInstance = await createIntegrationAsync(integration); return await integrationInstance.getTopReferrersAsync(input.websiteId, input.timeFrame, input.limit); }, - cacheDuration: dayjs.duration(5, "minutes"), - queryKey: "umamiTopReferrers", }); -export const umamiMultiEventRequestHandler = createCachedIntegrationRequestHandler< +export const umamiMultiEventRequestHandler = createIntegrationRequestHandler< UmamiEventSeries[], "umami", { websiteId: string; timeFrame: string; eventNames: string[] } @@ -66,11 +52,9 @@ export const umamiMultiEventRequestHandler = createCachedIntegrationRequestHandl const integrationInstance = await createIntegrationAsync(integration); return await integrationInstance.getMultiEventTimeSeriesAsync(input.websiteId, input.timeFrame, input.eventNames); }, - cacheDuration: dayjs.duration(5, "minutes"), - queryKey: "umamiMultiEvent", }); -export const umamiActiveVisitorsRequestHandler = createCachedIntegrationRequestHandler< +export const umamiActiveVisitorsRequestHandler = createIntegrationRequestHandler< number, "umami", { websiteId: string } @@ -79,6 +63,4 @@ export const umamiActiveVisitorsRequestHandler = createCachedIntegrationRequestH const integrationInstance = await createIntegrationAsync(integration); return await integrationInstance.getActiveVisitorsAsync(input.websiteId); }, - cacheDuration: dayjs.duration(30, "seconds"), - queryKey: "umamiActiveVisitors", }); diff --git a/packages/request-handler/src/update-checker.ts b/packages/request-handler/src/update-checker.ts index 8709f20c5..1cf4dbd6f 100644 --- a/packages/request-handler/src/update-checker.ts +++ b/packages/request-handler/src/update-checker.ts @@ -1,4 +1,3 @@ -import dayjs from "dayjs"; import { Octokit } from "octokit"; import { compareSemVer, isValidSemVer, parseSemVer } from "semver-parser"; @@ -6,27 +5,18 @@ import { env } from "@homarr/common/env"; import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http"; import { createLogger } from "@homarr/core/infrastructure/logs"; import { ErrorWithMetadata } from "@homarr/core/infrastructure/logs/error"; -import { createChannelWithLatestAndEvents } from "@homarr/redis"; -import { createCachedRequestHandler } from "@homarr/request-handler/lib/cached-request-handler"; +import { createRequestHandler } from "./lib/request-handler"; import packageJson from "../../../package.json"; const logger = createLogger({ module: "updateCheckerRequestHandler" }); -export const updateCheckerRequestHandler = createCachedRequestHandler({ - queryKey: "homarr-update-checker", - cacheDuration: dayjs.duration(1, "day"), - fallbackToStaleOnError: true, +export const updateCheckerRequestHandler = createRequestHandler({ async requestAsync(_) { - return { - availableUpdates: await getAvailableUpdatesAsync(packageJson.version), - }; - }, - createRedisChannel() { - return createChannelWithLatestAndEvents<{ - availableUpdates: Update[]; - }>("homarr:update"); + const availableUpdates = await getAvailableUpdatesAsync(packageJson.version); + return { availableUpdates }; }, + cacheTtlMs: 30 * 60 * 1000, }); interface Update { diff --git a/packages/request-handler/src/ups.ts b/packages/request-handler/src/ups.ts index acc97eb6d..4ca17b5ee 100644 --- a/packages/request-handler/src/ups.ts +++ b/packages/request-handler/src/ups.ts @@ -1,12 +1,10 @@ -import dayjs from "dayjs"; - import type { IntegrationKindByCategory } from "@homarr/definitions"; import { createIntegrationAsync } from "@homarr/integrations"; import type { UpsSummary } from "@homarr/integrations/types"; -import { createCachedIntegrationRequestHandler } from "./lib/cached-integration-request-handler"; +import { createIntegrationRequestHandler } from "./lib/integration-request-handler"; -export const upsSummariesRequestHandler = createCachedIntegrationRequestHandler< +export const upsSummariesRequestHandler = createIntegrationRequestHandler< UpsSummary[], IntegrationKindByCategory<"ups">, Record @@ -15,6 +13,4 @@ export const upsSummariesRequestHandler = createCachedIntegrationRequestHandler< const integrationInstance = await createIntegrationAsync(integration); return await integrationInstance.getUpsSummariesAsync(); }, - cacheDuration: dayjs.duration(1, "minute"), - queryKey: "upsSummaries", }); diff --git a/packages/request-handler/src/uptime-kuma.ts b/packages/request-handler/src/uptime-kuma.ts index 39149c137..b9453eb76 100644 --- a/packages/request-handler/src/uptime-kuma.ts +++ b/packages/request-handler/src/uptime-kuma.ts @@ -1,11 +1,9 @@ -import dayjs from "dayjs"; - import { createIntegrationAsync } from "@homarr/integrations"; import type { UptimeKumaDashboardData } from "@homarr/integrations/types"; -import { createCachedIntegrationRequestHandler } from "./lib/cached-integration-request-handler"; +import { createIntegrationRequestHandler } from "./lib/integration-request-handler"; -export const uptimeKumaRequestHandler = createCachedIntegrationRequestHandler< +export const uptimeKumaRequestHandler = createIntegrationRequestHandler< UptimeKumaDashboardData, "uptimeKuma", Record @@ -14,6 +12,4 @@ export const uptimeKumaRequestHandler = createCachedIntegrationRequestHandler< const integrationInstance = await createIntegrationAsync(integration); return await integrationInstance.getDashboardDataAsync(); }, - cacheDuration: dayjs.duration(1, "minute"), - queryKey: "uptimeKumaDashboard", }); diff --git a/packages/request-handler/src/vpn.ts b/packages/request-handler/src/vpn.ts index de068938c..ebca0fbeb 100644 --- a/packages/request-handler/src/vpn.ts +++ b/packages/request-handler/src/vpn.ts @@ -1,15 +1,13 @@ -import dayjs from "dayjs"; - import { createLogger } from "@homarr/core/infrastructure/logs"; import type { IntegrationKindByCategory } from "@homarr/definitions"; import { createIntegrationAsync } from "@homarr/integrations"; import type { VpnSummary } from "@homarr/integrations/types"; -import { createCachedIntegrationRequestHandler } from "./lib/cached-integration-request-handler"; +import { createIntegrationRequestHandler } from "./lib/integration-request-handler"; const logger = createLogger({ module: "vpnRequestHandler" }); -export const vpnSummaryHandler = createCachedIntegrationRequestHandler< +export const vpnSummaryHandler = createIntegrationRequestHandler< VpnSummary | null, IntegrationKindByCategory<"vpn">, Record @@ -19,9 +17,6 @@ export const vpnSummaryHandler = createCachedIntegrationRequestHandler< const integrationInstance = await createIntegrationAsync(integration); return await integrationInstance.getSummaryAsync(); } catch (error) { - // The VPN control server is unreachable (e.g. the container is down). - // Return null so the widget can render an "unavailable" status instead of - // surfacing a hard error, and so it recovers automatically once it is back up. logger.warn("Failed to fetch VPN summary, marking as unavailable", { integrationId: integration.id, integrationKind: integration.kind, @@ -30,6 +25,4 @@ export const vpnSummaryHandler = createCachedIntegrationRequestHandler< return null; } }, - cacheDuration: dayjs.duration(5, "minutes"), - queryKey: "vpnSummary", }); diff --git a/packages/request-handler/src/weather.ts b/packages/request-handler/src/weather.ts index eb4828048..5f5a097a4 100644 --- a/packages/request-handler/src/weather.ts +++ b/packages/request-handler/src/weather.ts @@ -1,14 +1,11 @@ -import dayjs from "dayjs"; import { z } from "zod"; import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http"; import { withTimeoutAsync } from "@homarr/core/infrastructure/http/timeout"; -import { createCachedWidgetRequestHandler } from "./lib/cached-widget-request-handler"; +import { createWidgetRequestHandler } from "./lib/widget-request-handler"; -export const weatherRequestHandler = createCachedWidgetRequestHandler({ - queryKey: "weatherAtLocation", - widgetKind: "weather", +export const weatherRequestHandler = createWidgetRequestHandler({ async requestAsync(input: { latitude: number; longitude: number }) { const res = await withTimeoutAsync(async (signal) => { return await fetchWithTrustedCertificatesAsync( @@ -34,7 +31,6 @@ export const weatherRequestHandler = createCachedWidgetRequestHandler({ }), } satisfies Weather; }, - cacheDuration: dayjs.duration(1, "minute"), }); const atLocationOutput = z.object({ diff --git a/packages/translation/src/lang/ca.json b/packages/translation/src/lang/ca.json index 18d07473d..f854b4a16 100644 --- a/packages/translation/src/lang/ca.json +++ b/packages/translation/src/lang/ca.json @@ -1021,6 +1021,7 @@ "mediaMonitoring": "", "speedtest": "", "analytics": "", + "vpn": "", "mediaLibrary": "", "uptimeMonitoring": "" }, @@ -1412,7 +1413,10 @@ "here": "", "iconPicker": { "label": "", - "header": "" + "header": "", + "headerLoading": "", + "noResults": "", + "error": "" }, "colorScheme": { "options": { @@ -1464,6 +1468,7 @@ "switchToDarkMode": "", "switchToLightMode": "", "management": "", + "docker": "", "preferences": "", "logout": "", "login": "", @@ -2352,6 +2357,9 @@ "name": "", "description": "", "option": { + "columns": { + "label": "" + }, "enableRowSorting": { "label": "" }, @@ -2405,6 +2413,12 @@ "title": "" } }, + "vpn": { + "name": "", + "description": "", + "serviceUnavailable": "", + "option": {} + }, "ups": { "name": "", "description": "", @@ -3639,6 +3653,9 @@ "error": { "internalServerError": "" }, + "empty": { + "noSystems": "" + }, "metric": { "cpu": "", "memory": "", @@ -3712,6 +3729,9 @@ } }, "selectSystem": "", + "empty": { + "noSystems": "" + }, "error": { "internalServerError": "", "liveConnectionFailed": "", @@ -4133,6 +4153,11 @@ "title": "", "text": "" }, + "error": { + "title": "", + "text": "", + "digest": "" + }, "navbar": { "items": { "home": "", @@ -4579,6 +4604,9 @@ }, "umamiActiveVisitors": { "label": "" + }, + "vpn": { + "label": "" } }, "interval": { @@ -5071,7 +5099,8 @@ } }, "error": { - "internalServerError": "" + "internalServerError": "", + "noColumns": "" } }, "kubernetes": { diff --git a/packages/translation/src/lang/cn.json b/packages/translation/src/lang/cn.json index 04f93c6b9..fb801729f 100644 --- a/packages/translation/src/lang/cn.json +++ b/packages/translation/src/lang/cn.json @@ -1021,6 +1021,7 @@ "mediaMonitoring": "", "speedtest": "", "analytics": "", + "vpn": "", "mediaLibrary": "", "uptimeMonitoring": "" }, @@ -1412,7 +1413,10 @@ "here": "这里", "iconPicker": { "label": "图标 URL", - "header": "输入名称或对象来筛选图标… Homarr 将为您搜索 {countIcons} 图标。" + "header": "输入名称或对象来筛选图标… Homarr 将为您搜索 {countIcons} 图标。", + "headerLoading": "", + "noResults": "", + "error": "" }, "colorScheme": { "options": { @@ -1464,6 +1468,7 @@ "switchToDarkMode": "切换到暗黑模式", "switchToLightMode": "切换到日间模式", "management": "管理中心", + "docker": "", "preferences": "偏好设置", "logout": "退出登录", "login": "登录", @@ -2352,6 +2357,9 @@ "name": "Docker 状态", "description": "您的容器状态 (这个小部件只能用管理员权限添加)", "option": { + "columns": { + "label": "" + }, "enableRowSorting": { "label": "启用项目排序" }, @@ -2405,6 +2413,12 @@ "title": "实例" } }, + "vpn": { + "name": "", + "description": "", + "serviceUnavailable": "", + "option": {} + }, "ups": { "name": "", "description": "", @@ -3639,6 +3653,9 @@ "error": { "internalServerError": "" }, + "empty": { + "noSystems": "" + }, "metric": { "cpu": "", "memory": "", @@ -3712,6 +3729,9 @@ } }, "selectSystem": "", + "empty": { + "noSystems": "" + }, "error": { "internalServerError": "", "liveConnectionFailed": "", @@ -4133,6 +4153,11 @@ "title": "未找到", "text": "找不到请求的资源" }, + "error": { + "title": "", + "text": "", + "digest": "" + }, "navbar": { "items": { "home": "首页", @@ -4579,6 +4604,9 @@ }, "umamiActiveVisitors": { "label": "Umami 活跃访客" + }, + "vpn": { + "label": "" } }, "interval": { @@ -5071,7 +5099,8 @@ } }, "error": { - "internalServerError": "获取 Docker 容器失败" + "internalServerError": "获取 Docker 容器失败", + "noColumns": "" } }, "kubernetes": { diff --git a/packages/translation/src/lang/cr.json b/packages/translation/src/lang/cr.json index 9341366dc..d94172129 100644 --- a/packages/translation/src/lang/cr.json +++ b/packages/translation/src/lang/cr.json @@ -1021,6 +1021,7 @@ "mediaMonitoring": "crwdns4473:0crwdne4473:0", "speedtest": "crwdns4475:0crwdne4475:0", "analytics": "crwdns4477:0crwdne4477:0", + "vpn": "crwdns5687:0crwdne5687:0", "mediaLibrary": "crwdns4979:0crwdne4979:0", "uptimeMonitoring": "crwdns4981:0crwdne4981:0" }, @@ -1412,7 +1413,10 @@ "here": "crwdns552:0crwdne552:0", "iconPicker": { "label": "crwdns554:0crwdne554:0", - "header": "crwdns556:0{countIcons}crwdne556:0" + "header": "crwdns556:0{countIcons}crwdne556:0", + "headerLoading": "crwdns5681:0crwdne5681:0", + "noResults": "crwdns5683:0{search}crwdne5683:0", + "error": "crwdns5685:0crwdne5685:0" }, "colorScheme": { "options": { @@ -1464,6 +1468,7 @@ "switchToDarkMode": "crwdns594:0crwdne594:0", "switchToLightMode": "crwdns596:0crwdne596:0", "management": "crwdns598:0crwdne598:0", + "docker": "crwdns5703:0crwdne5703:0", "preferences": "crwdns600:0crwdne600:0", "logout": "crwdns602:0crwdne602:0", "login": "crwdns604:0crwdne604:0", @@ -2352,6 +2357,9 @@ "name": "crwdns3424:0crwdne3424:0", "description": "crwdns3426:0crwdne3426:0", "option": { + "columns": { + "label": "crwdns5705:0crwdne5705:0" + }, "enableRowSorting": { "label": "crwdns3808:0crwdne3808:0" }, @@ -2405,6 +2413,12 @@ "title": "crwdns3876:0crwdne3876:0" } }, + "vpn": { + "name": "crwdns5689:0crwdne5689:0", + "description": "crwdns5691:0crwdne5691:0", + "serviceUnavailable": "crwdns5693:0crwdne5693:0", + "option": {} + }, "ups": { "name": "crwdns4481:0crwdne4481:0", "description": "crwdns4483:0crwdne4483:0", @@ -3639,6 +3653,9 @@ "error": { "internalServerError": "crwdns5653:0crwdne5653:0" }, + "empty": { + "noSystems": "crwdns5677:0crwdne5677:0" + }, "metric": { "cpu": "crwdns4865:0crwdne4865:0", "memory": "crwdns4867:0crwdne4867:0", @@ -3712,6 +3729,9 @@ } }, "selectSystem": "crwdns4927:0crwdne4927:0", + "empty": { + "noSystems": "crwdns5679:0crwdne5679:0" + }, "error": { "internalServerError": "crwdns5661:0crwdne5661:0", "liveConnectionFailed": "crwdns5663:0crwdne5663:0", @@ -4133,6 +4153,11 @@ "title": "crwdns1568:0crwdne1568:0", "text": "crwdns1570:0crwdne1570:0" }, + "error": { + "title": "crwdns5697:0crwdne5697:0", + "text": "crwdns5699:0crwdne5699:0", + "digest": "crwdns5701:0{digest}crwdne5701:0" + }, "navbar": { "items": { "home": "crwdns1572:0crwdne1572:0", @@ -4579,6 +4604,9 @@ }, "umamiActiveVisitors": { "label": "crwdns4223:0crwdne4223:0" + }, + "vpn": { + "label": "crwdns5695:0crwdne5695:0" } }, "interval": { @@ -5071,7 +5099,8 @@ } }, "error": { - "internalServerError": "crwdns2262:0crwdne2262:0" + "internalServerError": "crwdns2262:0crwdne2262:0", + "noColumns": "crwdns5707:0crwdne5707:0" } }, "kubernetes": { diff --git a/packages/translation/src/lang/cs.json b/packages/translation/src/lang/cs.json index 89b8949bb..c85e85bd0 100644 --- a/packages/translation/src/lang/cs.json +++ b/packages/translation/src/lang/cs.json @@ -1021,6 +1021,7 @@ "mediaMonitoring": "", "speedtest": "", "analytics": "", + "vpn": "", "mediaLibrary": "", "uptimeMonitoring": "" }, @@ -1412,7 +1413,10 @@ "here": "zde", "iconPicker": { "label": "URL ikony", - "header": "" + "header": "", + "headerLoading": "", + "noResults": "", + "error": "" }, "colorScheme": { "options": { @@ -1464,6 +1468,7 @@ "switchToDarkMode": "Přepnout do tmavého režimu", "switchToLightMode": "Přepnout do světlého režimu", "management": "Správa", + "docker": "", "preferences": "Vaše předvolby", "logout": "Odhlásit se", "login": "Přihlásit se", @@ -2352,6 +2357,9 @@ "name": "", "description": "", "option": { + "columns": { + "label": "" + }, "enableRowSorting": { "label": "" }, @@ -2405,6 +2413,12 @@ "title": "" } }, + "vpn": { + "name": "", + "description": "", + "serviceUnavailable": "", + "option": {} + }, "ups": { "name": "", "description": "", @@ -3639,6 +3653,9 @@ "error": { "internalServerError": "" }, + "empty": { + "noSystems": "" + }, "metric": { "cpu": "", "memory": "", @@ -3712,6 +3729,9 @@ } }, "selectSystem": "", + "empty": { + "noSystems": "" + }, "error": { "internalServerError": "", "liveConnectionFailed": "", @@ -4133,6 +4153,11 @@ "title": "", "text": "Nepodařilo se nalézt požadovaný zdroj" }, + "error": { + "title": "", + "text": "", + "digest": "" + }, "navbar": { "items": { "home": "Domovská stránka", @@ -4579,6 +4604,9 @@ }, "umamiActiveVisitors": { "label": "" + }, + "vpn": { + "label": "" } }, "interval": { @@ -5071,7 +5099,8 @@ } }, "error": { - "internalServerError": "" + "internalServerError": "", + "noColumns": "" } }, "kubernetes": { diff --git a/packages/translation/src/lang/da.json b/packages/translation/src/lang/da.json index d22ef9c61..36a26b4ee 100644 --- a/packages/translation/src/lang/da.json +++ b/packages/translation/src/lang/da.json @@ -1021,6 +1021,7 @@ "mediaMonitoring": "", "speedtest": "", "analytics": "", + "vpn": "", "mediaLibrary": "", "uptimeMonitoring": "" }, @@ -1412,7 +1413,10 @@ "here": "her", "iconPicker": { "label": "Ikon-URL", - "header": "Indtast navn eller objekter for at filtrere for ikoner... Homarr vil søge gennem {countIcons} ikoner for dig." + "header": "Indtast navn eller objekter for at filtrere for ikoner... Homarr vil søge gennem {countIcons} ikoner for dig.", + "headerLoading": "", + "noResults": "", + "error": "" }, "colorScheme": { "options": { @@ -1464,6 +1468,7 @@ "switchToDarkMode": "Skift til mørk tilstand", "switchToLightMode": "Skift til lys tilstand", "management": "Håndtering", + "docker": "", "preferences": "Dine indstillinger", "logout": "Log ud", "login": "Log ind", @@ -2352,6 +2357,9 @@ "name": "Docker statistik", "description": "Statistik for dine containere (Denne widget kan kun tilføjes med administratorrettigheder)", "option": { + "columns": { + "label": "" + }, "enableRowSorting": { "label": "Aktiver sortering" }, @@ -2405,6 +2413,12 @@ "title": "Instanser" } }, + "vpn": { + "name": "", + "description": "", + "serviceUnavailable": "", + "option": {} + }, "ups": { "name": "", "description": "", @@ -3639,6 +3653,9 @@ "error": { "internalServerError": "" }, + "empty": { + "noSystems": "" + }, "metric": { "cpu": "", "memory": "", @@ -3712,6 +3729,9 @@ } }, "selectSystem": "", + "empty": { + "noSystems": "" + }, "error": { "internalServerError": "", "liveConnectionFailed": "", @@ -4133,6 +4153,11 @@ "title": "Ikke Fundet", "text": "Kunne ikke finde den anmodede ressource" }, + "error": { + "title": "", + "text": "", + "digest": "" + }, "navbar": { "items": { "home": "Hjem", @@ -4579,6 +4604,9 @@ }, "umamiActiveVisitors": { "label": "" + }, + "vpn": { + "label": "" } }, "interval": { @@ -5071,7 +5099,8 @@ } }, "error": { - "internalServerError": "Kunne ikke hente Docker-containere" + "internalServerError": "Kunne ikke hente Docker-containere", + "noColumns": "" } }, "kubernetes": { diff --git a/packages/translation/src/lang/de-CH.json b/packages/translation/src/lang/de-CH.json index bbe34e1c0..845a115cb 100644 --- a/packages/translation/src/lang/de-CH.json +++ b/packages/translation/src/lang/de-CH.json @@ -1021,6 +1021,7 @@ "mediaMonitoring": "", "speedtest": "", "analytics": "", + "vpn": "", "mediaLibrary": "", "uptimeMonitoring": "" }, @@ -1412,7 +1413,10 @@ "here": "Hier", "iconPicker": { "label": "", - "header": "Geben Sie Namen oder Objekte ein, um nach Symbolen zu filtern... Homarr sucht nach {countIcons} Icons für Sie." + "header": "Geben Sie Namen oder Objekte ein, um nach Symbolen zu filtern... Homarr sucht nach {countIcons} Icons für Sie.", + "headerLoading": "", + "noResults": "", + "error": "" }, "colorScheme": { "options": { @@ -1464,6 +1468,7 @@ "switchToDarkMode": "Zum dunklen Design wechseln", "switchToLightMode": "Zum hellen Design wechseln", "management": "Verwaltung", + "docker": "", "preferences": "Ihre Einstellungen", "logout": "Abmelden", "login": "Anmelden", @@ -2352,6 +2357,9 @@ "name": "", "description": "", "option": { + "columns": { + "label": "" + }, "enableRowSorting": { "label": "" }, @@ -2405,6 +2413,12 @@ "title": "" } }, + "vpn": { + "name": "", + "description": "", + "serviceUnavailable": "", + "option": {} + }, "ups": { "name": "", "description": "", @@ -3639,6 +3653,9 @@ "error": { "internalServerError": "" }, + "empty": { + "noSystems": "" + }, "metric": { "cpu": "", "memory": "", @@ -3712,6 +3729,9 @@ } }, "selectSystem": "", + "empty": { + "noSystems": "" + }, "error": { "internalServerError": "", "liveConnectionFailed": "", @@ -4133,6 +4153,11 @@ "title": "Nicht gefunden", "text": "Konnte die angeforderte Ressource nicht finden" }, + "error": { + "title": "", + "text": "", + "digest": "" + }, "navbar": { "items": { "home": "Startseite", @@ -4579,6 +4604,9 @@ }, "umamiActiveVisitors": { "label": "" + }, + "vpn": { + "label": "" } }, "interval": { @@ -5071,7 +5099,8 @@ } }, "error": { - "internalServerError": "Fehler beim Abrufen der Docker Container" + "internalServerError": "Fehler beim Abrufen der Docker Container", + "noColumns": "" } }, "kubernetes": { diff --git a/packages/translation/src/lang/de.json b/packages/translation/src/lang/de.json index 09d4a4ce2..9c64d783f 100644 --- a/packages/translation/src/lang/de.json +++ b/packages/translation/src/lang/de.json @@ -1021,6 +1021,7 @@ "mediaMonitoring": "Überwachung", "speedtest": "Geschwindigkeitstest", "analytics": "Nutzungsdaten", + "vpn": "", "mediaLibrary": "Medienbibliothek", "uptimeMonitoring": "Überwachung der Betriebszeit" }, @@ -1412,7 +1413,10 @@ "here": "Hier", "iconPicker": { "label": "Icon URL", - "header": "Geben Sie Namen oder Objekte ein, um nach Symbolen zu filtern... Homarr sucht nach {countIcons} Icons für Sie." + "header": "Geben Sie Namen oder Objekte ein, um nach Symbolen zu filtern... Homarr sucht nach {countIcons} Icons für Sie.", + "headerLoading": "Tippe um die Symbole zu durchsuchen…", + "noResults": "Keine Symbole stimmen mit \"{search}\" überein.", + "error": "Konnte Symbole nicht laden." }, "colorScheme": { "options": { @@ -1464,6 +1468,7 @@ "switchToDarkMode": "Zum dunklen Design wechseln", "switchToLightMode": "Zum hellen Design wechseln", "management": "Verwaltung", + "docker": "", "preferences": "Ihre Einstellungen", "logout": "Abmelden", "login": "Anmelden", @@ -2352,6 +2357,9 @@ "name": "Docker Statistiken", "description": "Statistiken Ihrer Container (Dieses Widget kann nur mit Administratorrechten hinzugefügt werden)", "option": { + "columns": { + "label": "" + }, "enableRowSorting": { "label": "Sortierung von Elementen aktivieren" }, @@ -2405,6 +2413,12 @@ "title": "Instanzen" } }, + "vpn": { + "name": "", + "description": "", + "serviceUnavailable": "", + "option": {} + }, "ups": { "name": "UPS", "description": "Überwachen Sie den Status Ihrer UPS Geräte über einen NUT Server", @@ -3395,53 +3409,53 @@ }, "showNowPlaying": { "label": "\"Sie hören\" anzeigen", - "description": "" + "description": "Zeige aktuell gespielte Titel an (Navidrome)" }, "showNowPlayingList": { - "label": "", - "description": "" + "label": "\"Sie hören jetzt\" Wiedergabeliste anzeigen", + "description": "Einzelne Tracks anstelle der aktiven Streamanzahl anzeigen (Navidrome)" }, "maxNowPlayingItems": { - "label": "", - "description": "" + "label": "Maximale jetzt abgespielte Elemente", + "description": "Maximale Anzahl an Titeln, die in der \"Sie hören jetzt\" Wiedergabeliste angezeigt werden (Navidrome)" }, "showLibraryCount": { - "label": "", - "description": "" + "label": "Anzahl der Bibliotheken anzeigen", + "description": "Zeige die Anzahl der Bibliotheken in deiner Instanz (Hörbücherregal)" }, "showAudiobooks": { - "label": "", - "description": "" + "label": "Hörbücher anzeigen", + "description": "Zeige die Gesamtzahl der Hörbücher in allen Bibliotheken (Hörbücherregal)" }, "showPodcasts": { - "label": "", - "description": "" + "label": "Podcasts anzeigen", + "description": "Zeige die Gesamtzahl der Podcasts in allen Bibliotheken (Hörbücherregal)" }, "showListeningTime": { - "label": "", + "label": "Hördauer anzeigen", "description": "Zeigt deine gesamte Hörzeit (Audiobookshelf)" }, "showActiveSessions": { "label": "Aktive Streams anzeigen", - "description": "" + "description": "Zeige die Anzahl der aktuell aktiven Wiedergabesitzungen (Hörbücherregal)" }, "compactMode": { - "label": "", - "description": "" + "label": "Kompakter Modus", + "description": "Verwenden Sie ein dichteres Layout mit kleineren Kacheln, ideal für kleine Widget-Größen" } }, - "artists": "", - "albums": "", - "songs": "", - "libraryCount": "", - "audiobooks": "", - "podcasts": "", - "listeningTime": "", - "activeSessions": "", - "nowPlaying": "", - "nowPlayingCount": "", + "artists": "Künstler", + "albums": "Alben", + "songs": "Musik", + "libraryCount": "Bibliotheken", + "audiobooks": "Hörbücher", + "podcasts": "Podcasts", + "listeningTime": "Hördauer", + "activeSessions": "Aktive Sitzungen", + "nowPlaying": "Aktuelle Wiedergabe", + "nowPlayingCount": "{count, plural, one {# stream} other {# streams}}", "error": { - "internalServerError": "" + "internalServerError": "Fehler beim Abrufen der Audio-Statistikdaten" } }, "systemResources": { @@ -3457,7 +3471,7 @@ "option": { "cpu": "CPU", "memory": "Speicher", - "gpu": "", + "gpu": "GPU", "network": "Netzwerk" } }, @@ -3474,7 +3488,7 @@ "card": { "cpu": "CPU", "memory": "Arbeitsspeicher", - "gpu": "", + "gpu": "GPU", "network": "Netzwerk", "up": "OBEN", "down": "UNTEN" @@ -3510,93 +3524,93 @@ } }, "customApi": { - "name": "", - "description": "", + "name": "Benutzerdefinierte API", + "description": "Zeigt Daten von jeder externen API mit benutzerdefinierter Konfiguration an.", "option": { "definitionId": { - "label": "" + "label": "Definitions ID" }, "refreshInterval": { - "label": "", - "description": "" + "label": "Aktualisierungsintervall", + "description": "Wie oft Daten von der API neu geladen werden sollen (in Sekunden)" } }, - "noDefinition": "", - "definitionNotFound": "", - "noDisplayOutput": "", - "executeConfirm": "", - "executing": "", - "executeSuccess": "", - "executeFailed": "" + "noDefinition": "Keine Definition ausgewählt", + "definitionNotFound": "Widget-Definition nicht gefunden. Möglicherweise wurde sie gelöscht.", + "noDisplayOutput": "Keine Anzeige-Ausgabe", + "executeConfirm": "Bist du dir sicher, dass du diese Aktion ausführen möchtest?", + "executing": "Wird ausgeführt...", + "executeSuccess": "Aktion erfolgreich ausgeführt", + "executeFailed": "Aktion fehlgeschlagen. Überprüfen Sie die Widget-Konfiguration." }, "beszelSystemTable": { - "name": "", - "description": "", + "name": "Beszel Systeme (Tabelle)", + "description": "Tabellenansicht aller Beszel-überwachten Systeme mit sortierbaren Spalten und Statusindikatoren", "option": { "statusFilter": { - "label": "" + "label": "Status Filter" }, "sortBy": { - "label": "" + "label": "Sortieren nach" }, "sortDirection": { - "label": "" + "label": "Sortierreihenfolge" }, "showCpu": { - "label": "" + "label": "CPU anzeigen" }, "showMemory": { - "label": "" + "label": "Arbeitsspeicher anzeigen" }, "showDisk": { - "label": "" + "label": "Datenträger anzeigen" }, "showGpu": { - "label": "" + "label": "GPU anzeigen" }, "showLoadAvg": { - "label": "" + "label": "Zeige Belastungsdurchschnitt" }, "showNet": { - "label": "" + "label": "Netzwerk anzeigen" }, "showTemp": { - "label": "" + "label": "Temperatur anzeigen" }, "showBattery": { - "label": "" + "label": "Akkustand anzeigen" }, "showServices": { - "label": "" + "label": "Dienste anzeigen" }, "showUptime": { - "label": "" + "label": "Laufzeit anzeigen" }, "showAgent": { - "label": "" + "label": "Agent-Version anzeigen" } }, - "noRecords": "", + "noRecords": "Keine Systeme gefunden", "error": { - "internalServerError": "" + "internalServerError": "Keine Verbindung zu Beszel. Überprüfen Sie Ihre Integrationseinstellungen." }, "column": { - "system": "", - "cpu": "", - "memory": "", - "disk": "", - "gpu": "", - "loadAvg": "", - "net": "", - "temp": "", - "battery": "", - "services": "", - "uptime": "", - "agent": "" + "system": "System", + "cpu": "CPU", + "memory": "Arbeitsspeicher", + "disk": "Datenträger", + "gpu": "GPU", + "loadAvg": "Systemlast", + "net": "Netz", + "temp": "Temperatur", + "battery": "Batterie", + "services": "Dienste", + "uptime": "Betriebszeit", + "agent": "Agent" } }, "beszelSystemGrid": { - "name": "", + "name": "Beszel Systems (Grid)", "description": "", "option": { "statusFilter": { @@ -3639,6 +3653,9 @@ "error": { "internalServerError": "" }, + "empty": { + "noSystems": "" + }, "metric": { "cpu": "CPU", "memory": "Arbeitsspeicher", @@ -3712,6 +3729,9 @@ } }, "selectSystem": "System auswählen", + "empty": { + "noSystems": "" + }, "error": { "internalServerError": "", "liveConnectionFailed": "", @@ -4133,6 +4153,11 @@ "title": "Nicht gefunden", "text": "Konnte die angeforderte Ressource nicht finden" }, + "error": { + "title": "", + "text": "", + "digest": "" + }, "navbar": { "items": { "home": "Startseite", @@ -4579,6 +4604,9 @@ }, "umamiActiveVisitors": { "label": "" + }, + "vpn": { + "label": "" } }, "interval": { @@ -5071,7 +5099,8 @@ } }, "error": { - "internalServerError": "Fehler beim Abrufen der Docker Container" + "internalServerError": "Fehler beim Abrufen der Docker Container", + "noColumns": "" } }, "kubernetes": { diff --git a/packages/translation/src/lang/el.json b/packages/translation/src/lang/el.json index 168501668..b10b63682 100644 --- a/packages/translation/src/lang/el.json +++ b/packages/translation/src/lang/el.json @@ -1021,6 +1021,7 @@ "mediaMonitoring": "", "speedtest": "", "analytics": "", + "vpn": "", "mediaLibrary": "", "uptimeMonitoring": "" }, @@ -1412,7 +1413,10 @@ "here": "εδώ", "iconPicker": { "label": "", - "header": "" + "header": "", + "headerLoading": "", + "noResults": "", + "error": "" }, "colorScheme": { "options": { @@ -1464,6 +1468,7 @@ "switchToDarkMode": "Εναλλαγή σε σκοτεινή λειτουργία", "switchToLightMode": "Εναλλαγή σε φωτεινή λειτουργία", "management": "Διαχείριση", + "docker": "", "preferences": "Οι ρυθμίσεις σας", "logout": "Αποσύνδεση", "login": "Σύνδεση", @@ -2352,6 +2357,9 @@ "name": "Στατιστικά Docker", "description": "", "option": { + "columns": { + "label": "" + }, "enableRowSorting": { "label": "" }, @@ -2405,6 +2413,12 @@ "title": "" } }, + "vpn": { + "name": "", + "description": "", + "serviceUnavailable": "", + "option": {} + }, "ups": { "name": "", "description": "", @@ -3639,6 +3653,9 @@ "error": { "internalServerError": "" }, + "empty": { + "noSystems": "" + }, "metric": { "cpu": "", "memory": "", @@ -3712,6 +3729,9 @@ } }, "selectSystem": "", + "empty": { + "noSystems": "" + }, "error": { "internalServerError": "", "liveConnectionFailed": "", @@ -4133,6 +4153,11 @@ "title": "", "text": "" }, + "error": { + "title": "", + "text": "", + "digest": "" + }, "navbar": { "items": { "home": "Αρχική", @@ -4579,6 +4604,9 @@ }, "umamiActiveVisitors": { "label": "" + }, + "vpn": { + "label": "" } }, "interval": { @@ -5071,7 +5099,8 @@ } }, "error": { - "internalServerError": "" + "internalServerError": "", + "noColumns": "" } }, "kubernetes": { diff --git a/packages/translation/src/lang/en-gb.json b/packages/translation/src/lang/en-gb.json index ff08eecf7..747a48a47 100644 --- a/packages/translation/src/lang/en-gb.json +++ b/packages/translation/src/lang/en-gb.json @@ -1021,6 +1021,7 @@ "mediaMonitoring": "", "speedtest": "", "analytics": "", + "vpn": "", "mediaLibrary": "", "uptimeMonitoring": "" }, @@ -1412,7 +1413,10 @@ "here": "here", "iconPicker": { "label": "Icon URL", - "header": "Please enter a name or object to filter for the icon. Homarr will search throughout {countIcons} different icons to find the right one." + "header": "Please enter a name or object to filter for the icon. Homarr will search throughout {countIcons} different icons to find the right one.", + "headerLoading": "", + "noResults": "", + "error": "" }, "colorScheme": { "options": { @@ -1464,6 +1468,7 @@ "switchToDarkMode": "Switch to dark mode", "switchToLightMode": "Switch to light mode", "management": "Management", + "docker": "", "preferences": "Your preferences", "logout": "Log-out", "login": "Log-in", @@ -2352,6 +2357,9 @@ "name": "Docker stats", "description": "Stats of your containers (This widget can only be added with administrator privileges)", "option": { + "columns": { + "label": "" + }, "enableRowSorting": { "label": "Enable items sorting" }, @@ -2405,6 +2413,12 @@ "title": "Instances" } }, + "vpn": { + "name": "", + "description": "", + "serviceUnavailable": "", + "option": {} + }, "ups": { "name": "", "description": "", @@ -3639,6 +3653,9 @@ "error": { "internalServerError": "" }, + "empty": { + "noSystems": "" + }, "metric": { "cpu": "", "memory": "", @@ -3712,6 +3729,9 @@ } }, "selectSystem": "", + "empty": { + "noSystems": "" + }, "error": { "internalServerError": "", "liveConnectionFailed": "", @@ -4133,6 +4153,11 @@ "title": "", "text": "" }, + "error": { + "title": "", + "text": "", + "digest": "" + }, "navbar": { "items": { "home": "", @@ -4579,6 +4604,9 @@ }, "umamiActiveVisitors": { "label": "" + }, + "vpn": { + "label": "" } }, "interval": { @@ -5071,7 +5099,8 @@ } }, "error": { - "internalServerError": "" + "internalServerError": "", + "noColumns": "" } }, "kubernetes": { diff --git a/packages/translation/src/lang/en.json b/packages/translation/src/lang/en.json index 190a13733..f4cbf6f98 100644 --- a/packages/translation/src/lang/en.json +++ b/packages/translation/src/lang/en.json @@ -1598,7 +1598,10 @@ }, "menu": { "label": { - "settings": "Settings" + "settings": "Settings", + "refresh": "Refresh", + "options": "Options", + "actions": "Actions" } }, "create": { @@ -1900,6 +1903,9 @@ "title": "Departures {station}", "noStation": "No station selected", "option": { + "baseUrl": { + "label": "Search.ch URL" + }, "station": { "label": "Station" } @@ -2357,6 +2363,9 @@ "name": "Docker stats", "description": "Stats of your containers (This widget can only be added with administrator privileges)", "option": { + "columns": { + "label": "Columns to show" + }, "enableRowSorting": { "label": "Enable items sorting" }, @@ -2770,8 +2779,8 @@ "name": "Immich Album", "option": { "albumId": { - "label": "Album ID", - "description": "This is a unique ID which refers to your Immich album. You can find this id by opening an album and copying the unique string from the URL, for example 'a21aca09-9f05-47dc-b1a1-7b53c547226e'" + "label": "Album", + "description": "Select the Immich album to show in the slideshow." }, "rotationIntervalSeconds": { "label": "Interval in seconds", @@ -2780,6 +2789,10 @@ "showPhotoInfo": { "label": "Show photo info", "description": "Display date of the picture and the current index at the bottom of the slideshow" + }, + "randomizePhotos": { + "label": "Randomize photos", + "description": "Show photos in a random order instead of always starting with the same album order." } }, "description": "Slideshow of pictures in your Immich album", @@ -3144,6 +3157,9 @@ "provider": { "label": "Provider" }, + "providerUrl": { + "label": "Provider URL" + }, "identifier": { "label": "Identifier", "placeholder": "Name or Owner/Name" @@ -3191,6 +3207,13 @@ "noProvider": { "label": "No Provider", "tooltip": "The provider could not be parsed, please manually set it after importing the images" + }, + "tokens": { + "label": "Provider Tokens", + "token": "Token", + "configured": "Token configured", + "notConfigured": "No token set", + "save": "Save" } } }, @@ -3213,8 +3236,9 @@ "invalidIdentifier": "Invalid identifier", "noMatchingVersion": "No matching version found", "noReleasesFound": "No releases found", - "noProviderSeleceted": "No provider selected", - "noProviderResponse": "No response from provider" + "noProviderSelected": "No provider selected", + "noProviderResponse": "No response from provider", + "unexpected": "Unexpected provider error" } } }, @@ -4308,7 +4332,7 @@ "notStarted": "Not started" }, "name": { -"management": "Management", + "management": "Management", "dashboard": "Dashboard" } } @@ -4521,89 +4545,14 @@ "disabled": "Disabled" }, "job": { - "minecraftServerStatus": { - "label": "Minecraft server status" - }, - "timetable": { - "label": "Timetable" - }, "iconsUpdater": { "label": "Icons Updater" }, "analytics": { "label": "Analytics" }, - "smartHomeEntityState": { - "label": "Smart Home Entity State" - }, "ping": { "label": "Pings" - }, - "mediaServer": { - "label": "Media Server" - }, - "mediaOrganizer": { - "label": "Media Organizers" - }, - "downloads": { - "label": "Downloads" - }, - "mediaRequestStats": { - "label": "Media Request Stats" - }, - "mediaRequestList": { - "label": "Media Request List" - }, - "rssFeeds": { - "label": "RSS feeds" - }, - "indexerManager": { - "label": "Indexer Manager" - }, - "healthMonitoring": { - "label": "Health Monitoring" - }, - "dnsHole": { - "label": "DNS Hole Data" - }, - "updateChecker": { - "label": "Update checker" - }, - "mediaTranscoding": { - "label": "Media transcoding" - }, - "networkController": { - "label": "Network Controller" - }, - "refreshNotifications": { - "label": "Notification Updater" - }, - "dockerContainers": { - "label": "Docker containers" - }, - "firewallCpu": { - "label": "Firewall CPU" - }, - "firewallMemory": { - "label": "Firewall Memory" - }, - "firewallVersion": { - "label": "Firewall Version" - }, - "firewallInterfaces": { - "label": "Firewall Interfaces" - }, - "weather": { - "label": "Weather" - }, - "tracearr": { - "label": "Tracearr" - }, - "umamiActiveVisitors": { - "label": "Umami Active Visitors" - }, - "vpn": { - "label": "VPN Status" } }, "interval": { @@ -5096,7 +5045,8 @@ } }, "error": { - "internalServerError": "Failed to fetch Docker containers" + "internalServerError": "Failed to fetch Docker containers", + "noColumns": "Select at least one column in the widget settings" } }, "kubernetes": { diff --git a/packages/translation/src/lang/es.json b/packages/translation/src/lang/es.json index 7858cc262..89979a403 100644 --- a/packages/translation/src/lang/es.json +++ b/packages/translation/src/lang/es.json @@ -1021,6 +1021,7 @@ "mediaMonitoring": "", "speedtest": "", "analytics": "", + "vpn": "", "mediaLibrary": "", "uptimeMonitoring": "" }, @@ -1412,7 +1413,10 @@ "here": "aquí", "iconPicker": { "label": "URL del icono", - "header": "Escribe nombre u objetos para filtrar iconos... Homarr buscará a través de iconos {countIcons} por ti." + "header": "Escribe nombre u objetos para filtrar iconos... Homarr buscará a través de iconos {countIcons} por ti.", + "headerLoading": "", + "noResults": "", + "error": "" }, "colorScheme": { "options": { @@ -1464,6 +1468,7 @@ "switchToDarkMode": "Cambiar a modo oscuro", "switchToLightMode": "Cambiar a modo claro", "management": "Administración", + "docker": "", "preferences": "Tus preferencias", "logout": "Cerrar sesión", "login": "Iniciar sesión", @@ -2352,6 +2357,9 @@ "name": "Estadísticas de Docker", "description": "Estadísticas de tus contenedores (Este widget sólo puede ser añadido con privilegios de administrador)", "option": { + "columns": { + "label": "" + }, "enableRowSorting": { "label": "Habilitar ordenar elementos" }, @@ -2405,6 +2413,12 @@ "title": "Instancias" } }, + "vpn": { + "name": "", + "description": "", + "serviceUnavailable": "", + "option": {} + }, "ups": { "name": "", "description": "", @@ -3639,6 +3653,9 @@ "error": { "internalServerError": "" }, + "empty": { + "noSystems": "" + }, "metric": { "cpu": "", "memory": "", @@ -3712,6 +3729,9 @@ } }, "selectSystem": "", + "empty": { + "noSystems": "" + }, "error": { "internalServerError": "", "liveConnectionFailed": "", @@ -4133,6 +4153,11 @@ "title": "No Encontrado", "text": "No se pudo encontrar el recurso solicitado" }, + "error": { + "title": "", + "text": "", + "digest": "" + }, "navbar": { "items": { "home": "Inicio", @@ -4579,6 +4604,9 @@ }, "umamiActiveVisitors": { "label": "" + }, + "vpn": { + "label": "" } }, "interval": { @@ -5071,7 +5099,8 @@ } }, "error": { - "internalServerError": "Fallo al obtener los contenedores de Docker" + "internalServerError": "Fallo al obtener los contenedores de Docker", + "noColumns": "" } }, "kubernetes": { diff --git a/packages/translation/src/lang/et.json b/packages/translation/src/lang/et.json index 606d4833f..4b07e023f 100644 --- a/packages/translation/src/lang/et.json +++ b/packages/translation/src/lang/et.json @@ -1021,6 +1021,7 @@ "mediaMonitoring": "", "speedtest": "", "analytics": "", + "vpn": "", "mediaLibrary": "", "uptimeMonitoring": "" }, @@ -1412,7 +1413,10 @@ "here": "", "iconPicker": { "label": "", - "header": "" + "header": "", + "headerLoading": "", + "noResults": "", + "error": "" }, "colorScheme": { "options": { @@ -1464,6 +1468,7 @@ "switchToDarkMode": "", "switchToLightMode": "", "management": "", + "docker": "", "preferences": "Teie eelistused", "logout": "", "login": "", @@ -2352,6 +2357,9 @@ "name": "", "description": "", "option": { + "columns": { + "label": "" + }, "enableRowSorting": { "label": "" }, @@ -2405,6 +2413,12 @@ "title": "" } }, + "vpn": { + "name": "", + "description": "", + "serviceUnavailable": "", + "option": {} + }, "ups": { "name": "", "description": "", @@ -3639,6 +3653,9 @@ "error": { "internalServerError": "" }, + "empty": { + "noSystems": "" + }, "metric": { "cpu": "", "memory": "", @@ -3712,6 +3729,9 @@ } }, "selectSystem": "", + "empty": { + "noSystems": "" + }, "error": { "internalServerError": "", "liveConnectionFailed": "", @@ -4133,6 +4153,11 @@ "title": "", "text": "" }, + "error": { + "title": "", + "text": "", + "digest": "" + }, "navbar": { "items": { "home": "", @@ -4579,6 +4604,9 @@ }, "umamiActiveVisitors": { "label": "" + }, + "vpn": { + "label": "" } }, "interval": { @@ -5071,7 +5099,8 @@ } }, "error": { - "internalServerError": "" + "internalServerError": "", + "noColumns": "" } }, "kubernetes": { diff --git a/packages/translation/src/lang/fi.json b/packages/translation/src/lang/fi.json index 713c729b1..281b175cf 100644 --- a/packages/translation/src/lang/fi.json +++ b/packages/translation/src/lang/fi.json @@ -1021,6 +1021,7 @@ "mediaMonitoring": "Valvonta", "speedtest": "Nopeustestaus", "analytics": "Analytiikka", + "vpn": "", "mediaLibrary": "", "uptimeMonitoring": "" }, @@ -1044,7 +1045,7 @@ "description": "Vastausta ei voitu jäsentää. Tarkista, että URL-osoite osoittaa palvelun perusosoitteeseen." }, "authorization": { - "title": "Tuonnistautumisvirhe", + "title": "Tunnistautumisvirhe", "description": "Pyyntöä ei hyväksytty. Varmista, että kirjautumistiedot ovat oikein ja että niille on määritetty riittävät käyttöoikeudet." }, "statusCode": { @@ -1412,7 +1413,10 @@ "here": "", "iconPicker": { "label": "Kuvakkeen URL-osoite", - "header": "Suodata kuvakkeita syöttämällä nimi tai objekti... Homarr etsii {countIcons} kuvakkeesta." + "header": "Suodata kuvakkeita syöttämällä nimi tai objekti... Homarr etsii {countIcons} kuvakkeesta.", + "headerLoading": "", + "noResults": "", + "error": "" }, "colorScheme": { "options": { @@ -1464,6 +1468,7 @@ "switchToDarkMode": "", "switchToLightMode": "", "management": "Hallinta", + "docker": "", "preferences": "Asetuksesi", "logout": "Kirjaudu ulos", "login": "", @@ -2352,6 +2357,9 @@ "name": "Docker-tilastot", "description": "Konttiesi tilastot (tämä widget voidaan lisätä vain ylläpito-oikeuksilla)", "option": { + "columns": { + "label": "" + }, "enableRowSorting": { "label": "Salli kohteiden järjestys" }, @@ -2405,6 +2413,12 @@ "title": "" } }, + "vpn": { + "name": "", + "description": "", + "serviceUnavailable": "", + "option": {} + }, "ups": { "name": "", "description": "", @@ -3639,6 +3653,9 @@ "error": { "internalServerError": "" }, + "empty": { + "noSystems": "" + }, "metric": { "cpu": "", "memory": "", @@ -3712,6 +3729,9 @@ } }, "selectSystem": "", + "empty": { + "noSystems": "" + }, "error": { "internalServerError": "", "liveConnectionFailed": "", @@ -4133,6 +4153,11 @@ "title": "", "text": "" }, + "error": { + "title": "", + "text": "", + "digest": "" + }, "navbar": { "items": { "home": "Aloitus", @@ -4579,6 +4604,9 @@ }, "umamiActiveVisitors": { "label": "Umamin aktiiviset käyttäjät" + }, + "vpn": { + "label": "" } }, "interval": { @@ -5071,7 +5099,8 @@ } }, "error": { - "internalServerError": "" + "internalServerError": "", + "noColumns": "" } }, "kubernetes": { diff --git a/packages/translation/src/lang/fr.json b/packages/translation/src/lang/fr.json index 25b85fe71..7c75cb650 100644 --- a/packages/translation/src/lang/fr.json +++ b/packages/translation/src/lang/fr.json @@ -1021,6 +1021,7 @@ "mediaMonitoring": "", "speedtest": "", "analytics": "", + "vpn": "", "mediaLibrary": "", "uptimeMonitoring": "" }, @@ -1412,7 +1413,10 @@ "here": "ici", "iconPicker": { "label": "URL de l'icône :", - "header": "Tapez le nom ou les objets pour filtrer les icônes... Homarr recherchera les icônes {countIcons} pour vous." + "header": "Tapez le nom ou les objets pour filtrer les icônes... Homarr recherchera les icônes {countIcons} pour vous.", + "headerLoading": "", + "noResults": "", + "error": "" }, "colorScheme": { "options": { @@ -1464,6 +1468,7 @@ "switchToDarkMode": "Activer le mode sombre", "switchToLightMode": "Activer le mode clair", "management": "Administration", + "docker": "", "preferences": "Vos préférences", "logout": "Se déconnecter", "login": "Connexion", @@ -2352,6 +2357,9 @@ "name": "Statistiques de Docker", "description": "Statistiques de vos conteneurs (Ce widget ne peut être ajouté qu'avec les privilèges d'administrateur)", "option": { + "columns": { + "label": "" + }, "enableRowSorting": { "label": "Activer le tri des éléments" }, @@ -2405,6 +2413,12 @@ "title": "Instances" } }, + "vpn": { + "name": "", + "description": "", + "serviceUnavailable": "", + "option": {} + }, "ups": { "name": "", "description": "", @@ -3639,6 +3653,9 @@ "error": { "internalServerError": "" }, + "empty": { + "noSystems": "" + }, "metric": { "cpu": "", "memory": "", @@ -3712,6 +3729,9 @@ } }, "selectSystem": "", + "empty": { + "noSystems": "" + }, "error": { "internalServerError": "", "liveConnectionFailed": "", @@ -4133,6 +4153,11 @@ "title": "Non trouvé", "text": "Impossible de trouver la ressource demandée" }, + "error": { + "title": "", + "text": "", + "digest": "" + }, "navbar": { "items": { "home": "Accueil", @@ -4579,6 +4604,9 @@ }, "umamiActiveVisitors": { "label": "" + }, + "vpn": { + "label": "" } }, "interval": { @@ -5071,7 +5099,8 @@ } }, "error": { - "internalServerError": "Impossible de récupérer les conteneurs Docker" + "internalServerError": "Impossible de récupérer les conteneurs Docker", + "noColumns": "" } }, "kubernetes": { diff --git a/packages/translation/src/lang/he.json b/packages/translation/src/lang/he.json index 5ecba1a8c..a54280882 100644 --- a/packages/translation/src/lang/he.json +++ b/packages/translation/src/lang/he.json @@ -1021,6 +1021,7 @@ "mediaMonitoring": "", "speedtest": "", "analytics": "", + "vpn": "", "mediaLibrary": "", "uptimeMonitoring": "" }, @@ -1412,7 +1413,10 @@ "here": "כאן", "iconPicker": { "label": "קישור של הסמל", - "header": "הקלד שם או אובייקטים לסינון עבור אייקונים... Homarr יחפש עבורך סמלים של {countIcons} ." + "header": "הקלד שם או אובייקטים לסינון עבור אייקונים... Homarr יחפש עבורך סמלים של {countIcons} .", + "headerLoading": "", + "noResults": "", + "error": "" }, "colorScheme": { "options": { @@ -1464,6 +1468,7 @@ "switchToDarkMode": "עבור למצב כהה", "switchToLightMode": "עבור למצב בהיר", "management": "ניהול", + "docker": "", "preferences": "העדפות שלך", "logout": "התנתק/י", "login": "התחבר/י", @@ -2352,6 +2357,9 @@ "name": "סטטיסטיקות דוקר", "description": "סטטיסטיקות של המכולות שלך (ניתן להוסיף ווידג'ט זה רק עם הרשאות מנהל)", "option": { + "columns": { + "label": "" + }, "enableRowSorting": { "label": "אפשר מיון פריטים" }, @@ -2405,6 +2413,12 @@ "title": "" } }, + "vpn": { + "name": "", + "description": "", + "serviceUnavailable": "", + "option": {} + }, "ups": { "name": "", "description": "", @@ -3639,6 +3653,9 @@ "error": { "internalServerError": "" }, + "empty": { + "noSystems": "" + }, "metric": { "cpu": "", "memory": "", @@ -3712,6 +3729,9 @@ } }, "selectSystem": "", + "empty": { + "noSystems": "" + }, "error": { "internalServerError": "", "liveConnectionFailed": "", @@ -4133,6 +4153,11 @@ "title": "לא נמצא", "text": "לא ניתן לאתר את המשאב המבוקש" }, + "error": { + "title": "", + "text": "", + "digest": "" + }, "navbar": { "items": { "home": "מסך הבית", @@ -4579,6 +4604,9 @@ }, "umamiActiveVisitors": { "label": "" + }, + "vpn": { + "label": "" } }, "interval": { @@ -5071,7 +5099,8 @@ } }, "error": { - "internalServerError": "נכשל בקבלת קונטיינרי דוקר" + "internalServerError": "נכשל בקבלת קונטיינרי דוקר", + "noColumns": "" } }, "kubernetes": { diff --git a/packages/translation/src/lang/hr.json b/packages/translation/src/lang/hr.json index 1f7d5013b..077625fd6 100644 --- a/packages/translation/src/lang/hr.json +++ b/packages/translation/src/lang/hr.json @@ -1021,6 +1021,7 @@ "mediaMonitoring": "", "speedtest": "", "analytics": "", + "vpn": "", "mediaLibrary": "", "uptimeMonitoring": "" }, @@ -1412,7 +1413,10 @@ "here": "", "iconPicker": { "label": "", - "header": "" + "header": "", + "headerLoading": "", + "noResults": "", + "error": "" }, "colorScheme": { "options": { @@ -1464,6 +1468,7 @@ "switchToDarkMode": "", "switchToLightMode": "", "management": "", + "docker": "", "preferences": "Vaše preferencije", "logout": "", "login": "Prijaviti se", @@ -2352,6 +2357,9 @@ "name": "", "description": "", "option": { + "columns": { + "label": "" + }, "enableRowSorting": { "label": "" }, @@ -2405,6 +2413,12 @@ "title": "" } }, + "vpn": { + "name": "", + "description": "", + "serviceUnavailable": "", + "option": {} + }, "ups": { "name": "", "description": "", @@ -3639,6 +3653,9 @@ "error": { "internalServerError": "" }, + "empty": { + "noSystems": "" + }, "metric": { "cpu": "", "memory": "", @@ -3712,6 +3729,9 @@ } }, "selectSystem": "", + "empty": { + "noSystems": "" + }, "error": { "internalServerError": "", "liveConnectionFailed": "", @@ -4133,6 +4153,11 @@ "title": "", "text": "" }, + "error": { + "title": "", + "text": "", + "digest": "" + }, "navbar": { "items": { "home": "Dom", @@ -4579,6 +4604,9 @@ }, "umamiActiveVisitors": { "label": "" + }, + "vpn": { + "label": "" } }, "interval": { @@ -5071,7 +5099,8 @@ } }, "error": { - "internalServerError": "" + "internalServerError": "", + "noColumns": "" } }, "kubernetes": { diff --git a/packages/translation/src/lang/hu.json b/packages/translation/src/lang/hu.json index 1c8d473d7..9d3094f4f 100644 --- a/packages/translation/src/lang/hu.json +++ b/packages/translation/src/lang/hu.json @@ -1021,6 +1021,7 @@ "mediaMonitoring": "", "speedtest": "", "analytics": "", + "vpn": "", "mediaLibrary": "", "uptimeMonitoring": "" }, @@ -1412,7 +1413,10 @@ "here": "itt", "iconPicker": { "label": "Ikonhivatkozás", - "header": "Írjon be nevet vagy objektumokat az ikonok szűréséhez... A Homarr a {countIcons} ikonok között keres." + "header": "Írjon be nevet vagy objektumokat az ikonok szűréséhez... A Homarr a {countIcons} ikonok között keres.", + "headerLoading": "", + "noResults": "", + "error": "" }, "colorScheme": { "options": { @@ -1464,6 +1468,7 @@ "switchToDarkMode": "Váltás sötét módba", "switchToLightMode": "Váltás világos módra", "management": "Kezelés", + "docker": "", "preferences": "Saját preferenciák", "logout": "Kijelentkezés", "login": "Bejelentkezés", @@ -2352,6 +2357,9 @@ "name": "Docker statisztika", "description": "A konténereid statisztikái (Ezt a widgetet csak rendszergazdai jogosultsággal lehet hozzáadni)", "option": { + "columns": { + "label": "" + }, "enableRowSorting": { "label": "Az elemek rendezésének engedélyezése" }, @@ -2405,6 +2413,12 @@ "title": "Példányok" } }, + "vpn": { + "name": "", + "description": "", + "serviceUnavailable": "", + "option": {} + }, "ups": { "name": "", "description": "", @@ -3639,6 +3653,9 @@ "error": { "internalServerError": "" }, + "empty": { + "noSystems": "" + }, "metric": { "cpu": "", "memory": "", @@ -3712,6 +3729,9 @@ } }, "selectSystem": "", + "empty": { + "noSystems": "" + }, "error": { "internalServerError": "", "liveConnectionFailed": "", @@ -4133,6 +4153,11 @@ "title": "", "text": "" }, + "error": { + "title": "", + "text": "", + "digest": "" + }, "navbar": { "items": { "home": "Nyitólap", @@ -4579,6 +4604,9 @@ }, "umamiActiveVisitors": { "label": "" + }, + "vpn": { + "label": "" } }, "interval": { @@ -5071,7 +5099,8 @@ } }, "error": { - "internalServerError": "" + "internalServerError": "", + "noColumns": "" } }, "kubernetes": { diff --git a/packages/translation/src/lang/it.json b/packages/translation/src/lang/it.json index e170a2565..14dd5d838 100644 --- a/packages/translation/src/lang/it.json +++ b/packages/translation/src/lang/it.json @@ -1021,6 +1021,7 @@ "mediaMonitoring": "Monitoraggio", "speedtest": "Speed test", "analytics": "Analytics", + "vpn": "VPN", "mediaLibrary": "Media Library", "uptimeMonitoring": "Uptime Monitoring" }, @@ -1412,7 +1413,10 @@ "here": "qui", "iconPicker": { "label": "Icona URL", - "header": "Digita il nome o gli oggetti da filtrare per le icone... Homarr cercherà tra le icone {countIcons} per te." + "header": "Digita il nome o gli oggetti da filtrare per le icone... Homarr cercherà tra le icone {countIcons} per te.", + "headerLoading": "Digita per cercare icone…", + "noResults": "Nessuna icona corrisponde a \"{search}\".", + "error": "Impossibile caricare le icone." }, "colorScheme": { "options": { @@ -1464,6 +1468,7 @@ "switchToDarkMode": "Passa alla modalità scura", "switchToLightMode": "Passa al tema chiaro", "management": "Gestione", + "docker": "Docker", "preferences": "Le tue impostazioni", "logout": "Disconnettiti", "login": "Accedi", @@ -2352,6 +2357,9 @@ "name": "Statistiche del docker", "description": "Statistiche dei tuoi contenitori (questo widget può essere aggiunto solo con privilegi di amministratore)", "option": { + "columns": { + "label": "Colonne da mostrare" + }, "enableRowSorting": { "label": "Abilita l'ordinamento degli elementi" }, @@ -2405,6 +2413,12 @@ "title": "Istanze" } }, + "vpn": { + "name": "VPN", + "description": "Panoramica delle tue connessioni VPN", + "serviceUnavailable": "VPN non disponibile", + "option": {} + }, "ups": { "name": "UPS", "description": "Monitora lo stato dei tuoi dispositivi UPS tramite un server NUT", @@ -3639,6 +3653,9 @@ "error": { "internalServerError": "Impossibile connettersi a Beszel. Controlla le tue impostazioni di integrazione." }, + "empty": { + "noSystems": "Nessun sistema trovato" + }, "metric": { "cpu": "CPU", "memory": "Memoria", @@ -3712,6 +3729,9 @@ } }, "selectSystem": "Seleziona sistema", + "empty": { + "noSystems": "Nessun sistema trovato" + }, "error": { "internalServerError": "Impossibile connettersi a Beszel. Controlla le tue impostazioni di integrazione.", "liveConnectionFailed": "Connessione Live non riuscita. L'istanza di Beszel potrebbe non supportare lo streaming in tempo reale.", @@ -4133,6 +4153,11 @@ "title": "Non trovato", "text": "Impossibile trovare la risorsa richiesta" }, + "error": { + "title": "Questa pagina non può essere caricata", + "text": "Si è verificato un errore inatteso durante il caricamento di questa pagina. La navigazione è ancora disponibile: prova a ricaricare o usa un'altra sezione dalla barra laterale.", + "digest": "Riferimento errore: {digest}" + }, "navbar": { "items": { "home": "Home", @@ -4579,6 +4604,9 @@ }, "umamiActiveVisitors": { "label": "Visitatori Attivi Umami" + }, + "vpn": { + "label": "Stato VPN" } }, "interval": { @@ -5071,7 +5099,8 @@ } }, "error": { - "internalServerError": "Recupero dei container Docker non riuscito" + "internalServerError": "Recupero dei container Docker non riuscito", + "noColumns": "Seleziona almeno una colonna nelle impostazioni dei widget" } }, "kubernetes": { diff --git a/packages/translation/src/lang/ja.json b/packages/translation/src/lang/ja.json index 1be05e8c8..e317fd0fe 100644 --- a/packages/translation/src/lang/ja.json +++ b/packages/translation/src/lang/ja.json @@ -1021,6 +1021,7 @@ "mediaMonitoring": "", "speedtest": "", "analytics": "", + "vpn": "", "mediaLibrary": "", "uptimeMonitoring": "" }, @@ -1412,7 +1413,10 @@ "here": "こちら", "iconPicker": { "label": "アイコン URL", - "header": "アイコンをフィルタするために、名前またはオブジェクトを入力してください... Homarrは {countIcons} アイコンを検索します。" + "header": "アイコンをフィルタするために、名前またはオブジェクトを入力してください... Homarrは {countIcons} アイコンを検索します。", + "headerLoading": "", + "noResults": "", + "error": "" }, "colorScheme": { "options": { @@ -1464,6 +1468,7 @@ "switchToDarkMode": "ダークモードに切り替え", "switchToLightMode": "ライトモードに切り替え", "management": "管理", + "docker": "", "preferences": "個人設定", "logout": "ログアウト", "login": "ログイン", @@ -2352,6 +2357,9 @@ "name": "Dockerのステータス", "description": "あなたのコンテナの統計(このウィジェットは管理者権限でのみ追加できます)", "option": { + "columns": { + "label": "" + }, "enableRowSorting": { "label": "アイテムの並び替えを有効にする" }, @@ -2405,6 +2413,12 @@ "title": "インスタンス" } }, + "vpn": { + "name": "", + "description": "", + "serviceUnavailable": "", + "option": {} + }, "ups": { "name": "", "description": "", @@ -3639,6 +3653,9 @@ "error": { "internalServerError": "" }, + "empty": { + "noSystems": "" + }, "metric": { "cpu": "", "memory": "", @@ -3712,6 +3729,9 @@ } }, "selectSystem": "", + "empty": { + "noSystems": "" + }, "error": { "internalServerError": "", "liveConnectionFailed": "", @@ -4133,6 +4153,11 @@ "title": "見つかりません", "text": "要求されたリソースが見つかりませんでした" }, + "error": { + "title": "", + "text": "", + "digest": "" + }, "navbar": { "items": { "home": "ホーム", @@ -4579,6 +4604,9 @@ }, "umamiActiveVisitors": { "label": "" + }, + "vpn": { + "label": "" } }, "interval": { @@ -5071,7 +5099,8 @@ } }, "error": { - "internalServerError": "Docker コンテナの取得に失敗しました" + "internalServerError": "Docker コンテナの取得に失敗しました", + "noColumns": "" } }, "kubernetes": { diff --git a/packages/translation/src/lang/ko.json b/packages/translation/src/lang/ko.json index 8ddf946f3..7df8e6258 100644 --- a/packages/translation/src/lang/ko.json +++ b/packages/translation/src/lang/ko.json @@ -288,10 +288,10 @@ "changePingIconsEnabled": { "notification": { "success": { - "message": "" + "message": "Ping 아이콘 전환 완료" }, "error": { - "message": "" + "message": "Ping 아이콘을 전환할 수 없습니다" } } }, @@ -701,56 +701,56 @@ }, "action": { "create": "생성", - "import": "", - "save": "", - "delete": "", - "deleteConfirm": "", - "export": "", - "addMapping": "", - "addColumn": "", - "edit": "", - "removeMapping": "", - "removeColumn": "", - "addItem": "", - "removeItem": "", - "addBar": "", - "duplicate": "", - "copyAiPrompt": "", - "enable": "", - "disable": "", - "menu": "" + "import": "가져오기", + "save": "저장", + "delete": "사용자 지정 위젯 삭제", + "deleteConfirm": "\"{name}\"을(를) 삭제하시겠습니까? 이 작업은 되돌릴 수 없습니다.", + "export": "내보내기", + "addMapping": "매핑 추가", + "addColumn": "열 추가", + "edit": "편집", + "removeMapping": "매핑 제거", + "removeColumn": "열 제거", + "addItem": "항목 추가", + "removeItem": "항목 제거", + "addBar": "바 추가", + "duplicate": "복사", + "copyAiPrompt": "AI 프롬프트 복사", + "enable": "활성화", + "disable": "비활성화", + "menu": "동작" }, "field": { - "name": "", - "description": "", - "iconUrl": "", - "url": "", - "authType": "", - "headerName": "", - "method": "", - "requestBody": "", - "displayType": "", - "jsonPath": "", - "jsonPathHint": "", - "label": "", - "unit": "", - "mappings": "", - "tablePath": "", - "tablePathHint": "", - "columns": "", - "header": "", - "items": "", - "gridColumns": "", - "cardStyle": "", - "bars": "", - "valuePath": "", - "maxPath": "", - "showPercentage": "", - "barSize": "", - "goodValues": "", - "dotSize": "", - "valueSize": "", - "labelPosition": "", + "name": "이름", + "description": "설명", + "iconUrl": "아이콘 URL", + "url": "URL", + "authType": "인증 유형", + "headerName": "헤더/쿼리 파라미터 이름", + "method": "메서드", + "requestBody": "요청 본문", + "displayType": "표시 유형", + "jsonPath": "JSON 경로", + "jsonPathHint": "JSONPath 문법으로 값을 추출하십시오. 예: $.data.temperature 또는 $.items[0].name", + "label": "레이블", + "unit": "단위", + "mappings": "매핑", + "tablePath": "JSON 경로 배열", + "tablePathHint": "응답에서 배열이 위치한 경로입니다. 예: $.data.items", + "columns": "열", + "header": "헤더", + "items": "항목", + "gridColumns": "그리드 열 개수", + "cardStyle": "카드 스타일", + "bars": "바", + "valuePath": "JSON 경로 값", + "maxPath": "JSON 경로 최댓값", + "showPercentage": "백분율 표시", + "barSize": "바 크기", + "goodValues": "정상 값(쉼표로 구분)", + "dotSize": "점 크기", + "valueSize": "값 크기", + "labelPosition": "레이블 위치", "kvLayout": "레이아웃", "striped": "", "compact": "컴팩트 모드", @@ -849,14 +849,14 @@ "secret": { "apiKey": "", "username": "", - "password": "", - "savedPlaceholder": "" + "password": "비밀번호", + "savedPlaceholder": "••••••• (저장됨, 유지하려면 공백)" }, "preview": { - "title": "", - "test": "", - "hint": "", - "rawResponse": "" + "title": "미리보기", + "test": "테스트", + "hint": "현재 설정으로 API 응답을 미리 보려면 테스트를 클릭하십시오.", + "rawResponse": "원본 JSON 응답" }, "notification": { "created": "\"{name}\" 위젯 생성 완료.", @@ -1021,6 +1021,7 @@ "mediaMonitoring": "모니터링", "speedtest": "속도 테스트", "analytics": "분석", + "vpn": "", "mediaLibrary": "미디어 라이브러리", "uptimeMonitoring": "업타임 모니터링" }, @@ -1412,7 +1413,10 @@ "here": "여기", "iconPicker": { "label": "아이콘 URL", - "header": "이름 또는 주제를 입력해 아이콘을 필터링하세요... Homarr가 {countIcons}에서 아이콘을 검색합니다." + "header": "이름 또는 주제를 입력해 아이콘을 필터링하세요... Homarr가 {countIcons}에서 아이콘을 검색합니다.", + "headerLoading": "", + "noResults": "", + "error": "" }, "colorScheme": { "options": { @@ -1464,6 +1468,7 @@ "switchToDarkMode": "다크 모드로 전환", "switchToLightMode": "라이트 모드로 전환", "management": "관리", + "docker": "", "preferences": "기본 설정", "logout": "로그아웃", "login": "로그인", @@ -2352,6 +2357,9 @@ "name": "", "description": "", "option": { + "columns": { + "label": "" + }, "enableRowSorting": { "label": "" }, @@ -2405,6 +2413,12 @@ "title": "" } }, + "vpn": { + "name": "", + "description": "", + "serviceUnavailable": "", + "option": {} + }, "ups": { "name": "", "description": "", @@ -3639,6 +3653,9 @@ "error": { "internalServerError": "" }, + "empty": { + "noSystems": "" + }, "metric": { "cpu": "", "memory": "", @@ -3712,6 +3729,9 @@ } }, "selectSystem": "", + "empty": { + "noSystems": "" + }, "error": { "internalServerError": "", "liveConnectionFailed": "", @@ -4133,6 +4153,11 @@ "title": "찾을 수 없음", "text": "요청한 리소스를 찾을 수 없습니다" }, + "error": { + "title": "", + "text": "", + "digest": "" + }, "navbar": { "items": { "home": "홈", @@ -4579,6 +4604,9 @@ }, "umamiActiveVisitors": { "label": "" + }, + "vpn": { + "label": "" } }, "interval": { @@ -5071,7 +5099,8 @@ } }, "error": { - "internalServerError": "" + "internalServerError": "", + "noColumns": "" } }, "kubernetes": { diff --git a/packages/translation/src/lang/lt.json b/packages/translation/src/lang/lt.json index afec241f1..eb7bfa801 100644 --- a/packages/translation/src/lang/lt.json +++ b/packages/translation/src/lang/lt.json @@ -1021,6 +1021,7 @@ "mediaMonitoring": "", "speedtest": "", "analytics": "", + "vpn": "", "mediaLibrary": "", "uptimeMonitoring": "" }, @@ -1412,7 +1413,10 @@ "here": "", "iconPicker": { "label": "", - "header": "" + "header": "", + "headerLoading": "", + "noResults": "", + "error": "" }, "colorScheme": { "options": { @@ -1464,6 +1468,7 @@ "switchToDarkMode": "", "switchToLightMode": "", "management": "", + "docker": "", "preferences": "", "logout": "", "login": "Prisijungti", @@ -2352,6 +2357,9 @@ "name": "", "description": "", "option": { + "columns": { + "label": "" + }, "enableRowSorting": { "label": "" }, @@ -2405,6 +2413,12 @@ "title": "" } }, + "vpn": { + "name": "", + "description": "", + "serviceUnavailable": "", + "option": {} + }, "ups": { "name": "", "description": "", @@ -3639,6 +3653,9 @@ "error": { "internalServerError": "" }, + "empty": { + "noSystems": "" + }, "metric": { "cpu": "", "memory": "", @@ -3712,6 +3729,9 @@ } }, "selectSystem": "", + "empty": { + "noSystems": "" + }, "error": { "internalServerError": "", "liveConnectionFailed": "", @@ -4133,6 +4153,11 @@ "title": "", "text": "" }, + "error": { + "title": "", + "text": "", + "digest": "" + }, "navbar": { "items": { "home": "Pagrindinis", @@ -4579,6 +4604,9 @@ }, "umamiActiveVisitors": { "label": "" + }, + "vpn": { + "label": "" } }, "interval": { @@ -5071,7 +5099,8 @@ } }, "error": { - "internalServerError": "" + "internalServerError": "", + "noColumns": "" } }, "kubernetes": { diff --git a/packages/translation/src/lang/lv.json b/packages/translation/src/lang/lv.json index e5d262048..d307e78da 100644 --- a/packages/translation/src/lang/lv.json +++ b/packages/translation/src/lang/lv.json @@ -1021,6 +1021,7 @@ "mediaMonitoring": "", "speedtest": "", "analytics": "", + "vpn": "", "mediaLibrary": "", "uptimeMonitoring": "" }, @@ -1412,7 +1413,10 @@ "here": "", "iconPicker": { "label": "", - "header": "" + "header": "", + "headerLoading": "", + "noResults": "", + "error": "" }, "colorScheme": { "options": { @@ -1464,6 +1468,7 @@ "switchToDarkMode": "", "switchToLightMode": "", "management": "", + "docker": "", "preferences": "Jūsu iestatījumi", "logout": "", "login": "Pieslēgties", @@ -2352,6 +2357,9 @@ "name": "", "description": "", "option": { + "columns": { + "label": "" + }, "enableRowSorting": { "label": "" }, @@ -2405,6 +2413,12 @@ "title": "" } }, + "vpn": { + "name": "", + "description": "", + "serviceUnavailable": "", + "option": {} + }, "ups": { "name": "", "description": "", @@ -3639,6 +3653,9 @@ "error": { "internalServerError": "" }, + "empty": { + "noSystems": "" + }, "metric": { "cpu": "", "memory": "", @@ -3712,6 +3729,9 @@ } }, "selectSystem": "", + "empty": { + "noSystems": "" + }, "error": { "internalServerError": "", "liveConnectionFailed": "", @@ -4133,6 +4153,11 @@ "title": "", "text": "" }, + "error": { + "title": "", + "text": "", + "digest": "" + }, "navbar": { "items": { "home": "Sākums", @@ -4579,6 +4604,9 @@ }, "umamiActiveVisitors": { "label": "" + }, + "vpn": { + "label": "" } }, "interval": { @@ -5071,7 +5099,8 @@ } }, "error": { - "internalServerError": "" + "internalServerError": "", + "noColumns": "" } }, "kubernetes": { diff --git a/packages/translation/src/lang/nl.json b/packages/translation/src/lang/nl.json index 66a8f48d1..9d6dd4335 100644 --- a/packages/translation/src/lang/nl.json +++ b/packages/translation/src/lang/nl.json @@ -1021,6 +1021,7 @@ "mediaMonitoring": "", "speedtest": "", "analytics": "", + "vpn": "", "mediaLibrary": "", "uptimeMonitoring": "" }, @@ -1412,7 +1413,10 @@ "here": "hier", "iconPicker": { "label": "Icoon-URL", - "header": "Typ naam of objecten om te filteren op iconen… Homarr doorzoekt {countIcons} iconen voor je." + "header": "Typ naam of objecten om te filteren op iconen… Homarr doorzoekt {countIcons} iconen voor je.", + "headerLoading": "", + "noResults": "", + "error": "" }, "colorScheme": { "options": { @@ -1464,6 +1468,7 @@ "switchToDarkMode": "Schakel naar donkere modus", "switchToLightMode": "Schakel naar lichte modus", "management": "Beheer", + "docker": "", "preferences": "Jouw voorkeuren", "logout": "Uitloggen", "login": "Inloggen", @@ -2352,6 +2357,9 @@ "name": "Docker statistieken", "description": "", "option": { + "columns": { + "label": "" + }, "enableRowSorting": { "label": "" }, @@ -2405,6 +2413,12 @@ "title": "" } }, + "vpn": { + "name": "", + "description": "", + "serviceUnavailable": "", + "option": {} + }, "ups": { "name": "", "description": "", @@ -3639,6 +3653,9 @@ "error": { "internalServerError": "" }, + "empty": { + "noSystems": "" + }, "metric": { "cpu": "", "memory": "", @@ -3712,6 +3729,9 @@ } }, "selectSystem": "", + "empty": { + "noSystems": "" + }, "error": { "internalServerError": "", "liveConnectionFailed": "", @@ -4133,6 +4153,11 @@ "title": "Niet gevonden", "text": "Kon de aangevraagde bron niet vinden" }, + "error": { + "title": "", + "text": "", + "digest": "" + }, "navbar": { "items": { "home": "Home", @@ -4579,6 +4604,9 @@ }, "umamiActiveVisitors": { "label": "" + }, + "vpn": { + "label": "" } }, "interval": { @@ -5071,7 +5099,8 @@ } }, "error": { - "internalServerError": "Docker-containers zijn niet opgehaald" + "internalServerError": "Docker-containers zijn niet opgehaald", + "noColumns": "" } }, "kubernetes": { diff --git a/packages/translation/src/lang/no.json b/packages/translation/src/lang/no.json index 00309d122..d27fb6978 100644 --- a/packages/translation/src/lang/no.json +++ b/packages/translation/src/lang/no.json @@ -1021,6 +1021,7 @@ "mediaMonitoring": "", "speedtest": "", "analytics": "", + "vpn": "", "mediaLibrary": "", "uptimeMonitoring": "" }, @@ -1412,7 +1413,10 @@ "here": "her", "iconPicker": { "label": "Ikon-URL", - "header": "Skriv navn eller objekt å filtrere for ikoner... Homarr vil søke gjennom {countIcons} ikoner for deg." + "header": "Skriv navn eller objekt å filtrere for ikoner... Homarr vil søke gjennom {countIcons} ikoner for deg.", + "headerLoading": "", + "noResults": "", + "error": "" }, "colorScheme": { "options": { @@ -1464,6 +1468,7 @@ "switchToDarkMode": "Bytt til mørk modus", "switchToLightMode": "Bytt til lys modus", "management": "Administrasjon", + "docker": "", "preferences": "Dine innstillinger", "logout": "Logg ut", "login": "Logg Inn", @@ -2352,6 +2357,9 @@ "name": "Docker status", "description": "Statistikk over containerne dine (denne widgeten kan bare legges til med administratorrettigheter)", "option": { + "columns": { + "label": "" + }, "enableRowSorting": { "label": "Aktiver sortering av elementer" }, @@ -2405,6 +2413,12 @@ "title": "Instanser" } }, + "vpn": { + "name": "", + "description": "", + "serviceUnavailable": "", + "option": {} + }, "ups": { "name": "", "description": "", @@ -3639,6 +3653,9 @@ "error": { "internalServerError": "" }, + "empty": { + "noSystems": "" + }, "metric": { "cpu": "", "memory": "", @@ -3712,6 +3729,9 @@ } }, "selectSystem": "", + "empty": { + "noSystems": "" + }, "error": { "internalServerError": "", "liveConnectionFailed": "", @@ -4133,6 +4153,11 @@ "title": "Ikke funnet", "text": "Fant ikke den forespurte ressursen" }, + "error": { + "title": "", + "text": "", + "digest": "" + }, "navbar": { "items": { "home": "Hjem", @@ -4579,6 +4604,9 @@ }, "umamiActiveVisitors": { "label": "Umami Aktive Besøkende" + }, + "vpn": { + "label": "" } }, "interval": { @@ -5071,7 +5099,8 @@ } }, "error": { - "internalServerError": "Klarte ikke hente Docker-containere" + "internalServerError": "Klarte ikke hente Docker-containere", + "noColumns": "" } }, "kubernetes": { diff --git a/packages/translation/src/lang/pl.json b/packages/translation/src/lang/pl.json index 90aa8c3eb..b26f26541 100644 --- a/packages/translation/src/lang/pl.json +++ b/packages/translation/src/lang/pl.json @@ -2,15 +2,15 @@ "init": { "step": { "start": { - "title": "Witaj w Homarrze", - "subtitle": "Zacznijmy od konfiguracji twojego Homarra.", + "title": "Witaj w Homarr", + "subtitle": "Zacznijmy od konfiguracji Twojej instancji Homarr.", "description": "Aby rozpocząć, wybierz jak chcesz skonfigurować instancję Homarr.", "action": { - "scratch": "Zacznij od nowa", + "scratch": "Zacznij od zera", "importOldmarr": "Importuj z Homarr przed 1.0", - "restoreSqlite": "", - "restoreSqliteDescription": "", - "backToOptions": "" + "restoreSqlite": "Przywróć z kopii zapasowej SQLite", + "restoreSqliteDescription": "Prześlij pełną kopię zapasową bazy danych SQLite z poprzedniej instancji Homarr. Spowoduje to przywrócenie wszystkich tablic, użytkowników, aplikacji, integracji i ustawień.", + "backToOptions": "← Wróć do opcji konfiguracji" } }, "import": { @@ -31,7 +31,7 @@ }, "boardSelection": { "title": "Znaleziono {count} tablic", - "description": "Wybierz wszystkie działy o rozmiarze do zaimportowania", + "description": "Wybierz wszystkie tablice o rozmiarze do zaimportowania", "action": { "selectAll": "Zaznacz wszystkie", "unselectAll": "Odznacz wszystkie" @@ -55,7 +55,7 @@ "field": { "token": { "label": "Token", - "description": "Wprowadź token importu z poprzedniej instancji hommara" + "description": "Wprowadź token importu z poprzedniej instancji homarr" } }, "notification": { @@ -94,45 +94,45 @@ "subtitle": "Konfiguruj ustawienia serwera" }, "integrations": { - "title": "", - "subtitle": "", - "description": "", + "title": "Integracje", + "subtitle": "Wybierz usługi, których używasz.", + "description": "Wybierz integracje, które chcesz połączyć. Skonfigurujemy Twoją tablicę z pasującymi widgetami automatycznie.", "action": { - "continue": "", - "skip": "" + "continue": "Kontynuuj", + "skip": "Pomiń na razie" }, "configure": { - "progress": "", - "skip": "", - "finishing": "", + "progress": "{current} z {total}", + "skip": "Pomiń tę integrację", + "finishing": "Konfigurowanie twojej tablicy...", "error": { - "title": "", - "message": "" + "title": "Konfiguracja nie powiodła się", + "message": "Coś poszło nie tak podczas konfiguracji twojej tablicy. Spróbuj ponownie później." }, "helpers": { - "tooltip": "", - "description": "" + "tooltip": "URL helpers", + "description": "URL helpers spróbują odgadnąć URL twoich serwisów. Jeżeli to nie zadziała, po prostu wpisz adres ręcznie." } }, "urlTemplate": { - "label": "", - "placeholder": "", - "hint": "", - "warning": "", - "preview": "", + "label": "Base URL", + "placeholder": "homelab.local lub 192.168.1.1", + "hint": "Wprowadź swoją podstawową domenę lub adres IP, aby automatycznie wypełniać adresy URL integracji", + "warning": "Ustawienie Base URL spowoduje automatyczne wypełnienie wszystkich adresów URL integracji, oszczędzając czas podczas konfiguracji", + "preview": "np. {example}", "mode": { - "subdomain": "", - "hostPort": "" + "subdomain": "Subdomena", + "hostPort": "Host:Port" } }, "docker": { - "detected": "", - "detectedBreakdown": "", - "summary": "", - "appBadge": "", - "appSectionLabel": "", - "scanning": "", - "scanningDescription": "" + "detected": "Doker wykrył {count} usług", + "detectedBreakdown": "Integracje {integrations} · {apps} aplikacje", + "summary": "Homarr wykrył {apps} aplikacji, z których {integrations} ma wbudowane integracje", + "appBadge": "Aplikacja", + "appSectionLabel": "Aplikacje do dodania do tablicy (wybrano {count})", + "scanning": "Skanowanie Dockera...", + "scanningDescription": "Szukanie uruchomionych kontenerów w systemie" } }, "finish": { @@ -143,7 +143,7 @@ "goToBoard": "Idź do tablicy {name}", "createBoard": "Stwórz swoją pierwszą tablicę", "inviteUser": "Zaproś innych użytkowników", - "docs": "Przeczytaj dokumentację!" + "docs": "Przeczytaj dokumentację" } } }, @@ -163,7 +163,7 @@ "description": "Zostałeś(aś) zaproszony(a) przez {username}" }, "init": { - "title": "Nowa instalacja Homarra", + "title": "Nowa instalacja Homarr", "subtitle": "Proszę utworzyć użytkownika dla podstawowego administratora" } }, @@ -177,7 +177,7 @@ }, "password": { "label": "Hasło", - "suggestionsTitle": "", + "suggestionsTitle": "Propozycje hasła", "suggestion": { "length": "Zawiera co najmniej 8 znaków", "lowercase": "Zawiera małą literę", @@ -205,7 +205,7 @@ "label": "Otwórz wyniki wyszukiwania w nowej karcie" }, "ddgBangs": { - "label": "" + "label": "Włącz DuckDuckGo bangs" } }, "error": { @@ -239,11 +239,11 @@ }, "error": { "title": "Tworzenie konta nie powiodło się", - "message": "Twoje konto nie może zostać utworzone" + "message": "Twoje konto nie mogło zostać utworzone" } } }, - "create": "Dodaj użytkownika", + "create": "Utwórz użytkownika", "changePassword": { "label": "Zmień hasło", "notification": { @@ -251,7 +251,7 @@ "message": "Hasło zostało pomyślnie zmienione" }, "error": { - "message": "Problem ze zmianą hasła" + "message": "Nie można zmienić hasła" } } }, @@ -268,7 +268,7 @@ "changeSearchPreferences": { "notification": { "success": { - "message": "Ustawienia wyszukiwania zostały zmienione" + "message": "Preferencje wyszukiwania zostały zmienione" }, "error": { "message": "Nie można zmienić preferencji wyszukiwania" @@ -288,20 +288,20 @@ "changePingIconsEnabled": { "notification": { "success": { - "message": "Włączono ping na ikonach" + "message": "Pomyślnie przełączono ping ikon" }, "error": { - "message": "Nie można włączyć pingowania ikon" + "message": "Nie można przełączyć ping ikon" } } }, "changeDdgBangs": { "notification": { "success": { - "message": "" + "message": "DuckDuckGo bangs zostały pomyślnie przełączone" }, "error": { - "message": "" + "message": "Nie można przełączyć DuckDuckGo bangs" } } }, @@ -376,7 +376,7 @@ }, "permission": { "admin": { - "title": "Admin", + "title": "Administrator", "item": { "admin": { "label": "Administrator", @@ -451,16 +451,16 @@ "title": "Multimedia", "item": { "upload": { - "label": "Prześlij media", - "description": "Zezwalaj użytkownikom na przesyłanie mediów" + "label": "Prześlij multimedia", + "description": "Zezwalaj użytkownikom na przesyłanie multimediów" }, "view-all": { - "label": "Wyświetl wszystkie media", - "description": "Zezwalaj użytkownikom na wyświetlanie wszystkich mediów" + "label": "Wyświetl wszystkie multimedia", + "description": "Zezwalaj użytkownikom na wyświetlanie wszystkich multimediów" }, "full-all": { - "label": "Pełny dostęp do mediów", - "description": "Zezwalaj użytkownikom na zarządzanie i usuwanie wszelkich mediów" + "label": "Pełny dostęp do multimediów", + "description": "Zezwalaj użytkownikom na zarządzanie i usuwanie wszelkich multimediów" } } }, @@ -513,13 +513,13 @@ "transfer": { "label": "Przenieś własność", "description": "Przenieś właściciela tej grupy na innego użytkownika.", - "confirm": "Czy na pewno chcesz przenieść właściciela grupy {name} na {username}?", + "confirm": "Czy na pewno chcesz zmienić właściciela grupy {name} na {username}?", "notification": { "success": { - "message": "Przeniesiono pomyślnie właściciela grupy {group} do {user} " + "message": "Pomyślnie zmieniono właściciela grupy {group} na {user}" }, "error": { - "message": "Nie można przenieść właściciela" + "message": "Nie można zmienić właściciela" } } }, @@ -527,7 +527,7 @@ "label": "Dodaj członka" }, "removeMember": { - "label": "Usuń użytkownika", + "label": "Usuń członka", "confirm": "Czy na pewno chcesz usunąć {user} z tej grupy?" }, "delete": { @@ -536,7 +536,7 @@ "confirm": "Czy na pewno chcesz usunąć grupę {name}?", "notification": { "success": { - "message": "Usunięto grupę {name}" + "message": "Pomyślnie usunięto grupę {name}" }, "error": { "message": "Nie udało się usunąć grupy {name}" @@ -687,213 +687,213 @@ "customWidget": { "page": { "list": { - "title": "", - "noResults": "", - "loading": "" + "title": "Niestandardowe widgety", + "noResults": "Brak niestandardowych widgetów. Utwórz swój pierwszy!", + "loading": "Ładowanie..." }, "create": { - "title": "", - "subtitle": "" + "title": "Utwórz niestandardowy widget", + "subtitle": "Zdefiniuj adres URL API, uwierzytelnianie oraz sposób wyświetlania danych z odpowiedzi." }, "edit": { - "title": "" + "title": "Edytuj niestandardowy widget" } }, "action": { - "create": "", - "import": "", - "save": "", - "delete": "", - "deleteConfirm": "", - "export": "", - "addMapping": "", - "addColumn": "", - "edit": "", - "removeMapping": "", - "removeColumn": "", - "addItem": "", - "removeItem": "", - "addBar": "", - "duplicate": "", - "copyAiPrompt": "", - "enable": "", - "disable": "", - "menu": "" + "create": "Utwórz", + "import": "Importuj", + "save": "Zapisz", + "delete": "Usuń niestandardowy widget", + "deleteConfirm": "Czy na pewno chcesz usunąć \"{name}\"? Tej akcji nie można cofnąć.", + "export": "Eksportuj", + "addMapping": "Dodaj mapowanie", + "addColumn": "Dodaj kolumnę", + "edit": "Edytuj", + "removeMapping": "Usuń mapowanie", + "removeColumn": "Usuń kolumnę", + "addItem": "Dodaj element", + "removeItem": "Usuń element", + "addBar": "Dodaj pasek", + "duplicate": "Duplikuj", + "copyAiPrompt": "Skopiuj prompt AI", + "enable": "Włącz", + "disable": "Wyłącz", + "menu": "Akcje" }, "field": { - "name": "", - "description": "", - "iconUrl": "", - "url": "", - "authType": "", - "headerName": "", - "method": "", - "requestBody": "", - "displayType": "", - "jsonPath": "", - "jsonPathHint": "", - "label": "", - "unit": "", - "mappings": "", - "tablePath": "", - "tablePathHint": "", - "columns": "", - "header": "", - "items": "", - "gridColumns": "", - "cardStyle": "", - "bars": "", - "valuePath": "", - "maxPath": "", - "showPercentage": "", - "barSize": "", - "goodValues": "", - "dotSize": "", - "valueSize": "", - "labelPosition": "", - "kvLayout": "", - "striped": "", - "compact": "", - "maxHeight": "", - "color": "", - "buttonLabel": "", - "buttonColor": "", - "confirmText": "", - "confirmTextHint": "", - "successMessage": "", - "successMessageHint": "", - "enabled": "", + "name": "Nazwa", + "description": "Opis", + "iconUrl": "Adres URL ikony", + "url": "Adres URL", + "authType": "Typ uwierzytelniania", + "headerName": "Nazwa parametru Header/Query", + "method": "Metoda", + "requestBody": "Body Zapytania", + "displayType": "Typ Wyświetlania", + "jsonPath": "Ścieżka JSON", + "jsonPathHint": "Użyj składni JSONPath do wyodrębnienia wartości, np. $.data.temperature lub $.items[0].name", + "label": "Etykieta", + "unit": "Jednostka", + "mappings": "Mapowania", + "tablePath": "Ścieżka JSON tablicy", + "tablePathHint": "Ścieżka do tablicy w odpowiedzi, np. $.data.items", + "columns": "Kolumny", + "header": "Nagłówek", + "items": "Elementy", + "gridColumns": "Kolumny Siatki", + "cardStyle": "Styl Karty", + "bars": "Paski", + "valuePath": "Ścieżka JSON wartości", + "maxPath": "Ścieżka JSON wartości maksymalnej", + "showPercentage": "Pokaż Procent", + "barSize": "Rozmiar Paska", + "goodValues": "Dobre wartości (oddzielone przecinkami)", + "dotSize": "Rozmiar Kropki", + "valueSize": "Rozmair Wartości", + "labelPosition": "Pozycja Etykiety", + "kvLayout": "Układ", + "striped": "Paskowane rzędy", + "compact": "Tryb Kompaktowy", + "maxHeight": "Maksymalna Wysokość (px)", + "color": "Kolor", + "buttonLabel": "Etykieta Przycisku", + "buttonColor": "Kolor Przycisku", + "confirmText": "Tekst Potwierdzający", + "confirmTextHint": "Jeśli ustawione, okno dialogowe potwierdzenia pojawi się przed wykonaniem żądania", + "successMessage": "Wiadomość o powodzeniu", + "successMessageHint": "Wiadomość wyświetlana po pomyślnym wykonaniu", + "enabled": "Włączony", "template": { - "label": "", - "description": "" + "label": "Szablon JSX", + "description": "Szablon Mantine JSX. Uzyskaj dostęp do pól odpowiedzi API poprzez powiązanie danych (np. data.name). Używaj tylko kopmonentów na białej liście. Zabronione: constructor, __proto__, eval, Function, window, document." } }, "placeholder": { - "exampleLabel": "", - "exampleLabelShort": "", - "exampleHeader": "", - "exampleUnit": "", - "iconUrl": "", - "url": "", - "headerName": "", - "jsonPath": "", - "tablePath": "", - "columnJsonPath": "", - "valuePath": "", - "maxPath": "", - "goodValues": "", - "color": "" + "exampleLabel": "Temperatura CPU", + "exampleLabelShort": "Temp CPU", + "exampleHeader": "Nazwa", + "exampleUnit": "°C", + "iconUrl": "https://...", + "url": "https://api.example.com/api/v1/status", + "headerName": "X-API-Key", + "jsonPath": "$.data.value", + "tablePath": "$.data.items", + "columnJsonPath": "$.name", + "valuePath": "$.data.used", + "maxPath": "$.data.total", + "goodValues": "online, true, up", + "color": "niebieski" }, "cardStyleOption": { - "filled": "", - "outline": "", - "subtle": "" + "filled": "Wypełnione", + "outline": "Obrys", + "subtle": "Subtelny" }, "layoutOption": { - "list": "", - "grid": "" + "list": "Lista", + "grid": "Siatka" }, "sizeOption": { - "sm": "", - "md": "", - "lg": "", - "xl": "" + "sm": "Mały", + "md": "Średni", + "lg": "Duży", + "xl": "Bardzo duży" }, "labelPositionOption": { - "above": "", - "below": "" + "above": "Powyżej", + "below": "Poniżej" }, "fieldset": { - "general": "", - "connection": "", - "display": "" + "general": "Ogólne", + "connection": "Połączenie API", + "display": "Konfiguracja Wyświetlania" }, "displayTypeDescription": { - "singleValue": "", - "keyValue": "", - "table": "", - "statGrid": "", - "progressBars": "", - "statusIndicator": "", - "countGrid": "", - "raw": "", - "actionButton": "", - "customJsx": "" + "singleValue": "Pokaż jedną wyodrębnioną wartość z etykietą i jednostką", + "keyValue": "Pokaż wiele oznaczonych wartości obok siebie", + "table": "Pokaż wiersze danych w formacie tabeli", + "statGrid": "Kolorowe kart statystyk w siatce - takie jak widgety Pi-Hole lub Speedtest", + "progressBars": "Paski postępu pokazujące użycie lub ukończenie — dla dysku, pamięci, kwot", + "statusIndicator": "Kolorowe kropki stanu pokazujące stan online/offline lub stan zdrowia", + "countGrid": "Kompaktowe numer + licznik etykiety w ścisłej siatce", + "raw": "Sformatowany JSON do debugowania", + "actionButton": "Przycisk, który wywołuje zapytanie po kliknięciu", + "customJsx": "Własny układ JSX za pomocą komponentów Mantine z białej listy z pełnym powiązaniem danych API" }, "method": { - "GET": "", - "POST": "", - "PUT": "", - "DELETE": "", - "PATCH": "" + "GET": "GET", + "POST": "POST", + "PUT": "PUT", + "DELETE": "DELETE", + "PATCH": "PATCH" }, "authType": { - "none": "", - "bearer": "", - "basic": "", - "apiKeyHeader": "", - "apiKeyQuery": "" + "none": "None", + "bearer": "Bearer Token", + "basic": "Basic Auth", + "apiKeyHeader": "Klucz API (Nagłówek)", + "apiKeyQuery": "Klucz API (Query)" }, "displayType": { - "singleValue": "", - "keyValue": "", - "table": "", - "statGrid": "", - "progressBars": "", - "statusIndicator": "", - "countGrid": "", - "raw": "", - "actionButton": "", - "customJsx": "" + "singleValue": "Pojedyncza Wartość", + "keyValue": "Klucz-Wartość", + "table": "Tabela", + "statGrid": "Siatka statystyk", + "progressBars": "Paski Postępu", + "statusIndicator": "Wskaźnik statusu", + "countGrid": "Count Grid", + "raw": "Nieprzetworzony JSON", + "actionButton": "Przycisk Akcji", + "customJsx": "Niestandardowy JSX" }, "secret": { - "apiKey": "", - "username": "", - "password": "", - "savedPlaceholder": "" + "apiKey": "Klucz API / Token", + "username": "Nazwa użytkownika", + "password": "Hasło", + "savedPlaceholder": "•••••• (zapisane, pozostaw puste aby zachować)" }, "preview": { - "title": "", - "test": "", - "hint": "", - "rawResponse": "" + "title": "Podgląd", + "test": "Test", + "hint": "Kliknij test, aby wyświetlić podgląd odpowiedzi API w aktualnej konfiguracji.", + "rawResponse": "Nieprzetworzona odpowiedź JSON" }, "notification": { - "created": "", - "updated": "", - "deleted": "", - "imported": "", - "aiPromptCopied": "", - "aiPromptDescription": "", - "aiPromptCopy": "", - "aiPromptNoResponse": "", - "aiPromptCopyError": "", - "createError": "", - "updateError": "", - "importError": "", - "exportError": "", - "duplicated": "", - "duplicateError": "", - "toggleError": "", - "deleteError": "", - "previewError": "", - "executed": "", - "executeError": "" + "created": "Widget \"{name}\" został utworzony pomyślnie.", + "updated": "Widget \"{name}\" zaktualizowany pomyślnie.", + "deleted": "Widget \"{name}\" usunięty.", + "imported": "Widget został zaimportowany pomyślnie.", + "aiPromptCopied": "Prompt AI skopiowany do schowka", + "aiPromptDescription": "Skopiuj wstępnie zbudowany prompt, aby wkleić do dowolnego czatu AI (ChatGPT, Claude, Gemini...). Zawiera pełny schemat widgetu i odpowiedź API, aby AI mógł wygenerować gotową konfigurację JSON dla Ciebie.", + "aiPromptCopy": "Skopiuj do schowka", + "aiPromptNoResponse": "Wskazówka: Uruchom najpierw test, aby dołączyć odpowiedź API w prompcie w celu uzyskania lepszych wyników.", + "aiPromptCopyError": "Nie udało się skopiować prompta do schowka. Twoja przeglądarka może nie obsługiwać tej funkcji.", + "createError": "Nie udało się utworzyć widżetu. Sprawdź, czy endpoint API jest osiągalny.", + "updateError": "Nie udało się zaktualizować widgetu. Zmiany nie zostały zapisane.", + "importError": "Nie udało się zaimportować widgetu. Upewnij się, że plik jest prawidłowym eksportem niestandardowego widgetu JSON.", + "exportError": "Nie udało się wyeksportować widgetu.", + "duplicated": "Widget \"{name}\" zduplikowany.", + "duplicateError": "Nie udało się zduplikować widgetu.", + "toggleError": "Nie udało się przełączyć statusu widgetu.", + "deleteError": "Nie udało się usunąć widgetu.", + "previewError": "Nie udało się pobrać podglądu. Sprawdź URL i spróbuj ponownie.", + "executed": "Akcja wykonana pomyślnie.", + "executeError": "Akcja nie powiodła się: {error}" }, "table": { - "name": "", - "url": "", - "display": "" + "name": "Nazwa", + "url": "Adres URL", + "display": "Widok" }, "widget": { - "disabled": "" + "disabled": "Ten widget jest wyłączony" } }, "integration": { "page": { "list": { "title": "Integracje", - "search": "Wyszukaj integrację", + "search": "Szukaj integracji", "noResults": { "title": "Nie ma jeszcze integracji" } @@ -917,10 +917,10 @@ "label": "Wybierz istniejącą aplikację" }, "new": { - "title": "Nowy", + "title": "Nowa", "url": { "label": "Adres URL aplikacji", - "description": "Adres URL, który zostanie otwarty po kliknięciu aplikacji na pulpicie" + "description": "Adres URL, który zostanie otwarty po kliknięciu aplikacji na tablicy" } } } @@ -983,46 +983,47 @@ "placeholder": "Niestandardowy adres URL aplikacji" }, "apiKeySettings": { - "label": "" + "label": "Uzyskaj klucz API" } }, "action": { "create": "Nowa integracja" }, "grid": { - "widgetCount": "", - "noWidgets": "", - "detected": "" + "widgetCount": "{count} widgety", + "noWidgets": "Brak widgetów", + "detected": "Wykryto przez Docker" }, "category": { - "dnsHole": "", - "mediaService": "", - "calendar": "", - "mediaSearch": "", - "mediaRelease": "", - "mediaRequest": "", - "downloadClient": "", - "usenet": "", - "torrent": "", - "miscellaneous": "", - "smartHomeServer": "", - "indexerManager": "", - "healthMonitoring": "", - "search": "", - "mediaTranscoding": "", - "networkController": "", - "releasesProvider": "", - "notifications": "", - "firewall": "", - "timetable": "", - "photoService": "", - "documents": "", - "notes": "", - "mediaMonitoring": "", - "speedtest": "", - "analytics": "", - "mediaLibrary": "", - "uptimeMonitoring": "" + "dnsHole": "DNS", + "mediaService": "Media", + "calendar": "Kalendarz", + "mediaSearch": "Szukaj", + "mediaRelease": "Wydania", + "mediaRequest": "Zapytania", + "downloadClient": "Pobrania", + "usenet": "Usenet", + "torrent": "Torrent", + "miscellaneous": "Inne", + "smartHomeServer": "Smart Home", + "indexerManager": "Indexer", + "healthMonitoring": "Kondycja", + "search": "Szukaj", + "mediaTranscoding": "Transkodowanie", + "networkController": "Sieć", + "releasesProvider": "Wydania", + "notifications": "Powiadomienia", + "firewall": "Firewall", + "timetable": "Harmonogram", + "photoService": "Zdjęcia", + "documents": "Dokumenty", + "notes": "Notatki", + "mediaMonitoring": "Monitoring", + "speedtest": "Test prędkości", + "analytics": "Analityka", + "vpn": "VPN", + "mediaLibrary": "Biblioteka Multimediów", + "uptimeMonitoring": "Monitorowanie Czasu Pracy" }, "testConnection": { "action": { @@ -1215,26 +1216,26 @@ }, "invalidJson": { "title": "Nieprawidłowy JSON", - "message": "Odpowiedź nie była prawidłowym JSON'em" + "message": "Nie poprawny JSON w odpowiedzi" }, "wrongPath": { "title": "Błędna ścieżka", "message": "Ścieżka jest prawdopodobnie niepoprawna" }, "tooManyRequests": { - "title": "Zbyt wiele żądań w określonym czasie", - "message": "Wystąpiło zbyt wiele żądań. Prawdopodobnie jesteś ograniczony lub odrzucony przez system docelowy" + "title": "Zbyt wiele zapytań w określonym czasie", + "message": "Wystąpiło zbyt wiele zapytań. Prawdopodobnie jesteś ograniczony lub odrzucony przez system docelowy" } } }, "secrets": { "title": "Sekrety", - "lastUpdated": "Data ostatniej aktualizacji: {date}", + "lastUpdated": "Ostatnio zaktualizowane {date}", "notSet": { "label": "Nie ustawiono wartości", "tooltip": "Ten wymagany sekret nie został jeszcze ustawiony" }, - "secureNotice": "Ten sekret nie może zostać pobrany po utworzeniu", + "secureNotice": "Ten sekret nie może zostać odzyskany po utworzeniu", "reset": { "title": "Resetuj sekret", "message": "Czy na pewno chcesz zresetować ten sekret?" @@ -1277,8 +1278,8 @@ "newLabel": "Nowy adres URL" }, "opnsenseApiKey": { - "label": "Klucz API", - "newLabel": "Nowy klucz API" + "label": "Klucz API (Key)", + "newLabel": "Nowy klucz API (Key)" }, "opnsenseApiSecret": { "label": "Klucz API (Secret)", @@ -1297,8 +1298,8 @@ "newLabel": "Nowy klucz prywatny" }, "slug": { - "label": "", - "newLabel": "" + "label": "Slug strony statusu", + "newLabel": "Nowy Slug strony statusu" } } }, @@ -1308,33 +1309,33 @@ "full": "Pełny dostęp do integracji" }, "audiobookshelf": { - "name": "", - "description": "" + "name": "Audiobookshelf", + "description": "Samodzielnie hostowany serwer audiobooków i podcastów" }, "navidrome": { - "name": "", - "description": "" + "name": "Navidrome", + "description": "Samodzielnie hostowany serwer strumieniowania muzyki z kompatybilnością Subsonic API" }, "uptimeKuma": { - "name": "", - "description": "", + "name": "Uptime Kuma", + "description": "Samodzielnie hostowany serwer monitorujący czas pracy z publicznymi stronami statusu", "secret": { "slug": { - "description": "" + "description": "Slug twojej publicznej strony statusu. Domyślnie \"default\" jeśli nie ustawione." }, "apiKey": { - "description": "" + "description": "Opcjonalny klucz API dla uwierzytelnionego dostępu /metryki" } } }, "paperlessNgx": { - "name": "", - "description": "" + "name": "Paperless-ngx", + "description": "System zarządzania dokumentami do archiwizacji i organizacji zeskanowaych dokumnteów" } }, "media": { "plural": "Multimedia", - "search": "Znajdź media", + "search": "Znajdź multimedia", "field": { "name": "Nazwa", "size": "Rozmiar", @@ -1342,7 +1343,7 @@ }, "action": { "upload": { - "label": "Prześlij media", + "label": "Prześlij multimedia", "file": "Wybierz plik", "notification": { "success": { @@ -1354,14 +1355,14 @@ } }, "delete": { - "label": "Usuń media", - "description": "Czy na pewno chcesz usunąć media ?", + "label": "Usuń multimedia", + "description": "Czy na pewno chcesz usunąć multimedia ?", "notification": { "success": { "message": "Plik został usunięty pomyślnie" }, "error": { - "message": "Nie udało się usunąć mediów" + "message": "Nie udało się usunąć pliku" } } }, @@ -1369,11 +1370,11 @@ "label": "Kopiuj adres URL" }, "open": { - "label": "Otwórz media" + "label": "Otwórz multimedia" } }, "noResults": { - "title": "" + "title": "Brak przesłanych multimediów" } }, "common": { @@ -1384,7 +1385,7 @@ "add": "Dodaj", "apply": "Zastosuj", "backToOverview": "Powrót do widoku ogólnego", - "skip": "", + "skip": "Pomiń", "change": "Zmień", "create": "Utwórz", "createAnother": "Utwórz i zacznij od nowa", @@ -1406,24 +1407,27 @@ "checkLogs": "Sprawdź logi, aby uzyskać więcej informacji.", "tryAgain": "Spróbuj ponownie", "loading": "Ładowanie", - "show": "", - "hide": "" + "show": "Pokaż", + "hide": "Ukryj" }, "here": "tutaj", "iconPicker": { "label": "adres URL ikony", - "header": "Wpisz nazwę lub obiekty do filtrowania ikon... Homarr przeszuka dla Ciebie ikony {countIcons}." + "header": "Wpisz nazwę lub obiekty do filtrowania ikon... Homarr przeszuka dla Ciebie ikony {countIcons}.", + "headerLoading": "Wpisz, aby wyszukać ikony…", + "noResults": "Brak ikon pasujących do \"{search}\".", + "error": "Nie udało się załadować ikon." }, "colorScheme": { "options": { - "auto": "", + "auto": "Automatyczne", "light": "Jasny", "dark": "Ciemny" } }, "information": { "min": "Min", - "max": "Max.", + "max": "Maks", "days": "Dni", "hours": "Godziny", "minutes": "Minuty" @@ -1464,6 +1468,7 @@ "switchToDarkMode": "Przełącz na tryb nocny", "switchToLightMode": "Przełącz na tryb jasny", "management": "Zarządzanie", + "docker": "Docker", "preferences": "Twoje preferencje", "logout": "Wyloguj się", "login": "Zaloguj się", @@ -1501,8 +1506,8 @@ "number": "Wartość tego pola musi być mniejsza lub równa {maximum}" }, "custom": { - "passwordsDoNotMatch": "Wprowadzone hasła nie zgadzają się", - "passwordRequirements": "Twoje hasło nie spełnia wymagań.", + "passwordsDoNotMatch": "Hasła nie są takie same", + "passwordRequirements": "Twoje hasło nie spełnia wymagań", "boardAlreadyExists": "Tablica o tej nazwie już istnieje", "invalidFileType": "Nieprawidłowy typ pliku, oczekiwany {expected}", "invalidFileName": "Nieprawidłowa nazwa pliku", @@ -1573,7 +1578,7 @@ "label": { "create": "Nowa kategoria", "changePosition": "Zmiana pozycji", - "settings": "" + "settings": "Ustawienia" } }, "openAllInNewTabs": { @@ -1584,12 +1589,12 @@ }, "item": { "action": { - "create": "Nowa pozycja", - "import": "Importuj pozycję", - "edit": "Edytuj pozycję", - "moveResize": "Przenieś / zmień rozmiar pozycji", - "duplicate": "Duplikuj pozycję", - "remove": "Usuń pozycję" + "create": "Nowy element", + "import": "Importuj element", + "edit": "Edytuj element", + "moveResize": "Przenieś / zmień rozmiar element", + "duplicate": "Duplikuj element", + "remove": "Usuń element" }, "menu": { "label": { @@ -1602,7 +1607,7 @@ "addToBoard": "Dodaj do tablicy" }, "moveResize": { - "title": "Przenieś / zmień rozmiar pozycji", + "title": "Przenieś / zmień rozmiar elementu", "field": { "width": { "label": "Szerokość" @@ -1619,17 +1624,17 @@ } }, "edit": { - "title": "Edytuj pozycje", + "title": "Edytuj element", "tab": { - "widget": "", - "app": "" + "widget": "Edytuj widget", + "app": "Edytuj aplikację" }, "app": { - "propagationNotice": "" + "propagationNotice": "Zmiany w tej aplikacji będą zastosowane wszędzie tam, gdzie jest używana spośród wszystkich Tablic." }, "advancedOptions": { "label": "Ustawienia zaawansowane", - "title": "Zaawansowane opcje pozycji" + "title": "Zaawansowane opcje elementu" }, "field": { "integrations": { @@ -1653,11 +1658,11 @@ }, "widget": { "integrationSelect": { - "configureIntegrationFirst": "", - "loading": "", - "placeholder": "", - "noResults": "", - "loadError": "" + "configureIntegrationFirst": "Najpierw połącz integrację", + "loading": "Ładowanie...", + "placeholder": "Wybierz opcję", + "noResults": "Nie znaleziono żadnych wyników", + "loadError": "Nie udało się załadować opcji" }, "app": { "name": "Aplikacja", @@ -1673,7 +1678,7 @@ "label": "Pokaż nazwę aplikacji" }, "pingEnabled": { - "label": "Włącz prosty ping" + "label": "Włącz sprawdzanie statusu" }, "layout": { "label": "Układ", @@ -1738,7 +1743,7 @@ "label": "Otwórz w nowej karcie" }, "withBorder": { - "label": "" + "label": "Pokaż obramowanie" }, "items": { "label": "Zakładki", @@ -1747,11 +1752,11 @@ } }, "archiveTeamWarrior": { - "name": "", - "description": "", + "name": "ArchiveTeam Warrior", + "description": "Monitor ArchiveTeam Warrior status, projekty i aktywne prace archwialne.", "option": { "showBroadcastMessage": { - "label": "" + "label": "Pokaż wiadomości nadawcze" } } }, @@ -2352,6 +2357,9 @@ "name": "Statystyki Dockera", "description": "Statystyki Twoich kontenerów (ten widżet można dodać tylko z uprawnieniami administratora)", "option": { + "columns": { + "label": "" + }, "enableRowSorting": { "label": "Włącz sortowanie elementów" }, @@ -2405,35 +2413,41 @@ "title": "Instancje" } }, + "vpn": { + "name": "VPN", + "description": "Przegląd Twoich połączeń VPN", + "serviceUnavailable": "VPN niedostępny", + "option": {} + }, "ups": { - "name": "", - "description": "", + "name": "UPS", + "description": "Monitoruj status urządzeń UPS przez serwer NUT", "option": { "showBattery": { - "label": "" + "label": "Pokaż baterię" }, "showLoad": { - "label": "" + "label": "Pokaż obciążenie" }, "showVoltage": { - "label": "" + "label": "Pokaż napięcie" } }, "status": { - "online": "", - "onBattery": "", - "lowBattery": "", - "charging": "", - "unknown": "" + "online": "Online", + "onBattery": "Na baterii", + "lowBattery": "Niski poziom baterii", + "charging": "Ładowanie", + "unknown": "Nieznany" }, "field": { - "runtime": "", - "load": "", - "input": "", - "output": "" + "runtime": "Czas pracy", + "load": "Załaduj", + "input": "Input", + "output": "Output" }, "error": { - "internalServerError": "" + "internalServerError": "Nie udało się pobrać danych z UPS" } }, "tracearr": { @@ -2492,101 +2506,101 @@ } }, "umami": { - "name": "", - "description": "", - "visitors": "", - "active": "", - "pageviews": "", - "visits": "", - "bounceRate": "", - "avgDuration": "", + "name": "Analityka Umami", + "description": "Wyświetl statystyki odwiedzających z instancji analityki Umami", + "visitors": "goście", + "active": "aktywny", + "pageviews": "wyświetlenia strony", + "visits": "odwiedziny", + "bounceRate": "bounce", + "avgDuration": "średnia", "option": { "websiteId": { - "label": "", - "description": "", - "placeholder": "", - "loading": "", - "noWebsites": "", - "configureIntegrationFirst": "" + "label": "Strona internetowa", + "description": "Wybierz stronę śledzoną przez instancję Umami", + "placeholder": "Wybierz stronę internetową", + "loading": "Ładowanie stron internetowych...", + "noWebsites": "Nie znaleziono żadnych stron internetowych", + "configureIntegrationFirst": "Połącz najpierw integrację Umami" }, "timeFrame": { - "label": "", - "description": "", + "label": "Przedział czasowy", + "description": "Okres czasu dla którego wyświetlać dane odwiedzających", "option": { - "today": "", - "24h": "", - "7d": "", - "30d": "", - "month": "", - "lastMonth": "" + "today": "Dzisiaj", + "24h": "Ostatnie 24 godziny", + "7d": "Ostatnie 7 dni", + "30d": "Ostatnie 30 dni", + "month": "W tym miesiącu", + "lastMonth": "Ostatni miesiąc" } }, "eventName": { - "label": "", - "description": "", - "placeholder": "", - "loading": "", - "noEvents": "", - "none": "", - "configureWebsiteFirst": "" + "label": "Zdarzenie", + "description": "Pokaż wszystkie wystąpienia określonego zdarzenia", + "placeholder": "Wybierz zdarzenie", + "loading": "Ładowanie zdarzeń...", + "noEvents": "Nie znaleziono zdarzeń", + "none": "Brak zdarzeń", + "configureWebsiteFirst": "Wybierz stronę aby załadować zdarzenia" }, "chartType": { - "label": "", - "description": "", + "label": "Typ wykresu", + "description": "Wyświetl styl wykresu", "option": { - "bar": "", - "sparkline": "" + "bar": "Wykres słupkowy", + "sparkline": "Sparkline" } }, "chartStyle": { - "label": "", - "description": "", + "label": "Styl wykresu", + "description": "Jak wyświetlić serię zdarzeń na wykresie", "option": { - "grouped": "", - "overlay": "" + "grouped": "Obok siebie", + "overlay": "Overlay (subset)" } }, "viewMode": { - "label": "", - "description": "", + "label": "Widok", + "description": "Co wyświetlić w zawartości widgetu", "option": { - "chart": "", - "events": "", - "topPages": "", - "topReferrers": "" + "chart": "Wykres", + "events": "Wykres zdarzeń", + "topPages": "Najlepsze strony", + "topReferrers": "Główne źródła" } }, "eventNames": { - "label": "", - "description": "", - "placeholder": "", - "loading": "", - "noEvents": "", - "configureWebsiteFirst": "" + "label": "Zdarzenia", + "description": "Wybierz jedno lub więcej zdarzeń do wykresu", + "placeholder": "Wybierz zdarzenie...", + "loading": "Ładowanie zdarzeń...", + "noEvents": "Nie znaleziono zdarzeń", + "configureWebsiteFirst": "Wybierz stronę aby załadować zdarzenia" }, "topCount": { - "label": "", - "description": "" + "label": "Elementy do wyświetlenia", + "description": "Liczba górnych elementów do wyświetlenia (maks. 500)" } }, "event": { - "occurrences": "", - "label": "" + "occurrences": "zdarzenia", + "label": "zdarzenia" }, "topPages": { - "direct": "" + "direct": "Bezpośrednio" }, "topReferrers": { - "direct": "" + "direct": "Bezpośrednio" }, "events": { - "selectPrompt": "" + "selectPrompt": "Wybierz zdarzenia do wykresu" }, "error": { - "noIntegration": "", - "noWebsite": "", - "noData": "", - "internalServerError": "" + "noIntegration": "Brak skonfigurowanej integracji Umami", + "noWebsite": "Brak skonfigurowanej strony internetowej", + "noData": "Brak dostępnych danych", + "internalServerError": "Nie udało się pobrać danych z Umami" } }, "common": { @@ -2685,48 +2699,48 @@ } }, "paperlessNgx": { - "name": "", - "description": "", - "documentsTotal": "", - "documentsInbox": "", - "correspondents": "", - "tags": "", - "documentTypes": "", - "inboxRatio": "", - "inbox": "", - "total": "", + "name": "Paperless-ngx", + "description": "Przegląd Twojego archiwum dokumentów Paperless-ngx", + "documentsTotal": "Łączna liczba dokumentów", + "documentsInbox": "Skrzynka odbiorcza", + "correspondents": "Korespondenci", + "tags": "Tagi", + "documentTypes": "Typy dokumentów", + "inboxRatio": "Współczynnik skrzynki odbiorczej", + "inbox": "skrzynka odbiorcza", + "total": "razem", "option": { "showInboxRatio": { - "label": "", - "description": "" + "label": "Pokaż współczynnik skrzynki odbiorczej", + "description": "Wyświetl stosunek liczby dokumentów z skrzynki do wszystkich w sekcji hero" }, "showInboxRing": { - "label": "", - "description": "" + "label": "Pokaż pierścień skrzynki odbiorczej", + "description": "Wyświetlaj pierścień postępu obok współczynkia skrzynki odbiorczej" }, "showDocumentsTotal": { - "label": "", - "description": "" + "label": "Pokaż wszystkie dokumenty", + "description": "Pokaż całkowitą liczbę dokumentów w archiwum" }, "showDocumentsInbox": { - "label": "", - "description": "" + "label": "Pokaż liczbę skrzynek odbiorczych", + "description": "Pokaż liczbę nieprzetworzonych dokumentów w skrzynce odbiorczej" }, "showCorrespondents": { - "label": "", - "description": "" + "label": "Pokaż korespondentów", + "description": "Pokaż całkowitą liczbę korespondentów" }, "showTags": { - "label": "", - "description": "" + "label": "Pokaż Tagi", + "description": "Pokaż całkowitą liczbę tagów" }, "showDocumentTypes": { - "label": "", - "description": "" + "label": "Pokaż typy dokumentów", + "description": "Pokaż całkowitą liczbę typów dokumentów" } }, "error": { - "internalServerError": "" + "internalServerError": "Nie udało się pobrać danych Paperless-ngx" } }, "immich-serverStats": { @@ -3338,110 +3352,110 @@ } }, "uptimeKuma": { - "name": "", - "description": "", + "name": "Uptime Kuma", + "description": "Monitoruj statystyki czasu pracy ze strony statusu Uptime Kuma", "option": { "showAverageUptime": { - "label": "", - "description": "" + "label": "Pokaż średni czas pracy", + "description": "Wyświetlaj łączny średni 24-godzinny procent czasu pracy" }, "showUptimeRing": { - "label": "", - "description": "" + "label": "Pokaż pierścień czasu pracy", + "description": "Wyświetlaj pierścień postępu obok średniej wartości czasu pracy" }, "showTotalMonitors": { - "label": "", - "description": "" + "label": "Pokaż wszystkie monitory", + "description": "Pokaż ilośc wszystkich z konfigurowanych monitorów" }, "showUpCount": { - "label": "", - "description": "" + "label": "Pokaż ilość włączonych", + "description": "Pokaż ile monitorów obecnie jest włączonych" }, "showDownCount": { - "label": "", - "description": "" + "label": "Pokaż ilość wyłączonych", + "description": "Pokaż ile monitorów obecnie jest wyłączonych" }, "showPausedCount": { - "label": "", - "description": "" + "label": "Pokaż ilośc zatrzymanych", + "description": "Pokaż ile monitorów obecnie jest zatrzymane" } }, - "averageUptime": "", + "averageUptime": "Średni czas pracy (24 godz.)", "stats": { - "totalMonitors": "", - "upCount": "", - "downCount": "", - "pausedCount": "" + "totalMonitors": "Całkowita liczba monitorów", + "upCount": "Włączone", + "downCount": "Wyłączone", + "pausedCount": "Zatrzymane" }, "error": { - "internalServerError": "" + "internalServerError": "Nie udało się pobrać danych czasu pracy" } }, "audioStats": { - "name": "", - "description": "", + "name": "Audio stats", + "description": "Statystyki biblioteki i odtwarzania z Navidrome lub Audiobookshelf", "option": { "showArtists": { - "label": "", - "description": "" + "label": "Pokaż artystów", + "description": "Pokaż całkowitą liczbę artystów w twojej bibliotece muzycznej (Navidrome)" }, "showAlbums": { - "label": "", - "description": "" + "label": "Pokaż albumy", + "description": "Pokaż całkowitą liczbę albumów w twojej bibliotece muzycznej (Navidrome)" }, "showSongs": { - "label": "", - "description": "" + "label": "Pokaż utwory", + "description": "Pokaż całkowitą liczbę utworów w twojej bibliotece muzycznej (Navidrome)" }, "showNowPlaying": { - "label": "", - "description": "" + "label": "Pokaż teraz odtwarzane", + "description": "Pokaż aktualnie odtwarzane utwory (Navidrome)" }, "showNowPlayingList": { - "label": "", - "description": "" + "label": "Pokaż listę teraz odtwarzanych", + "description": "Pokaż poszczególne utwory zamiast tylko aktywnej liczby strumieni (Navidrome)" }, "maxNowPlayingItems": { - "label": "", - "description": "" + "label": "Maksymalna ilość teraz odtwarzanych elementów", + "description": "Maksymalna liczba elementów wyświetlanych na liście teraz odtwarzanych (Navidrome)" }, "showLibraryCount": { - "label": "", - "description": "" + "label": "Pokaż ilość bibliotek", + "description": "Pokaż liczbę bibliotek na twojej instancji (Audiobookshelf)" }, "showAudiobooks": { - "label": "", - "description": "" + "label": "Pokaż audiobooki", + "description": "Pokaż całkowitą liczbę audiobooków we wszystkich bibliotekach (Audiobookshelf)" }, "showPodcasts": { - "label": "", - "description": "" + "label": "Pokaż podcasty", + "description": "Pokaż całkowitą liczbę podcastów we wszystkich bibliotekach (Audiobookshelf)" }, "showListeningTime": { - "label": "", - "description": "" + "label": "Pokaż czas słuchania", + "description": "Pokaż całkowity czas słuchania (Audiobookshelf)" }, "showActiveSessions": { - "label": "", - "description": "" + "label": "Pokaż aktywne sesje", + "description": "Pokaż liczbę aktualnie aktywnych sesji odtwarzania (Audiobookshelf)" }, "compactMode": { - "label": "", - "description": "" + "label": "Tryb kompaktowy", + "description": "Użyj gęstszego układu z mniejszymi płytkami, idealnego dla widgetów małych rozmiarów" } }, - "artists": "", - "albums": "", - "songs": "", - "libraryCount": "", - "audiobooks": "", - "podcasts": "", - "listeningTime": "", - "activeSessions": "", - "nowPlaying": "", - "nowPlayingCount": "", + "artists": "Artyści", + "albums": "Albumy", + "songs": "Utwory", + "libraryCount": "Biblioteki", + "audiobooks": "Audiobooki", + "podcasts": "Podcasty", + "listeningTime": "Czas Słuchania", + "activeSessions": "Aktywne Sesje", + "nowPlaying": "Teraz Odtwarzane", + "nowPlayingCount": "{count, plural, one {# stream} other {# streams}}", "error": { - "internalServerError": "" + "internalServerError": "Nie udało się pobrać danych audio stats" } }, "systemResources": { @@ -3510,254 +3524,260 @@ } }, "customApi": { - "name": "", - "description": "", + "name": "Niestandardowe API", + "description": "Wyświetlaj dane z dowolnego zewnętrznego API z niestandardową konfiguracją.", "option": { "definitionId": { - "label": "" + "label": "Definition ID" }, "refreshInterval": { - "label": "", - "description": "" + "label": "Interwał odświeżania", + "description": "Jak często pobierać dane z API (w sekundach)" } }, - "noDefinition": "", - "definitionNotFound": "", - "noDisplayOutput": "", - "executeConfirm": "", - "executing": "", - "executeSuccess": "", - "executeFailed": "" + "noDefinition": "Nie wybrano definicji", + "definitionNotFound": "Nie znaleziono definicji widgetu. Być może została usunięta.", + "noDisplayOutput": "No display output", + "executeConfirm": "Czy jesteś pewien, że chcesz wykonać to zadanie?", + "executing": "Wykonywanie...", + "executeSuccess": "Akcja wykonana pomyślnie", + "executeFailed": "Akcja nie powiodła się. Sprawdź konfigurację widgetu." }, "beszelSystemTable": { - "name": "", - "description": "", + "name": "Systemy Beszel (Tabela)", + "description": "Widok tabeli wszystkich systemów monitorowanych przez Beszel z sortowalnymi kolumnami i wskaźnikami statusu", "option": { "statusFilter": { - "label": "" + "label": "Filtry statusu" }, "sortBy": { - "label": "" + "label": "Sortuj według" }, "sortDirection": { - "label": "" + "label": "Kierunek sortowania" }, "showCpu": { - "label": "" + "label": "Pokaż CPU" }, "showMemory": { - "label": "" + "label": "Pokaż pamięć" }, "showDisk": { - "label": "" + "label": "Pokaż dysk" }, "showGpu": { - "label": "" + "label": "Pokaż GPU" }, "showLoadAvg": { - "label": "" + "label": "Pokaż średnie obciążenie" }, "showNet": { - "label": "" + "label": "Pokaż sieć" }, "showTemp": { - "label": "" + "label": "Pokaż temperaturę" }, "showBattery": { - "label": "" + "label": "Pokaż baterię" }, "showServices": { - "label": "" + "label": "Pokaż usługi" }, "showUptime": { - "label": "" + "label": "Pokaż czas pracy" }, "showAgent": { - "label": "" + "label": "Pokaż wersję agenta" } }, - "noRecords": "", + "noRecords": "Nie znaleziono systemów", "error": { - "internalServerError": "" + "internalServerError": "Nie można połączyć się z Beszel. Sprawdź ustawienia integracji." }, "column": { - "system": "", - "cpu": "", - "memory": "", - "disk": "", - "gpu": "", - "loadAvg": "", - "net": "", - "temp": "", - "battery": "", - "services": "", - "uptime": "", - "agent": "" + "system": "System", + "cpu": "CPU", + "memory": "Pamięć", + "disk": "Dysk", + "gpu": "GPU", + "loadAvg": "Śr. Obciążenie", + "net": "Sieć", + "temp": "Temp.", + "battery": "Bateria", + "services": "Usługi", + "uptime": "Czas pracy", + "agent": "Agent" } }, "beszelSystemGrid": { - "name": "", - "description": "", + "name": "Beszel Systems (Siatka)", + "description": "Widok siatki wszystkich systemów monitorowanych przez Beszel z miernikami w czasie rzeczywistym", "option": { "statusFilter": { - "label": "" + "label": "Filtr statusu" }, "showCpu": { - "label": "" + "label": "Pokaż CPU" }, "showMemory": { - "label": "" + "label": "Pokaż pamięć" }, "showDisk": { - "label": "" + "label": "Pokaż dysk" }, "showGpu": { - "label": "" + "label": "Pokaż GPU" }, "showLoadAvg": { - "label": "" + "label": "Pokaż średnie obciążenie" }, "showNet": { - "label": "" + "label": "Pokaż sieć" }, "showTemp": { - "label": "" + "label": "Pokaż temperaturę" }, "showBattery": { - "label": "" + "label": "Pokaż baterię" }, "showServices": { - "label": "" + "label": "Pokaż usługi" }, "showUptime": { - "label": "" + "label": "Pokaż czas pracy" }, "showAgent": { - "label": "" + "label": "Pokaż wersję agenta" } }, "error": { - "internalServerError": "" + "internalServerError": "Nie można połączyć się z Beszel. Sprawdź ustawienia integracji." + }, + "empty": { + "noSystems": "Nie znaleziono systemów" }, "metric": { - "cpu": "", - "memory": "", - "disk": "", - "gpu": "", - "loadAvg": "", - "net": "", - "temp": "", - "battery": "", - "services": "", - "uptime": "", - "agent": "" + "cpu": "CPU", + "memory": "Pamięć", + "disk": "Dysk", + "gpu": "GPU", + "loadAvg": "Śr. Obciążenie", + "net": "Sieć", + "temp": "Temp.", + "battery": "Bateria", + "services": "Usługi", + "uptime": "Czas pracy", + "agent": "Agent" } }, "beszelAlerts": { - "name": "", - "description": "", + "name": "Alerty Beszel", + "description": "Zobacz konfiguracje i historię alarmów Beszel", "option": { "showHistory": { - "label": "" + "label": "Pokaż historię alertów" }, "maxHistoryItems": { - "label": "" + "label": "Maksymalna ilość elementów historii" } }, - "empty": "", + "empty": "Brak skonfigurowanych alertów", "error": { - "internalServerError": "" + "internalServerError": "Nie można połączyć się z Beszel. Sprawdź ustawienia integracji." }, - "history": "", - "resolved": "", + "history": "Historia", + "resolved": "Rozwiązane", "status": { - "triggered": "", - "ok": "" + "triggered": "Wywołane", + "ok": "OK" } }, "beszelSystemStats": { - "name": "", - "description": "", + "name": "Beszel System Stats", + "description": "Wykresy okresów czasowych dla CPU, pamięci, dysku, sieci i metryki kontenerów Docker", "option": { "systemId": { - "label": "", - "description": "" + "label": "System", + "description": "Wybierz system Beszel do wyświetlania statystyk dla" }, "timePeriod": { - "label": "" + "label": "Okres czasu" }, "showCpu": { - "label": "" + "label": "Pokaż wykres CPU" }, "showMemory": { - "label": "" + "label": "Pokaż wykres pamięci" }, "showDisk": { - "label": "" + "label": "Pokaż wykres dysku" }, "showDiskIO": { - "label": "" + "label": "Pokaż wykres I/O dysku" }, "showNetwork": { - "label": "" + "label": "Pokaż wykres sieci" }, "showDockerCpu": { - "label": "" + "label": "Pokaż wykres CPU Dockera" }, "showDockerMemory": { - "label": "" + "label": "Pokaż wykres Pamięci Dockera" }, "showDockerNetwork": { - "label": "" + "label": "Pokaż wykres Sieci Dockera" } }, - "selectSystem": "", + "selectSystem": "Wybierz system", + "empty": { + "noSystems": "Nie znaleziono systemów" + }, "error": { - "internalServerError": "", - "liveConnectionFailed": "", - "switchToHistorical": "", - "systemNotFound": "" + "internalServerError": "Nie można połączyć się z Beszel. Sprawdź ustawienia integracji.", + "liveConnectionFailed": "Połączenie na żywo nie powiodło się. Twoja instancja Beszel może nie obsługiwać strumieniowania w czasie rzeczywistym.", + "switchToHistorical": "Przełącz na widok historyczny", + "systemNotFound": "Nie znaleziono wybranego systemu. Wybierz inny system." }, "chart": { "cpu": { - "title": "", - "subtitle": "", - "series": "" + "title": "Użycie CPU", + "subtitle": "Średnie wykorzystanie procesora w całym systemie", + "series": "CPU" }, "memory": { - "title": "", - "subtitle": "", - "series": "", - "cache": "" + "title": "Użycie Pamięci", + "subtitle": "Dokładne wykorzystanie w odnotowanym czasie", + "series": "W Użyciu", + "cache": "Cache/Buffers" }, "disk": { - "title": "", - "subtitle": "", - "series": "" + "title": "Użycie Dysku", + "subtitle": "Użycie partycji root", + "series": "W Użyciu" }, "diskIO": { - "title": "", - "subtitle": "", - "read": "", - "write": "" + "title": "I/O Dysku", + "subtitle": "Przepustowość głównego systemu plików", + "read": "Odczyt", + "write": "Zapis" }, "network": { - "title": "", - "subtitle": "", - "sent": "", - "recv": "" + "title": "Przepustowość", + "subtitle": "Ruch sieciowy interfejsów publicznych", + "sent": "Wysłane", + "recv": "Odebrane" }, "dockerCpu": { - "title": "", - "subtitle": "" + "title": "Użycie CPU Docker", + "subtitle": "Średnie wykorzystanie procesora kontenerów" }, "dockerMemory": { - "title": "", - "subtitle": "" + "title": "Wykorzystanie pamięci przez Docker", + "subtitle": "Użycie pamięci przez kontenery Docker" }, "dockerNetwork": { - "title": "", - "subtitle": "" + "title": "I/O Sieci Dockera", + "subtitle": "Ruch sieciowy kontenerów Docker" } } } @@ -4133,13 +4153,18 @@ "title": "Nie znaleziono", "text": "Nie znaleziono żądanego zasobu." }, + "error": { + "title": "Nie można załadować tej strony", + "text": "Wystąpił nieoczekiwany błąd podczas ładowania tej strony. Twoja nawigacja jest nadal dostępna — spróbuj załadować ponownie lub użyj innej sekcji z paska bocznego.", + "digest": "Odniesienie błędu: {digest}" + }, "navbar": { "items": { "home": "Strona główna", "boards": "Tablice", "apps": "Aplikacje", "integrations": "Integracje", - "customWidgets": "", + "customWidgets": "Niestandardowe Widgety", "searchEngies": "Wyszukiwarki", "medias": "Media", "users": { @@ -4159,7 +4184,7 @@ "api": "API", "certificates": "Certyfikaty", "tasks": "Zadania", - "backup": "" + "backup": "Kopia Zapasowa" } }, "settings": "Ustawienia", @@ -4240,7 +4265,7 @@ "private": "Ta tablica jest prywatna" }, "noResults": { - "title": "" + "title": "Brak tablic" }, "modal": { "createBoard": { @@ -4274,20 +4299,20 @@ "firstDayOfWeek": "Pierwszy dzień tygodnia", "accessibility": "Ułatwienia dostępu", "onboardingTours": { - "title": "", - "description": "", - "reset": "", + "title": "Samouczek", + "description": "Zresetuj samouczki, aby zobaczyć je ponownie podczas następnej wizyty w obszarze zarządzania lub na tablicy.", + "reset": "Zresetuj samouczki", "resetSuccess": { - "title": "", - "message": "" + "title": "Zresetuj samouczki", + "message": "Samouczki pojawią się ponownie podczas następnej wizyty" }, "status": { - "completed": "", - "notStarted": "" + "completed": "Ukończony", + "notStarted": "Nie rozpoczęty" }, "name": { - "management": "", - "dashboard": "" + "management": "Zarządzanie", + "dashboard": "Tablica" } } } @@ -4475,7 +4500,7 @@ "defaultColorScheme": { "label": "Domyślny schemat kolorów", "options": { - "auto": "", + "auto": "Auto", "light": "Jasny", "dark": "Ciemny" } @@ -4578,7 +4603,10 @@ "label": "Tracearr" }, "umamiActiveVisitors": { - "label": "" + "label": "Aktywni odwiedzający Umami" + }, + "vpn": { + "label": "Status VPN" } }, "interval": { @@ -4687,213 +4715,213 @@ } }, "mcp": { - "label": "", - "title": "", - "subtitle": "", - "description": "", - "noApiKey": "", - "createApiKeyButton": "", + "label": "AI / MCP", + "title": "Integracja AI", + "subtitle": "Model Context Protocol (MCP) Server", + "description": "Połącz asystentów AI do twojej instancji Homarr i kontroluj cały homelab poprzez naturalny język. Szukaj i proś o multimedia na Overseerr, zarządzaj konterami Docker, włączaj/wyłączaj Pi-hole, sprawdź status zdrowia, kontroluj urządzenia Home Assistant i więcej - wszystko to przez asystenta AI.", + "noApiKey": "Potrzebujesz klucza API do uwierzytelnienia z serwerem MCP.", + "createApiKeyButton": "Utwórz klucz API", "endpoint": { - "title": "" + "title": "MCP Endpoint" }, "authentication": { - "title": "", - "intro": "", + "title": "Authentication", + "intro": "Możesz uwierzytelnić się z serwerem MCP przy użyciu klucza API lub OAuth. Obie metody zapewniają taki sam poziom dostępu.", "apiKey": { - "title": "", - "description": "" + "title": "API Key (manual)", + "description": "W nagłówku ApiKey przekaż pełny klucz API w konfiguracji klienta MCP." }, "oauth": { - "title": "", - "description": "", - "hint": "" + "title": "OAuth (browser login)", + "description": "Klienci obsługujący OAuth (tacy jak Claude Code, opencode lub Cursor) mogą uwierzytelniać się poprzez otwarcie okna przeglądarki. Uruchom polecenie authi w swoim kliencie, zaloguj się do Homarr w przeglądarce i jesteś połączony — klucz API jest tworzony automatycznie.", + "hint": "Tokeny OAuth pokazują się jako zwykłe klucze API w zakładce Uwierzytelniania. Możesz je cofnąć w każdej chwili." }, - "permissions": "" + "permissions": "Klucz API określa uprawnienia dostępne dla AI — działa z takim samym poziomem dostępu, jak właściciel klucza." }, "clientConfig": { - "title": "", + "title": "Konfiguracja klienta", "streamableHttp": { - "recommended": "", - "title": "", - "worksWith": "", - "addToConfig": "", - "cursor": "", - "claudeDesktop": "", - "claudeCode": "", - "vsCodeCopilot": "", - "replaceHint": "" + "recommended": "Rekomendowane", + "title": "Streamowalny HTTP", + "worksWith": "Działą z Cursor, Claude Desktop, VS Code Copliot Windsurf, Claude Code", + "addToConfig": "Dodaj do pliku konfiguracji klienta MCP:", + "cursor": "{name}: {file} w twoim projekcie lub ustawieniach globalnych", + "claudeDesktop": "{name}: {file}", + "claudeCode": "{name}: {file} lub użyj {command}", + "vsCodeCopilot": "{name}: {file} w ustawieniach obszaru roboczego", + "replaceHint": "Zastąp zastępczy symbol kluczem API, który otrzymałeś podczas jego tworzenia." }, "stdio": { - "title": "", - "description": "" + "title": "stdio (starsze klienty przez mpc-remote bridge)", + "description": "Dla klientów, którzy obsługują tylko transport stdio. Wymaga zainstalowania lokalnie {dependency} (Node.js):" } }, "testConnection": { - "title": "", - "description": "", - "hint": "" + "title": "Przetestuj Swoje Połączenie", + "description": "Uruchom to w swoim terminalu, aby sprawdzić, czy endpoint jest osiągalny, a Twój klucz API działa:", + "hint": "Pomyślna odpowiedź zwraca funkcje serwera MCP. Jeżeli widzisz błąd, sprawdź odpowiedź JSON po wskazówki." }, "availableTools": { - "title": "", - "description": "", - "columnType": "", - "columnName": "", - "columnDescription": "" + "title": "Dostępne Narzędzia", + "description": "Dostępne narzędzia: {count}. Rozszerz kategorię, aby zobaczyć poszczególne narzędzia i ich opisy.", + "columnType": "Typ", + "columnName": "Narzędzie", + "columnDescription": "Opis" }, "troubleshooting": { - "title": "", + "title": "Rozwiązywanie problemów", "missingHeader": { - "title": "", - "description": "" + "title": "\"Missing ApiKey header\"", + "description": "Twój klient nie wysyła nagłówka {header}. Zweryfikuj czy sekcja {section} w konfiguracji MCP jest poprawna. Niektórzy klienci wymagają ponownego uruchomienia po zmianie konfiguracji." }, "invalidFormat": { - "title": "", - "description": "" + "title": "\"Invalid ApiKey format\"", + "description": "Upewnij się, że skopiowałeś pełny klucz API, a nie tylko jego część." }, "invalidKey": { - "title": "", - "description": "" + "title": "\"Invalid API key\"", + "description": "Identyfikator klucza nie został znaleziony lub token jest nieprawidłowy. Klucze API można wyświetlić tylko raz po utworzeniu. Jeśli go zgubiłeś, usuń stary klucz i utwórz nowy." }, "connectionRefused": { - "title": "", - "description": "" + "title": "Odmowa połączenia / przekroczenie czasu połączenia", + "description": "Upewnij się, że adres URL jest dostępny od klienta AI. Jeśli Homarr działa za odwróconym proxy lub w Docker, użyj zewnętrznego adresu URL (nie {localhost}). Sprawdź, czy port jest poprawny (domyślnie: 7575 dla Docker)." }, "toolsNotAppearing": { - "title": "", - "description": "" + "title": "Narzędzia nie pojawiają się", + "description": "Niektóre narzędzia wymagają konfiguracji specyficznych integracji (np. Docker, Overseerr). Lista narzędzi zależy od tego, co jest dostępne w twojej instancji Homarr." } }, - "copy": "", + "copy": "Kopiuj", "oneClick": { - "title": "", - "subtitle": "", - "vsCode": "", - "cursor": "", - "copyUrl": "", - "copied": "", - "copyHint": "", - "manualConfig": "" + "title": "Zainstaluj jednym kliknięciem", + "subtitle": "Używa OAuth - uwierzytelniaj w przeglądarce przy pierwszym użyciu", + "vsCode": "Zainstaluj w VS Code", + "cursor": "Zainstaluj w Cursor", + "copyUrl": "Kopiuj URL MCP", + "copied": "Skopiowano!", + "copyHint": "Dla ChatGPT, Claude Code, lub dowolnego klienta MCP", + "manualConfig": "Konfiguracja ręczna" } } } }, "backup": { - "title": "", - "metaTitle": "", + "title": "Pełna kopia bazy danych", + "metaTitle": "Kopia Zapasowa i Przywracanie", "export": { - "title": "", - "description": "", - "button": "", - "error": "", + "title": "Eksportuj Bazę Danych", + "description": "Pobierz pełną kopię zapasową bazy danych SQLite zawierającą wszystkie ustawienia, użytkowników, tablice i szyfrowane sekrety integracyji. Kopia zapasowa jest powiązana jako plik ZIP z kluczem szyfrowania i metadanymi.", + "button": "Pobierz Kopię Zapasową", + "error": "Eksportowanie nie powiodło się", "confirm": { - "title": "", - "warningTitle": "", - "warningBody": "", - "typePrompt": "", - "submit": "", - "cancel": "" + "title": "Eksport danych wrażliwych", + "warningTitle": "Ten eksport zawiera całą Twoją bazę danych.", + "warningBody": "Kopia zapasowa obejmuje wszystkich użytkowników, sekrety integracji (zaszyfrowane), klucze API, tablice i dane aplikacji. Przechowuj wyeksportowany plik bezpiecznie i nie udostępniaj go.", + "typePrompt": "Wpisz \"{phrase}\", aby kontynuować", + "submit": "Eksportuj Bazę Danych", + "cancel": "Anuluj" } }, "import": { - "title": "", - "description": "", - "warning": "", + "title": "Przywróć Bazę Danych", + "description": "Prześlij poprzednio wyeksportowaną kopię zapasową ZIP, aby przywrócić bazę danych. To zastąpi całą bazę danych. Jeśli kopia zapasowa została utworzona przy użyciu innego klucza szyfrowania, sekrety zostaną automatycznie ponownie zaszyfrowane.", + "warning": "Przywracanie kopii zapasowej całkowicie zastąpi Twoją aktualną bazę danych i zrestartuje aplikację. Tej akcji nie można cofnąć.", "dropzone": { - "title": "", - "description": "" + "title": "Upuść kopię zapasową ZIP tutaj lub kliknij aby przeglądać", + "description": "Akceptowane są tylko pliki .zip z kopii zapasowej Homarr SQLite" }, - "changeFile": "", - "button": "", + "changeFile": "Zmień plik", + "button": "Przywróć Bazę Danych", "success": { - "title": "", - "message": "" + "title": "Przywracanie Ukończone", + "message": "Baza danych została pomyślnie przywrócona. Aplikacja uruchamia się ponownie..." }, - "error": "" + "error": "Importowanie nie powiodło się" }, "preview": { - "title": "", - "exported": "", - "loading": "", - "errorTitle": "", - "entity": "", - "count": "", - "boards": "", + "title": "Zawartość kopii zapasowej", + "exported": "Wyeksportowano {date}", + "loading": "Analizowanie pliku kopii zapasowej...", + "errorTitle": "Podgląd Błędu", + "entity": "Encja", + "count": "Ilość", + "boards": "Tablice", "entities": { - "board": "", - "user": "", - "app": "", - "integration": "", - "item": "", - "media": "", - "search_engine": "" + "board": "Tablice", + "user": "Użytkownicy", + "app": "Aplikacje", + "integration": "Integracje", + "item": "Widgety", + "media": "Multimedia", + "search_engine": "Wyszukiwarki" }, - "analyzeError": "", - "invalidMissingMetadata": "", - "invalidMissingDb": "" + "analyzeError": "Nie udało się przeanalizować pliku kopii zapasowej", + "invalidMissingMetadata": "Nieprawidłowa kopia zapasowa: brakuje metadata.json", + "invalidMissingDb": "Nieprawidłowa kopia zapasowa: brakuje db.sqlite" }, "restore": { "dropzone": { - "title": "", - "description": "" + "title": "Upuść tutaj kopię zapasową ZIP", + "description": "Akceptuje pliki .zip z kopii zapasowej Homarr SQLite (maksymalnie 256 MB)" }, - "analyzing": "", - "changeFile": "", - "continueToRestore": "", - "tryAgain": "", - "analysisError": "", + "analyzing": "Analizowanie kopii zapasowej...", + "changeFile": "Zmień plik", + "continueToRestore": "Kontynuuj przywracanie", + "tryAgain": "Spróbuj ponownie", + "analysisError": "Błąd analizy", "preview": { - "title": "", - "exported": "", - "entity": "", - "count": "", - "boards": "", + "title": "Zawartość kopii zapasowej", + "exported": "Eksportowano {date}", + "entity": "Encja", + "count": "Ilość", + "boards": "Tablice", "entities": { - "board": "", - "user": "", - "app": "", - "integration": "", - "item": "", - "media": "", - "search_engine": "" + "board": "Tablice", + "user": "Użytkownicy", + "app": "Aplikacje", + "integration": "Integracje", + "item": "Widgety", + "media": "Multimedia", + "search_engine": "Wyszukiwarki" }, - "migrationsApplied": "", - "migrationsAppliedPlural": "", - "migrationsOlderVersion": "", - "migrationsOlderVersionPlural": "", - "migrationsUpToDate": "", - "upToDate": "", - "migrationsPending": "" + "migrationsApplied": "{count} migracja została zastosowana", + "migrationsAppliedPlural": "{count} migracje zostały zastosowane", + "migrationsOlderVersion": "Ta kopia zapasowa została wykonana ze starszej wersji Homarr. {count} migracja schematu została zastosowana, aby ją zaktualizować.", + "migrationsOlderVersionPlural": "Ta kopia zapasowa została wykonana ze starszej wersji Homarr. {count} migracje schematu zostały zastosowane, aby ją zaktualizować.", + "migrationsUpToDate": "Schemat bazy danych jest aktualny (zastosowano {count} migracji)", + "upToDate": "Na bieżąco", + "migrationsPending": "{count} oczekujące" }, "migration": { - "title": "", - "migrationLabel": "" + "title": "Stosowanie migracji ({current}/{total})", + "migrationLabel": "Migracja {index}" }, "confirm": { - "title": "", - "warningTitle": "", - "warningBody": "", - "typePrompt": "", - "cancel": "", - "submit": "" + "title": "Czynność niszczycielska", + "warningTitle": "To nieodwracalnie zastąpi całą Twoją bazę danych.", + "warningBody": "Wszystkie bieżące tablice, użytkownicy, aplikacje, integracje, ustawienia i sesje zostaną utracone. Tej czynności nie można cofnąć. Aplikacja zostanie uruchomiona ponownie po przywróceniu.", + "typePrompt": "Wpisz \"{phrase}\", aby kontynuować", + "cancel": "Anuluj", + "submit": "Przywrócono Bazę Danych" }, "progress": { - "title": "", - "extracting": "", - "migrating": "", - "encrypting": "", - "swapping": "", - "restarting": "" + "title": "Przywracanie bazy danych...", + "extracting": "Rozpakowywanie archiwum kopii zapasowej", + "migrating": "Wdrażanie migracji bazy danych", + "encrypting": "Ponowne szyfrowanie sekretów integracji", + "swapping": "Zastępowanie pliku bazy danych", + "restarting": "Ponowne uruchamianie serwera" }, "complete": { - "title": "", - "message": "" + "title": "Przywrócono kopię zapasową", + "message": "Baza danych została pomyślnie przywrócona. Serwer uruchamia się ponownie — odśwież stronę za kilka sekund." }, "failed": { - "title": "" + "title": "Przywracanie nie powiodło się" }, - "invalidMissingMetadata": "", - "invalidMissingDb": "", - "analyzeError": "" + "invalidMissingMetadata": "Nieprawidłowa kopia zapasowa: brakuje metadata.json", + "invalidMissingDb": "Nieprawidłowa kopia zapasowa: brakuje db.sqlite", + "analyzeError": "Nie udało się przeanalizować kopii zapasowej" } } }, @@ -4943,8 +4971,8 @@ "search": "Szukaj wśród {count} kontenerów", "selected": "Wybrano {selectCount} z {totalCount} kontenerów", "footer": "Łącznie {count} kontenerów", - "totalCpu": "", - "totalMemory": "" + "totalCpu": "Całkowity CPU: {cpu}%", + "totalMemory": "Całkowita Pamięć: {memory}" }, "field": { "name": { @@ -4977,15 +5005,15 @@ "label": "Porty" }, "host": { - "label": "" + "label": "Host" } }, "action": { "title": "Akcje", "logs": { - "label": "", + "label": "Wyświetl logi", "page": { - "title": "" + "title": "Logi {name}" } }, "start": { @@ -5071,7 +5099,8 @@ } }, "error": { - "internalServerError": "Nie udało się pobrać kontenerów Dockera" + "internalServerError": "Nie udało się pobrać kontenerów Dockera", + "noColumns": "" } }, "kubernetes": { @@ -5380,14 +5409,14 @@ } }, "custom-widgets": { - "label": "", + "label": "Niestandardowe Wigety", "new": { - "label": "" + "label": "Nowy" }, "edit": { - "label": "", + "label": "Edytuj", "flow": { - "label": "" + "label": "Flow" } } }, @@ -5414,7 +5443,7 @@ "docker": { "label": "Docker", "logs": { - "label": "" + "label": "Logi" } }, "kubernetes": { @@ -5525,7 +5554,7 @@ "title": "Polecenia globalne", "option": { "colorScheme": { - "auto": "", + "auto": "Przełącz tryb wyglądu", "light": "Przełącz na tryb jasny", "dark": "Przełącz na tryb ciemny" }, @@ -5566,138 +5595,138 @@ } }, "preferences": { - "title": "", + "title": "Preferencje", "children": { "detail": { - "title": "", - "backHint": "", - "backAction": "" + "title": "Wybierz preferencje do zmiany", + "backHint": "Naciśnij Backspace lub użyj przycisku wstecz, aby wrócić", + "backAction": "Wróć" } }, "notification": { "error": { - "message": "" + "message": "Nie można zaktualizować preferencji" } }, "option": { "preferences": { - "label": "" + "label": "Otwórz preferencje" }, "boolean": { - "enabled": "", - "disabled": "" + "enabled": "Aktywny", + "disabled": "Nieaktywny" }, "board": { - "none": "", - "unavailable": "" + "none": "Brak", + "unavailable": "Aktualnie wybrane (niedostępne)" }, "searchEngine": { - "none": "", - "unavailable": "" + "none": "Brak", + "unavailable": "Aktualnie wybrane (niedostępne)" }, "colorScheme": { - "label": "", + "label": "Paleta kolorów", "children": { "detail": { - "title": "" + "title": "Wybierz swoją palete kolorów" } } }, "locale": { - "label": "", + "label": "Język", "children": { "detail": { - "title": "" + "title": "Wybierz preferowany język" } } }, "defaultSearchEngineId": { - "label": "", + "label": "Domyślna wyszukiwarka", "children": { "detail": { - "title": "" + "title": "Wybierz domyślną wyszukiwarkę" } } }, "openSearchInNewTab": { - "label": "", + "label": "Otwórz wyniki wyszukiwania w nowej karcie", "children": { "detail": { - "title": "" + "title": "Otwórz wyniki wyszukiwania w nowej karcie" } } }, "ddgBangs": { - "label": "", + "label": "Włącz DuckDuckGo bangs", "children": { "detail": { - "title": "" + "title": "Włącz DuckDuckGo bangs" } } }, "firstDayOfWeek": { - "label": "", + "label": "Pierwszy dzień tygodnia", "children": { "detail": { - "title": "" + "title": "Wybierz pierwszy dzień tygodnia" } } }, "homeBoardId": { - "label": "", + "label": "Tablica główna", "children": { "detail": { - "title": "" + "title": "Wybierz swoją tablicę główną" } } }, "mobileHomeBoardId": { - "label": "", + "label": "Mobilna tablica główna", "children": { "detail": { - "title": "" + "title": "Wybierz swoją mobilną tablicę główną" } } }, "pingIconsEnabled": { - "label": "", + "label": "Użyj ikon dla pingów", "children": { "detail": { - "title": "" + "title": "Użyj ikon dla pingów" } } }, "fullPreferencesPage": { - "label": "" + "label": "Otwórz pełną stronę preferencji" } } } } }, "media": { - "help": "", + "help": "Szukaj filmów lub seriali do żądania", "requestMovie": "Poproś o film", "requestSeries": "Poproś o serial", "openIn": "Otwórz w {kind}", "group": { - "title": "" + "title": "Filmy i seriale" }, "action": { "search": { - "label": "", - "description": "", + "label": "Szukaj filmów lub seriali", + "description": "Zacznij wpisywać, aby wyszukać integracje z twoimi żądaniami mediów", "disabled": { - "noIntegration": "", - "scoped": "" + "noIntegration": "Dodaj integrację Overseerr, Jellyseerr lub Seerr, aby włączyć wyszukiwanie multimediów.", + "scoped": "Ten widget nie ma dostępnej integracji z żądaniem mediów." } } }, "availability": { - "available": "", - "partiallyAvailable": "", - "processing": "", - "requested": "", - "pending": "" + "available": "Dostępne", + "partiallyAvailable": "Częściowo", + "processing": "Przetwarzane", + "requested": "Zapytane", + "pending": "Zapytane" } }, "external": { @@ -5705,13 +5734,13 @@ "group": { "searchEngine": { "title": "Wyszukiwarki", - "tipDdgBangs": "", + "tipDdgBangs": "Wskazówka: Możesz również użyć wszystkich DuckDuckGo Bangs tutaj, np. !something", "bang": { - "searchWithQuery": "", - "pressEnterToOpen": "", - "engineSelected": "", - "typeQueryToContinue": "", - "emptyBangHint": "" + "searchWithQuery": "Wyszukaj „{query}” za pomocą {label}", + "pressEnterToOpen": "Naciśnij Enter, aby otworzyć", + "engineSelected": "Wybrano {label} (!{bang})", + "typeQueryToContinue": "Wpisz zapytanie aby kontynuować", + "emptyBangHint": "Wpisz bang np. !yt, a następnie wciśnij Spację, aby wybrać" }, "children": { "action": { @@ -5975,10 +6004,10 @@ "header": { "season": "Sezon", "episodes": "Odcinki", - "status": "" + "status": "Status" }, "status": { - "requested": "" + "requested": "Zapytane" } }, "button": { @@ -6091,69 +6120,69 @@ } }, "onboardingTour": { - "learnMore": "", - "next": "", - "prev": "", - "done": "", - "skip": "", + "learnMore": "Przeczytaj dokumentację", + "next": "Dalej", + "prev": "Wstecz", + "done": "Gotowe", + "skip": "Pomiń samouczek", "manage": { "welcome": { - "title": "", - "description": "" + "title": "Witaj w Homarr!", + "description": "To jest twoje centrum zarządzania. Tutaj zarządzasz tablicami, aplikacjami, integracjami, użytkownikami i ustawieniami serwera. Zacznijmy samouczek z każdej sekcji." }, "boardsList": { - "title": "", - "description": "" + "title": "Twoje Tablice", + "description": "Oto wszystkie twoje Tablice. Każda tablica ma własny układ, tło i ustawienia dostępu. Możesz zobaczyć, kto stworzył każdą z nich i czy jest publiczna czy prywatna." }, "boardsCreate": { - "title": "", - "description": "" + "title": "Utwórz Tablicę", + "description": "Kliknij tutaj, aby utworzyć nową tablicę lub zaimportować go z poprzedniej instalacji Homarr. Każdy dział otrzymuje swój własny adres URL który możesz udostępnić." }, "appsList": { - "title": "", - "description": "" + "title": "Twoje aplikacje", + "description": "To są wszystkie aplikacje, które dodałeś — linki do swoich hostowanych usług. Każda aplikacja ma ikonę, opcjonalną kontrolę zdrowia i odznakę statusu na tablicy." }, "appsCreate": { - "title": "", - "description": "" + "title": "Dodaj Aplikację", + "description": "Zarejestruj nową usługę tutaj. Podaj nazwę, adres URL i ikonę — pojawi się ona jako kafelek na twoich tablicach z monitorowaniem statusu na żywo." }, "integrationsList": { - "title": "", - "description": "" + "title": "Twoje Integracje", + "description": "Są to usługi połączone z Homarr — Sonarr, Radarr, Plex, Jellyfin, i ponad 50 więcej. Wszystkie dane uwierzytelniające są szyfrowane." }, "integrationsCreate": { - "title": "", - "description": "" + "title": "Dodaj Integrację", + "description": "Połącz nową usługę tutaj. Wybierz typ, wprowadź adres URL i klucz API — Homarr sprawdzi połączenie przed zapisaniem." }, "usersList": { - "title": "", - "description": "" + "title": "Twoi Użytkownicy", + "description": "Zobacz wszystkich, którzy mają dostęp. Organizuj użytkowników w grupy i kontroluj, kto może przeglądać lub edytować dane tablice." }, "usersCreate": { - "title": "", - "description": "" + "title": "Utwórz Użytkownika", + "description": "Dodaj tutaj nowe konto użytkownika. Możesz również użyć linków z zaproszeniami do łatwego wdrożenia. To koniec samouczka — wszystko gotowe!" } }, "board": { "welcome": { - "title": "", - "description": "" + "title": "To jest twoja tablica", + "description": "Przeciągnij i upuść tablica, w której układasz aplikacje i widgety. Wciśnij Ctrl+K w dowolnym momencie, aby wyszukać aplikacje, tablice i akcje." }, "editMode": { - "title": "", - "description": "" + "title": "Tryb Edycji", + "description": "Kliknij tutaj, aby przejść do trybu edycji. Następnie możesz dodać widgety, aplikacje i sekcje — przeciągnij aby zmienić pozycję, zmień rozmiar z przeciągając narożnik. Kliknij ponownie, aby zapisać zmiany." }, "settings": { - "title": "", - "description": "" + "title": "Ustawienia Tablicy", + "description": "Zmień nazwę, kolumny, tło, kolory i kto ma dostęp do tablicy. Każda tablica może być publiczna lub prywatna." }, "switcher": { - "title": "", - "description": "" + "title": "Przełącz tablice", + "description": "Przełączaj się między tablicami. Twórz oddzielne tablice dla różnych celów - multimedia, zarządzanie siecią, dedykowane dla użytkownia, lub cokolwiek chcesz." }, "userMenu": { - "title": "", - "description": "" + "title": "Twój Profil", + "description": "Zmień swoją paletę kolorów, język, tablicę startową i preferencje. Możesz również przejść z tąd do obszaru zarządzania." } } } diff --git a/packages/translation/src/lang/pt-br.json b/packages/translation/src/lang/pt-br.json index 2c096f760..20c0d48ef 100644 --- a/packages/translation/src/lang/pt-br.json +++ b/packages/translation/src/lang/pt-br.json @@ -1021,6 +1021,7 @@ "mediaMonitoring": "Monitoramento", "speedtest": "Teste de Velocidade", "analytics": "Análises", + "vpn": "", "mediaLibrary": "Biblioteca de mídia", "uptimeMonitoring": "Monitoramento do Uptime" }, @@ -1412,7 +1413,10 @@ "here": "aqui", "iconPicker": { "label": "URL do ícone", - "header": "Digite o nome ou os objetos para filtrar os ícones... O Homarr pesquisará {countIcons} ícones para você." + "header": "Digite o nome ou os objetos para filtrar os ícones... O Homarr pesquisará {countIcons} ícones para você.", + "headerLoading": "", + "noResults": "", + "error": "" }, "colorScheme": { "options": { @@ -1464,6 +1468,7 @@ "switchToDarkMode": "Alternar para o modo escuro", "switchToLightMode": "Alternar para o modo claro", "management": "Gerenciamento", + "docker": "", "preferences": "Suas preferências", "logout": "Sair", "login": "“Login”", @@ -2352,6 +2357,9 @@ "name": "Estatísticas do Docker", "description": "Estatísticas dos seus contêineres (Esse widget só pode ser adicionado com privilégios de administrador)", "option": { + "columns": { + "label": "" + }, "enableRowSorting": { "label": "Ativar ordenação de itens" }, @@ -2405,6 +2413,12 @@ "title": "Instâncias" } }, + "vpn": { + "name": "", + "description": "", + "serviceUnavailable": "", + "option": {} + }, "ups": { "name": "UPS", "description": "Monitore o status de seus dispositivos UPS através de um servidor NUT", @@ -3639,6 +3653,9 @@ "error": { "internalServerError": "" }, + "empty": { + "noSystems": "" + }, "metric": { "cpu": "", "memory": "", @@ -3712,6 +3729,9 @@ } }, "selectSystem": "", + "empty": { + "noSystems": "" + }, "error": { "internalServerError": "", "liveConnectionFailed": "", @@ -4133,6 +4153,11 @@ "title": "Não encontrado", "text": "Não foi possível encontrar o recurso solicitado" }, + "error": { + "title": "", + "text": "", + "digest": "" + }, "navbar": { "items": { "home": "Início", @@ -4579,6 +4604,9 @@ }, "umamiActiveVisitors": { "label": "Visitantes Ativos do Umami" + }, + "vpn": { + "label": "" } }, "interval": { @@ -5071,7 +5099,8 @@ } }, "error": { - "internalServerError": "Falha ao buscar contêineres Docker" + "internalServerError": "Falha ao buscar contêineres Docker", + "noColumns": "" } }, "kubernetes": { diff --git a/packages/translation/src/lang/pt.json b/packages/translation/src/lang/pt.json index 48bf163a9..61ecdfe44 100644 --- a/packages/translation/src/lang/pt.json +++ b/packages/translation/src/lang/pt.json @@ -1021,6 +1021,7 @@ "mediaMonitoring": "", "speedtest": "", "analytics": "", + "vpn": "", "mediaLibrary": "", "uptimeMonitoring": "" }, @@ -1412,7 +1413,10 @@ "here": "", "iconPicker": { "label": "", - "header": "" + "header": "", + "headerLoading": "", + "noResults": "", + "error": "" }, "colorScheme": { "options": { @@ -1464,6 +1468,7 @@ "switchToDarkMode": "", "switchToLightMode": "", "management": "", + "docker": "", "preferences": "", "logout": "", "login": "", @@ -2352,6 +2357,9 @@ "name": "", "description": "", "option": { + "columns": { + "label": "" + }, "enableRowSorting": { "label": "" }, @@ -2405,6 +2413,12 @@ "title": "" } }, + "vpn": { + "name": "", + "description": "", + "serviceUnavailable": "", + "option": {} + }, "ups": { "name": "", "description": "", @@ -3639,6 +3653,9 @@ "error": { "internalServerError": "" }, + "empty": { + "noSystems": "" + }, "metric": { "cpu": "", "memory": "", @@ -3712,6 +3729,9 @@ } }, "selectSystem": "", + "empty": { + "noSystems": "" + }, "error": { "internalServerError": "", "liveConnectionFailed": "", @@ -4133,6 +4153,11 @@ "title": "", "text": "" }, + "error": { + "title": "", + "text": "", + "digest": "" + }, "navbar": { "items": { "home": "", @@ -4579,6 +4604,9 @@ }, "umamiActiveVisitors": { "label": "" + }, + "vpn": { + "label": "" } }, "interval": { @@ -5071,7 +5099,8 @@ } }, "error": { - "internalServerError": "" + "internalServerError": "", + "noColumns": "" } }, "kubernetes": { diff --git a/packages/translation/src/lang/ro.json b/packages/translation/src/lang/ro.json index 32081e109..c3f1c60ac 100644 --- a/packages/translation/src/lang/ro.json +++ b/packages/translation/src/lang/ro.json @@ -1021,6 +1021,7 @@ "mediaMonitoring": "", "speedtest": "", "analytics": "", + "vpn": "", "mediaLibrary": "", "uptimeMonitoring": "" }, @@ -1412,7 +1413,10 @@ "here": "", "iconPicker": { "label": "", - "header": "" + "header": "", + "headerLoading": "", + "noResults": "", + "error": "" }, "colorScheme": { "options": { @@ -1464,6 +1468,7 @@ "switchToDarkMode": "", "switchToLightMode": "", "management": "", + "docker": "", "preferences": "Preferințele dvs.", "logout": "", "login": "Utilizator", @@ -2352,6 +2357,9 @@ "name": "", "description": "", "option": { + "columns": { + "label": "" + }, "enableRowSorting": { "label": "" }, @@ -2405,6 +2413,12 @@ "title": "" } }, + "vpn": { + "name": "", + "description": "", + "serviceUnavailable": "", + "option": {} + }, "ups": { "name": "", "description": "", @@ -3639,6 +3653,9 @@ "error": { "internalServerError": "" }, + "empty": { + "noSystems": "" + }, "metric": { "cpu": "", "memory": "", @@ -3712,6 +3729,9 @@ } }, "selectSystem": "", + "empty": { + "noSystems": "" + }, "error": { "internalServerError": "", "liveConnectionFailed": "", @@ -4133,6 +4153,11 @@ "title": "", "text": "" }, + "error": { + "title": "", + "text": "", + "digest": "" + }, "navbar": { "items": { "home": "Acasă", @@ -4579,6 +4604,9 @@ }, "umamiActiveVisitors": { "label": "" + }, + "vpn": { + "label": "" } }, "interval": { @@ -5071,7 +5099,8 @@ } }, "error": { - "internalServerError": "" + "internalServerError": "", + "noColumns": "" } }, "kubernetes": { diff --git a/packages/translation/src/lang/ru.json b/packages/translation/src/lang/ru.json index edca67abd..005b0fc92 100644 --- a/packages/translation/src/lang/ru.json +++ b/packages/translation/src/lang/ru.json @@ -1021,6 +1021,7 @@ "mediaMonitoring": "", "speedtest": "", "analytics": "", + "vpn": "", "mediaLibrary": "", "uptimeMonitoring": "" }, @@ -1412,7 +1413,10 @@ "here": "здесь", "iconPicker": { "label": "URL иконки", - "header": "Введите название или объекты для фильтрации иконок... Homarr найдёт для вас среди {countIcons} иконок." + "header": "Введите название или объекты для фильтрации иконок... Homarr найдёт для вас среди {countIcons} иконок.", + "headerLoading": "", + "noResults": "", + "error": "" }, "colorScheme": { "options": { @@ -1464,6 +1468,7 @@ "switchToDarkMode": "Переключить на тёмную тему", "switchToLightMode": "Переключить на светлую тему", "management": "Управление", + "docker": "", "preferences": "Настройки", "logout": "Выйти", "login": "Вход", @@ -2352,6 +2357,9 @@ "name": "Статистика Docker", "description": "Статистика контейнеров (этот виджет можно добавить только с правами администратора)", "option": { + "columns": { + "label": "" + }, "enableRowSorting": { "label": "Включить сортировку элементов" }, @@ -2405,6 +2413,12 @@ "title": "Экземпляры" } }, + "vpn": { + "name": "", + "description": "", + "serviceUnavailable": "", + "option": {} + }, "ups": { "name": "", "description": "", @@ -3639,6 +3653,9 @@ "error": { "internalServerError": "" }, + "empty": { + "noSystems": "" + }, "metric": { "cpu": "", "memory": "", @@ -3712,6 +3729,9 @@ } }, "selectSystem": "", + "empty": { + "noSystems": "" + }, "error": { "internalServerError": "", "liveConnectionFailed": "", @@ -4133,6 +4153,11 @@ "title": "Не найдено", "text": "Запрошенный ресурс не найден" }, + "error": { + "title": "", + "text": "", + "digest": "" + }, "navbar": { "items": { "home": "Главная", @@ -4579,6 +4604,9 @@ }, "umamiActiveVisitors": { "label": "" + }, + "vpn": { + "label": "" } }, "interval": { @@ -5071,7 +5099,8 @@ } }, "error": { - "internalServerError": "Произошла ошибка при обновлении данных о контейнерах" + "internalServerError": "Произошла ошибка при обновлении данных о контейнерах", + "noColumns": "" } }, "kubernetes": { diff --git a/packages/translation/src/lang/sk.json b/packages/translation/src/lang/sk.json index 3e44a4be6..748322cd8 100644 --- a/packages/translation/src/lang/sk.json +++ b/packages/translation/src/lang/sk.json @@ -1021,6 +1021,7 @@ "mediaMonitoring": "", "speedtest": "", "analytics": "", + "vpn": "", "mediaLibrary": "", "uptimeMonitoring": "" }, @@ -1412,7 +1413,10 @@ "here": "tu", "iconPicker": { "label": "URL ikony", - "header": "Zadajte názov alebo objekty na filtrovanie ikon... Homarr pre vás vyhľadá ikony na stránke {countIcons} ." + "header": "Zadajte názov alebo objekty na filtrovanie ikon... Homarr pre vás vyhľadá ikony na stránke {countIcons} .", + "headerLoading": "", + "noResults": "", + "error": "" }, "colorScheme": { "options": { @@ -1464,6 +1468,7 @@ "switchToDarkMode": "Prepnúť na tmavý motív", "switchToLightMode": "Prepnúť na svetlý motív", "management": "Administrácia", + "docker": "", "preferences": "Vaše preferencie", "logout": "Odhlásiť", "login": "Prihlásiť sa", @@ -2352,6 +2357,9 @@ "name": "Docker stav", "description": "Štatistiky vašich kontajnerov (Túto miniaplikáciu je možné pridať iba s oprávneniami správcu)", "option": { + "columns": { + "label": "" + }, "enableRowSorting": { "label": "" }, @@ -2405,6 +2413,12 @@ "title": "" } }, + "vpn": { + "name": "", + "description": "", + "serviceUnavailable": "", + "option": {} + }, "ups": { "name": "", "description": "", @@ -3639,6 +3653,9 @@ "error": { "internalServerError": "" }, + "empty": { + "noSystems": "" + }, "metric": { "cpu": "", "memory": "", @@ -3712,6 +3729,9 @@ } }, "selectSystem": "", + "empty": { + "noSystems": "" + }, "error": { "internalServerError": "", "liveConnectionFailed": "", @@ -4133,6 +4153,11 @@ "title": "Nenájdené", "text": "Nepodarilo sa nájsť požadovaný zdroj" }, + "error": { + "title": "", + "text": "", + "digest": "" + }, "navbar": { "items": { "home": "Domovská stránka", @@ -4579,6 +4604,9 @@ }, "umamiActiveVisitors": { "label": "" + }, + "vpn": { + "label": "" } }, "interval": { @@ -5071,7 +5099,8 @@ } }, "error": { - "internalServerError": "Nepodarilo sa načítať kontajnery Docker" + "internalServerError": "Nepodarilo sa načítať kontajnery Docker", + "noColumns": "" } }, "kubernetes": { diff --git a/packages/translation/src/lang/sl.json b/packages/translation/src/lang/sl.json index eec31b510..7f597ff3c 100644 --- a/packages/translation/src/lang/sl.json +++ b/packages/translation/src/lang/sl.json @@ -1021,6 +1021,7 @@ "mediaMonitoring": "", "speedtest": "", "analytics": "", + "vpn": "", "mediaLibrary": "", "uptimeMonitoring": "" }, @@ -1412,7 +1413,10 @@ "here": "", "iconPicker": { "label": "", - "header": "" + "header": "", + "headerLoading": "", + "noResults": "", + "error": "" }, "colorScheme": { "options": { @@ -1464,6 +1468,7 @@ "switchToDarkMode": "", "switchToLightMode": "Svetli način", "management": "Nastavitve", + "docker": "", "preferences": "Prilagoditev", "logout": "Odjava", "login": "Prijava", @@ -2352,6 +2357,9 @@ "name": "", "description": "", "option": { + "columns": { + "label": "" + }, "enableRowSorting": { "label": "" }, @@ -2405,6 +2413,12 @@ "title": "" } }, + "vpn": { + "name": "", + "description": "", + "serviceUnavailable": "", + "option": {} + }, "ups": { "name": "", "description": "", @@ -3639,6 +3653,9 @@ "error": { "internalServerError": "" }, + "empty": { + "noSystems": "" + }, "metric": { "cpu": "", "memory": "", @@ -3712,6 +3729,9 @@ } }, "selectSystem": "", + "empty": { + "noSystems": "" + }, "error": { "internalServerError": "", "liveConnectionFailed": "", @@ -4133,6 +4153,11 @@ "title": "", "text": "" }, + "error": { + "title": "", + "text": "", + "digest": "" + }, "navbar": { "items": { "home": "Domov", @@ -4579,6 +4604,9 @@ }, "umamiActiveVisitors": { "label": "" + }, + "vpn": { + "label": "" } }, "interval": { @@ -5071,7 +5099,8 @@ } }, "error": { - "internalServerError": "" + "internalServerError": "", + "noColumns": "" } }, "kubernetes": { diff --git a/packages/translation/src/lang/sv.json b/packages/translation/src/lang/sv.json index 1747bc399..fdd8f4bd8 100644 --- a/packages/translation/src/lang/sv.json +++ b/packages/translation/src/lang/sv.json @@ -1021,6 +1021,7 @@ "mediaMonitoring": "", "speedtest": "", "analytics": "", + "vpn": "", "mediaLibrary": "", "uptimeMonitoring": "" }, @@ -1412,7 +1413,10 @@ "here": "", "iconPicker": { "label": "", - "header": "" + "header": "", + "headerLoading": "", + "noResults": "", + "error": "" }, "colorScheme": { "options": { @@ -1464,6 +1468,7 @@ "switchToDarkMode": "Byt till mörkt läge", "switchToLightMode": "Byt till ljust läge", "management": "Administration", + "docker": "", "preferences": "Dina inställningar", "logout": "Logga ut", "login": "Logga in", @@ -2352,6 +2357,9 @@ "name": "", "description": "", "option": { + "columns": { + "label": "" + }, "enableRowSorting": { "label": "" }, @@ -2405,6 +2413,12 @@ "title": "" } }, + "vpn": { + "name": "", + "description": "", + "serviceUnavailable": "", + "option": {} + }, "ups": { "name": "", "description": "", @@ -3639,6 +3653,9 @@ "error": { "internalServerError": "" }, + "empty": { + "noSystems": "" + }, "metric": { "cpu": "", "memory": "", @@ -3712,6 +3729,9 @@ } }, "selectSystem": "", + "empty": { + "noSystems": "" + }, "error": { "internalServerError": "", "liveConnectionFailed": "", @@ -4133,6 +4153,11 @@ "title": "Hittades ej", "text": "" }, + "error": { + "title": "", + "text": "", + "digest": "" + }, "navbar": { "items": { "home": "Hem", @@ -4579,6 +4604,9 @@ }, "umamiActiveVisitors": { "label": "" + }, + "vpn": { + "label": "" } }, "interval": { @@ -5071,7 +5099,8 @@ } }, "error": { - "internalServerError": "" + "internalServerError": "", + "noColumns": "" } }, "kubernetes": { diff --git a/packages/translation/src/lang/tr.json b/packages/translation/src/lang/tr.json index a3acc6650..feae933c0 100644 --- a/packages/translation/src/lang/tr.json +++ b/packages/translation/src/lang/tr.json @@ -1021,6 +1021,7 @@ "mediaMonitoring": "", "speedtest": "", "analytics": "", + "vpn": "", "mediaLibrary": "", "uptimeMonitoring": "" }, @@ -1412,7 +1413,10 @@ "here": "buraya", "iconPicker": { "label": "Simge URL'si", - "header": "Simgeleri filtrelemek için isim veya nesne yazın... Homarr sizin için {countIcons} simge arasında arama yapacaktır." + "header": "Simgeleri filtrelemek için isim veya nesne yazın... Homarr sizin için {countIcons} simge arasında arama yapacaktır.", + "headerLoading": "", + "noResults": "", + "error": "" }, "colorScheme": { "options": { @@ -1464,6 +1468,7 @@ "switchToDarkMode": "Koyu Temaya Geç", "switchToLightMode": "Aydınlık Temaya Geç", "management": "Yönetim", + "docker": "", "preferences": "Tercihleriniz", "logout": "Oturumu kapat", "login": "Giriş", @@ -2352,6 +2357,9 @@ "name": "Docker istatistikleri", "description": "Konteynerlerinizin istatistikleri (Bu widget yalnızca yönetici ayrıcalıklarıyla eklenebilir)", "option": { + "columns": { + "label": "" + }, "enableRowSorting": { "label": "Enable items sorting" }, @@ -2405,6 +2413,12 @@ "title": "Instances" } }, + "vpn": { + "name": "", + "description": "", + "serviceUnavailable": "", + "option": {} + }, "ups": { "name": "", "description": "", @@ -3639,6 +3653,9 @@ "error": { "internalServerError": "" }, + "empty": { + "noSystems": "" + }, "metric": { "cpu": "", "memory": "", @@ -3712,6 +3729,9 @@ } }, "selectSystem": "", + "empty": { + "noSystems": "" + }, "error": { "internalServerError": "", "liveConnectionFailed": "", @@ -4133,6 +4153,11 @@ "title": "Bulunamadı", "text": "İstenen kaynak bulunamadı" }, + "error": { + "title": "", + "text": "", + "digest": "" + }, "navbar": { "items": { "home": "Ana Sayfa", @@ -4579,6 +4604,9 @@ }, "umamiActiveVisitors": { "label": "" + }, + "vpn": { + "label": "" } }, "interval": { @@ -5071,7 +5099,8 @@ } }, "error": { - "internalServerError": "Docker konteynerlerları getirilemedi" + "internalServerError": "Docker konteynerlerları getirilemedi", + "noColumns": "" } }, "kubernetes": { diff --git a/packages/translation/src/lang/uk.json b/packages/translation/src/lang/uk.json index a6305e4e1..2b7a4b9b5 100644 --- a/packages/translation/src/lang/uk.json +++ b/packages/translation/src/lang/uk.json @@ -1021,6 +1021,7 @@ "mediaMonitoring": "", "speedtest": "", "analytics": "", + "vpn": "", "mediaLibrary": "Медіа бібліотека", "uptimeMonitoring": "" }, @@ -1412,7 +1413,10 @@ "here": "тут", "iconPicker": { "label": "", - "header": "" + "header": "", + "headerLoading": "", + "noResults": "", + "error": "" }, "colorScheme": { "options": { @@ -1464,6 +1468,7 @@ "switchToDarkMode": "Перемикнути на темну тему", "switchToLightMode": "Перемикнути на світлу тему", "management": "Управління", + "docker": "", "preferences": "Ваші налаштування", "logout": "Вийти", "login": "Логін", @@ -1938,7 +1943,7 @@ "addTable": "Додати таблицю", "deleteTable": "Видалити таблицю", "colorCell": "", - "mergeCell": "", + "mergeCell": "Ввімкнути/вимкнути об'єднання клітини", "addColumnLeft": "", "addColumnRight": "", "deleteColumn": "", @@ -2101,13 +2106,13 @@ "label": "" }, "1y": { - "label": "" + "label": "1 рік" }, "2y": { "label": "" }, "5y": { - "label": "" + "label": "5 років" }, "10y": { "label": "" @@ -2142,7 +2147,7 @@ "label": "" }, "1mo": { - "label": "" + "label": "1 місяць" } } } @@ -2182,7 +2187,7 @@ "label": "Температура за Фаренгейтом" }, "disableTemperatureDecimals": { - "label": "" + "label": "Вимкнути десяткові дроби температури" }, "showCurrentWindSpeed": { "label": "Показати поточну швидкість вітру", @@ -2352,6 +2357,9 @@ "name": "", "description": "", "option": { + "columns": { + "label": "" + }, "enableRowSorting": { "label": "" }, @@ -2359,7 +2367,7 @@ "label": "", "option": { "name": "", - "state": "", + "state": "Стан", "cpuUsage": "", "memoryUsage": "" } @@ -2405,6 +2413,12 @@ "title": "" } }, + "vpn": { + "name": "", + "description": "", + "serviceUnavailable": "", + "option": {} + }, "ups": { "name": "", "description": "", @@ -2470,7 +2484,7 @@ "videoDecision": { "directPlay": "", "transcode": "", - "directStream": "" + "directStream": "Прямий потік" } }, "recentActivity": { @@ -2550,7 +2564,7 @@ "label": "", "description": "", "option": { - "chart": "", + "chart": "Графік", "events": "", "topPages": "", "topReferrers": "" @@ -3639,6 +3653,9 @@ "error": { "internalServerError": "" }, + "empty": { + "noSystems": "" + }, "metric": { "cpu": "", "memory": "", @@ -3712,6 +3729,9 @@ } }, "selectSystem": "", + "empty": { + "noSystems": "" + }, "error": { "internalServerError": "", "liveConnectionFailed": "", @@ -4133,6 +4153,11 @@ "title": "Не знайдено", "text": "Не вдалося знайти запитуваний ресурс" }, + "error": { + "title": "", + "text": "", + "digest": "" + }, "navbar": { "items": { "home": "Головна", @@ -4579,6 +4604,9 @@ }, "umamiActiveVisitors": { "label": "" + }, + "vpn": { + "label": "" } }, "interval": { @@ -5071,7 +5099,8 @@ } }, "error": { - "internalServerError": "" + "internalServerError": "", + "noColumns": "" } }, "kubernetes": { diff --git a/packages/translation/src/lang/vi.json b/packages/translation/src/lang/vi.json index dd8e8b519..1e1ad6c24 100644 --- a/packages/translation/src/lang/vi.json +++ b/packages/translation/src/lang/vi.json @@ -1021,6 +1021,7 @@ "mediaMonitoring": "", "speedtest": "", "analytics": "", + "vpn": "", "mediaLibrary": "", "uptimeMonitoring": "" }, @@ -1412,7 +1413,10 @@ "here": "", "iconPicker": { "label": "", - "header": "" + "header": "", + "headerLoading": "", + "noResults": "", + "error": "" }, "colorScheme": { "options": { @@ -1464,6 +1468,7 @@ "switchToDarkMode": "", "switchToLightMode": "Đổi qua chế độ nền sáng", "management": "", + "docker": "", "preferences": "Cá nhân hoá", "logout": "", "login": "Đăng nhập", @@ -2352,6 +2357,9 @@ "name": "", "description": "", "option": { + "columns": { + "label": "" + }, "enableRowSorting": { "label": "" }, @@ -2405,6 +2413,12 @@ "title": "" } }, + "vpn": { + "name": "", + "description": "", + "serviceUnavailable": "", + "option": {} + }, "ups": { "name": "", "description": "", @@ -3639,6 +3653,9 @@ "error": { "internalServerError": "" }, + "empty": { + "noSystems": "" + }, "metric": { "cpu": "", "memory": "", @@ -3712,6 +3729,9 @@ } }, "selectSystem": "", + "empty": { + "noSystems": "" + }, "error": { "internalServerError": "", "liveConnectionFailed": "", @@ -4133,6 +4153,11 @@ "title": "", "text": "" }, + "error": { + "title": "", + "text": "", + "digest": "" + }, "navbar": { "items": { "home": "Trang chủ", @@ -4579,6 +4604,9 @@ }, "umamiActiveVisitors": { "label": "" + }, + "vpn": { + "label": "" } }, "interval": { @@ -5071,7 +5099,8 @@ } }, "error": { - "internalServerError": "" + "internalServerError": "", + "noColumns": "" } }, "kubernetes": { diff --git a/packages/translation/src/lang/zh.json b/packages/translation/src/lang/zh.json index 99def3d69..950ee85bc 100644 --- a/packages/translation/src/lang/zh.json +++ b/packages/translation/src/lang/zh.json @@ -1021,6 +1021,7 @@ "mediaMonitoring": "", "speedtest": "", "analytics": "", + "vpn": "", "mediaLibrary": "", "uptimeMonitoring": "" }, @@ -1412,7 +1413,10 @@ "here": "此處", "iconPicker": { "label": "Icon 網址", - "header": "輸入名稱或物件來篩選 icons,Homarr 會自動搜尋 {countIcons}" + "header": "輸入名稱或物件來篩選 icons,Homarr 會自動搜尋 {countIcons}", + "headerLoading": "", + "noResults": "", + "error": "" }, "colorScheme": { "options": { @@ -1464,6 +1468,7 @@ "switchToDarkMode": "切換至暗黑模式", "switchToLightMode": "切換至日間模式", "management": "管理中心", + "docker": "", "preferences": "偏好設定", "logout": "登出", "login": "登入", @@ -2352,6 +2357,9 @@ "name": "", "description": "", "option": { + "columns": { + "label": "" + }, "enableRowSorting": { "label": "" }, @@ -2405,6 +2413,12 @@ "title": "" } }, + "vpn": { + "name": "", + "description": "", + "serviceUnavailable": "", + "option": {} + }, "ups": { "name": "", "description": "", @@ -3639,6 +3653,9 @@ "error": { "internalServerError": "" }, + "empty": { + "noSystems": "" + }, "metric": { "cpu": "", "memory": "", @@ -3712,6 +3729,9 @@ } }, "selectSystem": "", + "empty": { + "noSystems": "" + }, "error": { "internalServerError": "", "liveConnectionFailed": "", @@ -4133,6 +4153,11 @@ "title": "不存在", "text": "找不到請求的資源" }, + "error": { + "title": "", + "text": "", + "digest": "" + }, "navbar": { "items": { "home": "首頁", @@ -4579,6 +4604,9 @@ }, "umamiActiveVisitors": { "label": "" + }, + "vpn": { + "label": "" } }, "interval": { @@ -5071,7 +5099,8 @@ } }, "error": { - "internalServerError": "取得 Docker 容器失敗" + "internalServerError": "取得 Docker 容器失敗", + "noColumns": "" } }, "kubernetes": { diff --git a/packages/widgets/src/_inputs/common.tsx b/packages/widgets/src/_inputs/common.tsx index 5c7769eb1..c7be65262 100644 --- a/packages/widgets/src/_inputs/common.tsx +++ b/packages/widgets/src/_inputs/common.tsx @@ -8,6 +8,7 @@ export interface CommonWidgetInputProps { property: string; options: Omit, "defaultValue" | "type">; initialOptions: Record; + itemId?: string; } type UseWidgetInputTranslationReturnType = (key: "label" | "description") => string; diff --git a/packages/widgets/src/_inputs/widget-anchor-note-input.tsx b/packages/widgets/src/_inputs/widget-anchor-note-input.tsx index e045e7df0..e909477b5 100644 --- a/packages/widgets/src/_inputs/widget-anchor-note-input.tsx +++ b/packages/widgets/src/_inputs/widget-anchor-note-input.tsx @@ -30,9 +30,6 @@ export const WidgetAnchorNoteInput = ({ property, kind, options }: CommonWidgetI { enabled: Boolean(integrationId), refetchOnMount: false, - refetchOnWindowFocus: false, - refetchOnReconnect: false, - retry: false, }, ); diff --git a/packages/widgets/src/_inputs/widget-dynamic-select-input.tsx b/packages/widgets/src/_inputs/widget-dynamic-select-input.tsx index de8ef5021..3b5b636aa 100644 --- a/packages/widgets/src/_inputs/widget-dynamic-select-input.tsx +++ b/packages/widgets/src/_inputs/widget-dynamic-select-input.tsx @@ -24,7 +24,11 @@ export const WidgetDynamicSelectInput = ({ property, kind, options }: CommonWidg const inputProps = form.getInputProps(`options.${property}`); const [search, setSearch] = useState(""); const [debouncedSearch] = useDebouncedValue(search, 300); - const { isPending, options: selectOptions } = options.useOptions(debouncedSearch, form.values.integrationIds); + const { isPending, options: selectOptions } = options.useOptions( + debouncedSearch, + form.values.integrationIds, + form.values.options, + ); const currentOption = inputProps.value as DynamicSelectOption | null; const onChange = inputProps.onChange as (value: DynamicSelectOption | null) => void; diff --git a/packages/widgets/src/_inputs/widget-multiReleasesRepositories-input.tsx b/packages/widgets/src/_inputs/widget-multiReleasesRepositories-input.tsx index f7f026c3c..675d56330 100644 --- a/packages/widgets/src/_inputs/widget-multiReleasesRepositories-input.tsx +++ b/packages/widgets/src/_inputs/widget-multiReleasesRepositories-input.tsx @@ -12,6 +12,7 @@ import { Group, Image, Loader, + PasswordInput, Select, Stack, Text, @@ -27,6 +28,7 @@ import { IconCopy, IconCopyCheckFilled, IconEdit, + IconKey, IconPackageImport, IconPlus, IconTrash, @@ -38,16 +40,20 @@ import { escapeForRegEx } from "@tiptap/react"; import { clientApi } from "@homarr/api/client"; import { useSession } from "@homarr/auth/client"; import { createId } from "@homarr/common"; -import { getIconUrl } from "@homarr/definitions"; -import type { IntegrationKind } from "@homarr/definitions"; +import type { ReleaseProviderKind } from "@homarr/definitions"; +import { + getReleaseProviderDefaultUrl, + getReleaseProviderIconUrl, + getReleaseProviderName, + normalizeReleaseProviderIdentifier, + releaseProviderKinds, +} from "@homarr/definitions"; import { findBestIconMatch, IconPicker } from "@homarr/forms-collection"; import { createModal, useModalAction } from "@homarr/modals"; import { useScopedI18n } from "@homarr/translation/client"; import { MaskedImage } from "@homarr/ui"; import type { ReleasesRepository, ReleasesVersionFilter } from "../releases/releases-repository"; -import { WidgetIntegrationSelect } from "../widget-integration-select"; -import type { IntegrationSelectOption } from "../widget-integration-select"; import type { CommonWidgetInputProps } from "./common"; import { useWidgetInputTranslation } from "./common"; import { useFormContext } from "./form"; @@ -57,13 +63,10 @@ interface FormValidation { errors: FormErrors; } -interface Integration extends IntegrationSelectOption { - iconUrl: string; -} - export const WidgetMultiReleasesRepositoriesInput = ({ property, kind, + itemId, }: CommonWidgetInputProps<"multiReleasesRepositories">) => { const t = useWidgetInputTranslation(kind, property); const tRepository = useScopedI18n("widget.releases.option.repositories"); @@ -78,38 +81,19 @@ export const WidgetMultiReleasesRepositoriesInput = ({ const { data: session } = useSession(); const isAdmin = session?.user.permissions.includes("admin") ?? false; - const integrationsApi = clientApi.integration.allOfGivenCategory.useQuery( - { - category: "releasesProvider", - }, - { - refetchOnMount: false, - refetchOnWindowFocus: false, - refetchOnReconnect: false, - }, - ); - const integrations = useMemo( - () => - integrationsApi.data?.reduce>((acc, integration) => { - acc[integration.id] = { - id: integration.id, - name: integration.name, - url: integration.url, - kind: integration.kind, - iconUrl: getIconUrl(integration.kind), - }; - return acc; - }, {}) ?? {}, - [integrationsApi], + const providerOptions = useMemo( + () => releaseProviderKinds.map((provider) => ({ value: provider, label: getReleaseProviderName(provider) })), + [], ); const onRepositorySave = useCallback( (repository: ReleasesRepository, index: number): FormValidation => { - form.setFieldValue(`options.${property}.${index}.providerIntegrationId`, repository.providerIntegrationId); + form.setFieldValue(`options.${property}.${index}.provider`, repository.provider); form.setFieldValue(`options.${property}.${index}.identifier`, repository.identifier); form.setFieldValue(`options.${property}.${index}.name`, repository.name); form.setFieldValue(`options.${property}.${index}.versionFilter`, repository.versionFilter); form.setFieldValue(`options.${property}.${index}.iconUrl`, repository.iconUrl); + form.setFieldValue(`options.${property}.${index}.providerUrl`, repository.providerUrl); const formValidation = form.validate(); const fieldErrors: FormErrors = Object.entries(formValidation.errors).reduce((acc, [key, value]) => { @@ -152,7 +136,7 @@ export const WidgetMultiReleasesRepositoriesInput = ({ onRepositorySave: (saved) => onRepositorySave(saved, index), onRepositoryCancel: () => onRepositoryRemove(index), versionFilterPrecisionOptions, - integrations, + providerOptions, }); }; @@ -183,7 +167,6 @@ export const WidgetMultiReleasesRepositoriesInput = ({ onClick={() => openImportModal({ repositories, - integrations, versionFilterPrecisionOptions, onConfirm: (selectedRepositories) => { if (!selectedRepositories.length) return; @@ -210,22 +193,22 @@ export const WidgetMultiReleasesRepositoriesInput = ({ {repositories.map((repository, index) => { - const integration = repository.providerIntegrationId - ? integrations[repository.providerIntegrationId] - : undefined; + const providerName = repository.provider ? getReleaseProviderName(repository.provider) : ""; return ( - - {integration?.name ?? ""} + + {providerName} @@ -242,7 +225,7 @@ export const WidgetMultiReleasesRepositoriesInput = ({ repository, onRepositorySave: (saved) => onRepositorySave(saved, index), versionFilterPrecisionOptions, - integrations, + providerOptions, }) } variant="light" @@ -269,10 +252,125 @@ export const WidgetMultiReleasesRepositoriesInput = ({ ); })} + {itemId && } ); }; +const providersWithAuth: ReleaseProviderKind[] = [ + "github", + "gitHubContainerRegistry", + "dockerHub", + "gitlab", + "npm", + "codeberg", +]; + +const ProviderTokensSection = ({ itemId, repositories }: { itemId: string; repositories: ReleasesRepository[] }) => { + const tRepository = useScopedI18n("widget.releases.option.repositories"); + const { data: configuredKinds = [], refetch } = clientApi.widget.secrets.getConfiguredKinds.useQuery({ itemId }); + const setSecret = clientApi.widget.secrets.setSecret.useMutation({ onSuccess: () => refetch() }); + const deleteSecret = clientApi.widget.secrets.deleteSecret.useMutation({ onSuccess: () => refetch() }); + + const authProviders = useMemo(() => { + const usedProviders = repositories.map((r) => r.provider).filter((p): p is ReleaseProviderKind => p !== undefined); + const configuredProviders = configuredKinds.filter((k): k is ReleaseProviderKind => + providersWithAuth.includes(k as ReleaseProviderKind), + ); + return [...new Set([...usedProviders, ...configuredProviders])].filter((p) => providersWithAuth.includes(p)); + }, [repositories, configuredKinds]); + + if (authProviders.length === 0) return null; + + return ( + <> + + + {authProviders.map((provider) => ( + { + await setSecret.mutateAsync({ itemId, kind: provider, value }); + }} + onDelete={async () => { + await deleteSecret.mutateAsync({ itemId, kind: provider }); + }} + /> + ))} + + + ); +}; + +const ProviderTokenInput = ({ + provider, + hasToken, + onSave, + onDelete, +}: { + provider: ReleaseProviderKind; + hasToken: boolean; + onSave: (value: string) => Promise; + onDelete: () => Promise; +}) => { + const tRepository = useScopedI18n("widget.releases.option.repositories"); + const [value, setValue] = useState(""); + const [editing, setEditing] = useState(false); + const [saving, setSaving] = useState(false); + + const handleSave = async () => { + if (!value.trim()) return; + setSaving(true); + try { + await onSave(value.trim()); + setValue(""); + setEditing(false); + } finally { + setSaving(false); + } + }; + + const handleDelete = async () => { + setSaving(true); + try { + await onDelete(); + setValue(""); + setEditing(false); + } finally { + setSaving(false); + } + }; + + return ( + + { + setValue(e.currentTarget.value); + setEditing(true); + }} + style={{ flex: 1 }} + size="xs" + leftSection={} + /> + {editing && value.trim() && ( + + )} + {hasToken && !editing && ( + + + + )} + + ); +}; + const formatVersionFilterRegex = (versionFilter: ReleasesVersionFilter | undefined) => { if (!versionFilter) return undefined; @@ -294,7 +392,7 @@ interface RepositoryEditProps { onRepositorySave: (repository: ReleasesRepository) => FormValidation; onRepositoryCancel?: () => void; versionFilterPrecisionOptions: string[]; - integrations: Record; + providerOptions: { value: ReleaseProviderKind; label: string }[]; } const RepositoryEditModal = createModal(({ innerProps, actions }) => { @@ -302,11 +400,6 @@ const RepositoryEditModal = createModal(({ innerProps, acti const [loading, setLoading] = useState(false); const [tempRepository, setTempRepository] = useState(() => ({ ...innerProps.repository })); const [formErrors, setFormErrors] = useState({}); - const integrationSelectOptions: IntegrationSelectOption[] = useMemo( - () => Object.values(innerProps.integrations), - [innerProps.integrations], - ); - // Allows user to not select an icon by removing the url from the input, // will only try and get an icon if the name or identifier changes const [autoSetIcon, setAutoSetIcon] = useState(false); @@ -361,15 +454,14 @@ const RepositoryEditModal = createModal(({ innerProps, acti
- { - handleChange({ providerIntegrationId: value.length > 0 ? value.pop() : undefined }); + handleChange({ provider: value ? (value as ReleaseProviderKind) : undefined }); }} />
@@ -397,6 +489,16 @@ const RepositoryEditModal = createModal(({ innerProps, acti />
+ { + handleChange({ providerUrl: event.currentTarget.value || undefined }); + }} + error={formErrors[`${innerProps.fieldPath}.providerUrl`]} + /> + void; @@ -522,12 +623,12 @@ interface ImportRepositorySelectProps { const ImportRepositorySelect = ({ repository, checked, - integration, versionFilterPrecisionOptions, disabled = false, onImageSelectionChanged = undefined, }: ImportRepositorySelectProps) => { const tRepository = useScopedI18n("widget.releases.option.repositories"); + const provider = repository.provider; return ( @@ -570,12 +671,12 @@ const ImportRepositorySelect = ({ )} - + - {integration ? ( + {provider ? ( - {integration?.name ?? tRepository("noProvider.label")} + {provider ? getReleaseProviderName(provider) : tRepository("noProvider.label")} @@ -596,7 +697,6 @@ const ImportRepositorySelect = ({ interface RepositoryImportProps { repositories: ReleasesRepository[]; - integrations: Record; versionFilterPrecisionOptions: string[]; onConfirm: (selectedRepositories: ReleasesRepositoryImport[]) => void; isAdmin: boolean; @@ -609,8 +709,6 @@ const RepositoryImportModal = createModal(({ innerProps, const docker = clientApi.docker.getContainers.useQuery(undefined, { refetchOnMount: false, - refetchOnWindowFocus: false, - refetchOnReconnect: false, enabled: innerProps.isAdmin, }); @@ -625,28 +723,25 @@ const RepositoryImportModal = createModal(({ innerProps, if (!identifier) return acc; - const providerKind = containerImageToProviderKind[source] ?? "dockerHub"; - const integrationId = Object.values(innerProps.integrations).find( - (integration) => integration.kind === providerKind, - )?.id; + const provider = containerImageToProviderKind[source] ?? "dockerHub"; + const normalizedIdentifier = normalizeReleaseProviderIdentifier(provider, identifier); - if (acc.some((item) => item.providerIntegrationId === integrationId && item.identifier === identifier)) - return acc; + if (acc.some((item) => item.provider === provider && item.identifier === normalizedIdentifier)) return acc; acc.push({ id: createId(), - providerIntegrationId: integrationId, - identifier, + provider, + identifier: normalizedIdentifier, iconUrl: container.iconUrl ?? undefined, - name: formatIdentifierName(identifier), + name: formatIdentifierName(normalizedIdentifier), versionFilter: version ? parseImageVersionToVersionFilter(version) : undefined, alreadyImported: innerProps.repositories.some( - (item) => item.providerIntegrationId === integrationId && item.identifier === identifier, + (item) => item.provider === provider && item.identifier === normalizedIdentifier, ), }); return acc; }, []) ?? [], - [docker.data, innerProps.repositories, innerProps.integrations], + [docker.data, innerProps.repositories], ); const handleConfirm = useCallback(() => { @@ -722,27 +817,20 @@ const RepositoryImportModal = createModal(({ innerProps, {importRepositories .filter((repository) => !repository.alreadyImported) - .map((repository) => { - const integration = repository.providerIntegrationId - ? innerProps.integrations[repository.providerIntegrationId] - : undefined; - - return ( - - isSelected - ? setSelectedImages([...selectedImages, repository]) - : setSelectedImages(selectedImages.filter((img) => img !== repository)) - } - /> - ); - })} + .map((repository) => ( + + isSelected + ? setSelectedImages([...selectedImages, repository]) + : setSelectedImages(selectedImages.filter((img) => img !== repository)) + } + /> + ))}
)} @@ -756,22 +844,15 @@ const RepositoryImportModal = createModal(({ innerProps, {importRepositories .filter((repository) => repository.alreadyImported) - .map((repository) => { - const integration = repository.providerIntegrationId - ? innerProps.integrations[repository.providerIntegrationId] - : undefined; - - return ( - - ); - })} + .map((repository) => ( + + ))} )} @@ -798,8 +879,8 @@ const RepositoryImportModal = createModal(({ innerProps, size: "xl", }); -const containerImageToProviderKind: Record = { - "ghcr.io": "github", +const containerImageToProviderKind: Record = { + "ghcr.io": "gitHubContainerRegistry", "docker.io": "dockerHub", "lscr.io": "linuxServerIO", "quay.io": "quay", diff --git a/packages/widgets/src/anchor-note/component.tsx b/packages/widgets/src/anchor-note/component.tsx index 99585b838..90a1c998c 100644 --- a/packages/widgets/src/anchor-note/component.tsx +++ b/packages/widgets/src/anchor-note/component.tsx @@ -145,18 +145,10 @@ interface AnchorNoteWidgetContentProps { const AnchorNoteWidgetContent = ({ options, integrationId, noteId }: AnchorNoteWidgetContentProps) => { const t = useScopedI18n("widget.anchorNote"); - const { data: note, refetch } = clientApi.widget.anchorNotes.getNote.useQuery( - { - integrationId, - noteId, - }, - { - staleTime: 15 * 1000, - refetchOnWindowFocus: false, - refetchOnReconnect: false, - retry: false, - }, - ); + const { data: note, refetch } = clientApi.widget.anchorNotes.getNote.useQuery({ + integrationId, + noteId, + }); const { mutateAsync: updateNoteAsync, isPending: isUpdating } = clientApi.widget.anchorNotes.updateNote.useMutation(); const [isEditing, setIsEditing] = useState(false); diff --git a/packages/widgets/src/app/app.module.css b/packages/widgets/src/app/app.module.css index e1477f431..b14962396 100644 --- a/packages/widgets/src/app/app.module.css +++ b/packages/widgets/src/app/app.module.css @@ -8,6 +8,8 @@ transition: scale 0.2s ease-in-out; user-select: none; -webkit-touch-callout: none; + -webkit-user-drag: none; + touch-action: manipulation; } .appWithUrl:hover > .appIcon { diff --git a/packages/widgets/src/app/component.tsx b/packages/widgets/src/app/component.tsx index 054a2da6b..56e079a8c 100644 --- a/packages/widgets/src/app/component.tsx +++ b/packages/widgets/src/app/component.tsx @@ -23,16 +23,9 @@ export default function AppWidget({ options, isEditMode, height, width }: Widget const t = useI18n(); const settings = useSettings(); const board = useRequiredBoard(); - const { data: app } = clientApi.app.byId.useQuery( - { - id: options.appId, - }, - { - refetchOnWindowFocus: false, - refetchOnReconnect: false, - retry: false, - }, - ); + const { data: app } = clientApi.app.byId.useQuery({ + id: options.appId, + }); useRegisterSpotlightContextResults( `app-${app?.id ?? options.appId}`, app?.href @@ -87,6 +80,7 @@ export default function AppWidget({ options, isEditMode, height, width }: Widget justify="center" align="center" gap={isColumnLayout ? 0 : "sm"} + onContextMenu={isEditMode ? (e) => e.preventDefault() : undefined} > {options.showTitle && ( diff --git a/packages/widgets/src/app/ping/ping-indicator.tsx b/packages/widgets/src/app/ping/ping-indicator.tsx index b8f88cdc8..75dc38974 100644 --- a/packages/widgets/src/app/ping/ping-indicator.tsx +++ b/packages/widgets/src/app/ping/ping-indicator.tsx @@ -11,20 +11,7 @@ interface PingIndicatorProps { export const PingIndicator = ({ appId }: PingIndicatorProps) => { const t = useI18n(); - const { data: pingResult } = clientApi.widget.app.ping.useQuery( - { id: appId }, - { refetchOnMount: false, refetchOnWindowFocus: false }, - ); - - const utils = clientApi.useUtils(); - clientApi.widget.app.updatedPing.useSubscription( - { id: appId }, - { - onData(data) { - utils.widget.app.ping.setData({ id: appId }, data); - }, - }, - ); + const { data: pingResult } = clientApi.widget.app.ping.useQuery({ id: appId }, { refetchOnMount: false }); if (!pingResult) return ; diff --git a/packages/widgets/src/archive-team-warrior/component.tsx b/packages/widgets/src/archive-team-warrior/component.tsx index dacac283d..032858c2f 100644 --- a/packages/widgets/src/archive-team-warrior/component.tsx +++ b/packages/widgets/src/archive-team-warrior/component.tsx @@ -28,20 +28,7 @@ const ArchiveTeamWarriorWidgetContent = ({ integrationId: string; options: WidgetComponentProps<"archiveTeamWarrior">["options"]; }) => { - const [data] = clientApi.widget.archiveTeamWarrior.getStatus.useSuspenseQuery({ - integrationId, - }); - - const utils = clientApi.useUtils(); - - clientApi.widget.archiveTeamWarrior.subscribeStatus.useSubscription( - { integrationId }, - { - onData: (next) => { - utils.widget.archiveTeamWarrior.getStatus.setData({ integrationId }, next); - }, - }, - ); + const [data] = clientApi.widget.archiveTeamWarrior.getStatus.useSuspenseQuery({ integrationId }); const status = data.status; const projectName = status.project?.title ?? status.selectedProject ?? "No project selected"; diff --git a/packages/widgets/src/audio-stats/component.tsx b/packages/widgets/src/audio-stats/component.tsx index d9d8d26d3..9e410f527 100644 --- a/packages/widgets/src/audio-stats/component.tsx +++ b/packages/widgets/src/audio-stats/component.tsx @@ -7,10 +7,9 @@ import type { WidgetComponentProps } from "../definition"; import { AudioStatsContent } from "./audio-stats-content"; export default function AudioStatsWidget({ integrationIds, options, width }: WidgetComponentProps<"audioStats">) { - const { data: response } = clientApi.widget.audioStats.getStats.useQuery( - { integrationId: integrationIds[0] ?? "" }, - { staleTime: 5 * 60 * 1000 }, - ); + const { data: response } = clientApi.widget.audioStats.getStats.useQuery({ + integrationId: integrationIds[0] ?? "", + }); if (!response) return ; diff --git a/packages/widgets/src/beszel-alerts/component.tsx b/packages/widgets/src/beszel-alerts/component.tsx index 7c2bcc06c..3f934423d 100644 --- a/packages/widgets/src/beszel-alerts/component.tsx +++ b/packages/widgets/src/beszel-alerts/component.tsx @@ -41,36 +41,7 @@ export default function BeszelAlertsWidget({ () => ({ integrationIds, includeHistory: options.showHistory, maxHistoryItems: options.maxHistoryItems }), [integrationIds, options.showHistory, options.maxHistoryItems], ); - const { - data: results = [], - error: alertsError, - isPending, - } = clientApi.widget.beszel.getAlerts.useQuery(alertsInput, { - staleTime: 10_000, - gcTime: 48 * 60 * 60 * 1000, - refetchInterval: 10_000, - retry: false, - }); - const utils = clientApi.useUtils(); - - clientApi.widget.beszel.subscribeAlerts.useSubscription(alertsInput, { - enabled: !isEditMode, - onData(data) { - utils.widget.beszel.getAlerts.setData(alertsInput, (prev) => { - if (!prev) return prev; - return prev.map((r) => - r.integrationId === data.integrationId - ? { - ...r, - alerts: data.alerts.alerts, - history: data.alerts.history, - updatedAt: data.timestamp, - } - : r, - ); - }); - }, - }); + const { data: results = [], error: alertsError, isPending } = clientApi.widget.beszel.getAlerts.useQuery(alertsInput); const systemNameMap = useMemo(() => { const map: Record = {}; diff --git a/packages/widgets/src/beszel-system-grid/component.tsx b/packages/widgets/src/beszel-system-grid/component.tsx index 3d5c3bd42..5d4671f5f 100644 --- a/packages/widgets/src/beszel-system-grid/component.tsx +++ b/packages/widgets/src/beszel-system-grid/component.tsx @@ -28,7 +28,7 @@ import type { WidgetComponentProps } from "../definition"; import type { BeszelSystemRow } from "../beszel/_shared/types"; import { statusColorMap, thresholdColor } from "../beszel/_shared/colors"; import { formatByteRate, formatLoadAvg, formatPercent, formatTemp, formatUptime } from "../beszel/_shared/format"; -import { useBeszelFilteredSystems, useBeszelSystemsSubscription } from "../beszel/_shared/hooks"; +import { useBeszelFilteredSystems } from "../beszel/_shared/hooks"; import { BeszelIntegrationErrorIndicator } from "../beszel/_shared/error-indicator"; import { BeszelSystemStatsModal } from "../beszel/_shared/system-stats-modal"; @@ -369,12 +369,8 @@ export default function BeszelSystemGridWidget({ data: results = [], error: systemsError, isPending, - } = clientApi.widget.beszel.getSystems.useQuery( - { integrationIds }, - { staleTime: 10_000, gcTime: 48 * 60 * 60 * 1000, refetchInterval: 10_000, retry: false }, - ); + } = clientApi.widget.beszel.getSystems.useQuery({ integrationIds }); - useBeszelSystemsSubscription(integrationIds, !isEditMode); const filteredSystems = useBeszelFilteredSystems(results, options.statusFilter); if (systemsError) throw systemsError; diff --git a/packages/widgets/src/beszel-system-stats/component.tsx b/packages/widgets/src/beszel-system-stats/component.tsx index 02d77a832..c82196639 100644 --- a/packages/widgets/src/beszel-system-stats/component.tsx +++ b/packages/widgets/src/beszel-system-stats/component.tsx @@ -67,10 +67,7 @@ export default function BeszelSystemStatsWidget({ data: systemsResult = [], isPending: systemsPending, error: systemsError, - } = clientApi.widget.beszel.getSystems.useQuery( - { integrationIds }, - { staleTime: 10_000, gcTime: 48 * 60 * 60 * 1000, retry: false }, - ); + } = clientApi.widget.beszel.getSystems.useQuery({ integrationIds }); const systems = useMemo( () => systemsResult.flatMap((r) => r.systems.map((s) => ({ value: s.id, label: s.name }))), @@ -103,11 +100,8 @@ export default function BeszelSystemStatsWidget({ includeDocker: showDocker, }, { - staleTime: 10_000, - gcTime: 48 * 60 * 60 * 1000, - refetchInterval: isLive ? false : 10_000, + refetchInterval: isLive ? false : 5_000, enabled: !isLive && systemReady && selectedSystem !== "", - retry: false, }, ); diff --git a/packages/widgets/src/beszel-system-stats/index.ts b/packages/widgets/src/beszel-system-stats/index.ts index 0b1cdf127..a184963e7 100644 --- a/packages/widgets/src/beszel-system-stats/index.ts +++ b/packages/widgets/src/beszel-system-stats/index.ts @@ -28,10 +28,7 @@ export const { definition, componentLoader } = createWidgetDefinition("beszelSys data = [], isPending, isError, - } = clientApi.widget.beszel.getSystems.useQuery( - { integrationIds }, - { enabled: integrationIds.length > 0, staleTime: 30_000 }, - ); + } = clientApi.widget.beszel.getSystems.useQuery({ integrationIds }, { enabled: integrationIds.length > 0 }); const selectData = data.flatMap((r) => r.systems.map((s) => ({ value: s.id, label: s.name }))); return { data: selectData, isPending, isError }; }, diff --git a/packages/widgets/src/beszel-system-table/component.tsx b/packages/widgets/src/beszel-system-table/component.tsx index 1d8c92355..49b3dca74 100644 --- a/packages/widgets/src/beszel-system-table/component.tsx +++ b/packages/widgets/src/beszel-system-table/component.tsx @@ -27,7 +27,7 @@ import type { WidgetComponentProps } from "../definition"; import type { BeszelSystemRow } from "../beszel/_shared/types"; import { loadAvgColor, statusColorMap, thresholdColor } from "../beszel/_shared/colors"; import { formatByteRate, formatLoadAvg, formatPercent, formatTemp, formatUptime } from "../beszel/_shared/format"; -import { useBeszelFilteredSystems, useBeszelSystemsSubscription } from "../beszel/_shared/hooks"; +import { useBeszelFilteredSystems } from "../beszel/_shared/hooks"; import { BeszelIntegrationErrorIndicator } from "../beszel/_shared/error-indicator"; import { BeszelSystemStatsModal } from "../beszel/_shared/system-stats-modal"; @@ -74,13 +74,9 @@ export default function BeszelSystemTableWidget({ data: results = [], error: systemsError, isPending, - } = clientApi.widget.beszel.getSystems.useQuery( - { integrationIds }, - { staleTime: 10_000, gcTime: 48 * 60 * 60 * 1000, refetchInterval: 10_000, retry: false }, - ); + } = clientApi.widget.beszel.getSystems.useQuery({ integrationIds }); const size = getSizeConfig(width); - useBeszelSystemsSubscription(integrationIds, !isEditMode); const filteredSystems = useBeszelFilteredSystems(results, options.statusFilter); const [sortStatus, setSortStatus] = useState>({ diff --git a/packages/widgets/src/beszel/_shared/hooks.ts b/packages/widgets/src/beszel/_shared/hooks.ts index ebb84b4eb..b00e6f857 100644 --- a/packages/widgets/src/beszel/_shared/hooks.ts +++ b/packages/widgets/src/beszel/_shared/hooks.ts @@ -2,30 +2,10 @@ import { useMemo } from "react"; -import { clientApi } from "@homarr/api/client"; - import type { BeszelSystemRow } from "./types"; type SystemRowWithKey = BeszelSystemRow & { _key: string }; -export const useBeszelSystemsSubscription = (integrationIds: string[], enabled = true) => { - const utils = clientApi.useUtils(); - clientApi.widget.beszel.subscribeSystems.useSubscription( - { integrationIds }, - { - enabled, - onData(data) { - utils.widget.beszel.getSystems.setData({ integrationIds }, (prev) => { - if (!prev) return prev; - return prev.map((r) => - r.integrationId === data.integrationId ? { ...r, systems: data.systems, updatedAt: data.timestamp } : r, - ); - }); - }, - }, - ); -}; - export const useBeszelFilteredSystems = ( results: { integrationId: string; systems: BeszelSystemRow[] }[], statusFilter: string, diff --git a/packages/widgets/src/beszel/_shared/system-stats-modal.tsx b/packages/widgets/src/beszel/_shared/system-stats-modal.tsx index 52781c5af..1071ccc53 100644 --- a/packages/widgets/src/beszel/_shared/system-stats-modal.tsx +++ b/packages/widgets/src/beszel/_shared/system-stats-modal.tsx @@ -36,15 +36,12 @@ export const BeszelSystemStatsModal = createModal(( const t = useScopedI18n("widget.beszelSystemStats"); const [timePeriod, setTimePeriod] = useState("1h"); - const { data } = clientApi.widget.beszel.getSystemStats.useQuery( - { - integrationIds: [innerProps.integrationId], - systemId: innerProps.systemId, - timePeriod, - includeDocker: false, - }, - { staleTime: 10_000, gcTime: 48 * 60 * 60 * 1000, refetchInterval: 10_000 }, - ); + const { data } = clientApi.widget.beszel.getSystemStats.useQuery({ + integrationIds: [innerProps.integrationId], + systemId: innerProps.systemId, + timePeriod, + includeDocker: false, + }); const mappers = useMemo( () => ({ diff --git a/packages/widgets/src/beszel/_shared/use-live-stats.ts b/packages/widgets/src/beszel/_shared/use-live-stats.ts index 440800d0d..b07bbbd2c 100644 --- a/packages/widgets/src/beszel/_shared/use-live-stats.ts +++ b/packages/widgets/src/beszel/_shared/use-live-stats.ts @@ -1,92 +1,15 @@ "use client"; -import { useCallback, useEffect, useMemo, useRef, useState } from "react"; - import { clientApi } from "@homarr/api/client"; -import type { BeszelContainerStatsRecord, BeszelSystemStatsRecord } from "@homarr/integrations/types"; - -const MAX_BUFFER = 30; -const FLUSH_INTERVAL_MS = 3000; - -interface LiveStatsData { - systemStats: BeszelSystemStatsRecord[]; - containerStats: BeszelContainerStatsRecord[]; -} - -function appendToBuffer( - prev: LiveStatsData | null, - incoming: { stats: BeszelSystemStatsRecord; containerStats: BeszelContainerStatsRecord | null }, -): LiveStatsData { - const systemStats = [...(prev?.systemStats ?? []), incoming.stats].slice(-MAX_BUFFER); - const prevContainer = prev?.containerStats ?? []; - if (!incoming.containerStats) { - return { systemStats, containerStats: prevContainer }; - } - return { systemStats, containerStats: [...prevContainer, incoming.containerStats].slice(-MAX_BUFFER) }; -} export const useLiveStats = (integrationIds: string[], systemId: string, enabled: boolean) => { - const [data, setData] = useState(null); - const [error, setError] = useState(null); - const bufferRef = useRef(null); - const dirtyRef = useRef(false); - const generationRef = useRef(0); - - const integrationKey = [...integrationIds].sort().join(","); - - useEffect(() => { - generationRef.current += 1; - setData(null); - setError(null); - bufferRef.current = null; - dirtyRef.current = false; - }, [systemId, enabled, integrationKey]); - - const onData = useCallback( - (incoming: { stats: BeszelSystemStatsRecord; containerStats: BeszelContainerStatsRecord | null }) => { - const gen = generationRef.current; - queueMicrotask(() => { - if (generationRef.current !== gen) return; - setError(null); - const isFirst = !bufferRef.current; - bufferRef.current = appendToBuffer(bufferRef.current, incoming); - dirtyRef.current = true; - if (isFirst) { - setData(bufferRef.current); - } - }); - }, - [], + const { data, error } = clientApi.widget.beszel.getSystemStats.useQuery( + { integrationIds, systemId, timePeriod: "1m" as const, includeDocker: true }, + { enabled: enabled && systemId !== "", refetchInterval: 5_000 }, ); - const onError = useCallback((err: unknown) => { - if (err instanceof Error) { - setError(err); - return; - } - setError(new Error(String(err))); - }, []); - - const subscriptionInput = useMemo(() => ({ integrationIds, systemId }), [integrationKey, systemId]); - const subscriptionEnabled = enabled && systemId !== ""; - - useEffect(() => { - if (!subscriptionEnabled) return; - const gen = generationRef.current; - const id = setInterval(() => { - if (generationRef.current !== gen) return; - if (!dirtyRef.current || !bufferRef.current) return; - dirtyRef.current = false; - setData(bufferRef.current); - }, FLUSH_INTERVAL_MS); - return () => clearInterval(id); - }, [subscriptionEnabled, integrationKey, systemId]); - - clientApi.widget.beszel.subscribeSystemStats.useSubscription(subscriptionInput, { - enabled: subscriptionEnabled, - onData, - onError, - }); - - return { data, error }; + return { + data: data ? { systemStats: data.systemStats, containerStats: data.containerStats } : null, + error: error ?? (data?.error ? new Error(data.error) : null), + }; }; diff --git a/packages/widgets/src/calendar/calender-day.tsx b/packages/widgets/src/calendar/calender-day.tsx index 790cc0a82..70f2f26a8 100644 --- a/packages/widgets/src/calendar/calender-day.tsx +++ b/packages/widgets/src/calendar/calender-day.tsx @@ -1,4 +1,4 @@ -import { useState } from "react"; +import { useCallback, useState } from "react"; import { Box, Container, Flex, Popover, Text, useMantineTheme } from "@mantine/core"; import { useElementSize } from "@mantine/hooks"; @@ -13,10 +13,12 @@ interface CalendarDayProps { disabled: boolean; rootWidth: number; rootHeight: number; + onOpen: (close: () => void) => void; } -export const CalendarDay = ({ date, events, disabled, rootHeight, rootWidth }: CalendarDayProps) => { +export const CalendarDay = ({ date, events, disabled, rootHeight, rootWidth, onOpen }: CalendarDayProps) => { const [opened, setOpened] = useState(false); + const [pinned, setPinned] = useState(false); const { primaryColor } = useMantineTheme(); const { ref, height } = useElementSize(); const board = useRequiredBoard(); @@ -29,6 +31,19 @@ export const CalendarDay = ({ date, events, disabled, rootHeight, rootWidth }: C const isTooSmallForIndicators = height < 30; + const handleMouseEnter = useCallback(() => { + if (disabled) return; + onOpen(() => { + setOpened(false); + setPinned(false); + }); + setOpened(true); + }, [disabled, onOpen]); + + const handleMouseLeave = useCallback(() => { + if (!pinned) setOpened(false); + }, [pinned]); + return ( { + setOpened(value); + if (!value) setPinned(false); + }} opened={opened} disabled={disabled} > @@ -59,10 +77,10 @@ export const CalendarDay = ({ date, events, disabled, rootHeight, rootWidth }: C cursor: disabled ? "default" : "pointer", }} onClick={() => { - if (disabled) return; - - setOpened((prev) => !prev); + if (!disabled) setPinned((prev) => !prev); }} + onMouseEnter={handleMouseEnter} + onMouseLeave={handleMouseLeave} > {date.getDate()} @@ -71,7 +89,15 @@ export const CalendarDay = ({ date, events, disabled, rootHeight, rootWidth }: C {/* Popover has some offset on the left side, padding is removed because of scrollarea paddings */} - + setOpened(true)} + onMouseLeave={handleMouseLeave} + > diff --git a/packages/widgets/src/calendar/component.tsx b/packages/widgets/src/calendar/component.tsx index ac3d8b37b..b00cd1af0 100644 --- a/packages/widgets/src/calendar/component.tsx +++ b/packages/widgets/src/calendar/component.tsx @@ -1,6 +1,6 @@ "use client"; -import { useMemo, useState } from "react"; +import { useCallback, useMemo, useRef, useState } from "react"; import { useParams } from "next/navigation"; import { useMantineTheme } from "@mantine/core"; import { Calendar } from "@mantine/dates"; @@ -39,27 +39,7 @@ const FetchCalendar = ({ month, setMonth, isEditMode, integrationIds, options }: releaseType: options.releaseType, showUnmonitored: options.showUnmonitored, }; - const { data } = clientApi.widget.calendar.findAllEvents.useQuery(input, { - staleTime: 60 * 1000, - refetchOnWindowFocus: false, - refetchOnReconnect: false, - retry: false, - }); - - const utils = clientApi.useUtils(); - clientApi.widget.calendar.subscribeToEvents.useSubscription(input, { - onData(data) { - utils.widget.calendar.findAllEvents.setData(input, (old) => { - return old?.map((item) => { - if (item.integration.id !== data.integration.id) return item; - return { - ...item, - events: data.events, - }; - }); - }); - }, - }); + const { data } = clientApi.widget.calendar.findAllEvents.useQuery(input); const events = useMemo(() => data?.flatMap((item) => item.events) ?? [], [data]); @@ -85,6 +65,11 @@ const CalendarBase = ({ isEditMode, events, month, setMonth, options }: Calendar const isSmall = width < 256; const normalizedEvents = useMemo(() => splitEvents(events), [events]); + const activeCloseRef = useRef<(() => void) | null>(null); + const onDayOpen = useCallback((close: () => void) => { + activeCloseRef.current?.(); + activeCloseRef.current = close; + }, []); return ( ); }} diff --git a/packages/widgets/src/coolify/component.tsx b/packages/widgets/src/coolify/component.tsx index 91b37ecd4..46940f56e 100644 --- a/packages/widgets/src/coolify/component.tsx +++ b/packages/widgets/src/coolify/component.tsx @@ -31,27 +31,7 @@ interface CoolifyContentProps { } function CoolifyContent({ integrationIds, options, width }: CoolifyContentProps) { - const { data: instancesData = [] } = clientApi.widget.coolify.getInstancesInfo.useQuery( - { integrationIds }, - { staleTime: 30 * 1000 }, - ); - - const utils = clientApi.useUtils(); - clientApi.widget.coolify.subscribeInstancesInfo.useSubscription( - { integrationIds }, - { - onData(newData) { - utils.widget.coolify.getInstancesInfo.setData({ integrationIds }, (prevData) => { - if (!prevData) return prevData; - return prevData.map((instance) => - instance.integrationId === newData.integrationId - ? { ...instance, instanceInfo: newData.instanceInfo, updatedAt: newData.timestamp } - : instance, - ); - }); - }, - }, - ); + const { data: instancesData = [] } = clientApi.widget.coolify.getInstancesInfo.useQuery({ integrationIds }); const isTiny = width < 256; const [firstInstance] = instancesData; diff --git a/packages/widgets/src/definition.ts b/packages/widgets/src/definition.ts index 4c65d231e..a6e769d5d 100644 --- a/packages/widgets/src/definition.ts +++ b/packages/widgets/src/definition.ts @@ -1,3 +1,4 @@ +import type React from "react"; import type { LoaderComponent } from "next/dynamic"; import type { QueryClient } from "@tanstack/react-query"; import type { DefaultErrorData } from "@trpc/server/unstable-core-do-not-import"; @@ -9,7 +10,35 @@ import type { stringOrTranslation } from "@homarr/translation"; import type { TablerIcon } from "@homarr/ui"; import type { WidgetImports } from "."; -import type { inferOptionsFromCreator, WidgetOptionsRecord } from "./options"; +import type { inferOptionsFromCreator, inferOptionsFromDefinition, WidgetOptionsRecord } from "./options"; + +export interface WidgetContextMenuAction { + key: string; + label: stringOrTranslation; + icon?: TablerIcon; + onClick: () => void; + hidden?: boolean; + disabled?: boolean; + color?: string; +} + +export interface WidgetContextMenuContext { + isEditMode: boolean; + boardId: string | undefined; + itemId: string | undefined; +} + +export interface WidgetContextActionProps< + TKind extends WidgetKind, + TOptions extends WidgetOptionsRecord = WidgetOptionsRecord, +> { + kind: TKind; + options: inferOptionsFromDefinition; + setOptions: (partial: Partial>) => void; + integrationIds: string[]; + context: WidgetContextMenuContext; + widgetStateRef: React.MutableRefObject | null>; +} const createWithDynamicImport = (kind: TKind, definition: TDefinition) => @@ -56,6 +85,7 @@ export interface WidgetDefinition { } > >; + contextActions?: (props: Omit, "kind">) => WidgetContextMenuAction[]; } export interface WidgetProps { @@ -70,6 +100,7 @@ export type WidgetComponentProps = WidgetProps setOptions: ({ newOptions }: { newOptions: Partial>> }) => void; width: number; height: number; + widgetStateRef?: React.MutableRefObject | null>; }; export type WidgetOptionsRecordOf = WidgetImports[TKind]["definition"]["createOptions"]; diff --git a/packages/widgets/src/dns-hole/controls/component.tsx b/packages/widgets/src/dns-hole/controls/component.tsx index afe397cfd..f46c3d746 100644 --- a/packages/widgets/src/dns-hole/controls/component.tsx +++ b/packages/widgets/src/dns-hole/controls/component.tsx @@ -51,99 +51,16 @@ export default function DnsHoleControlsWidget({ .map(({ id }) => id) .filter((id) => integrationIds.includes(id)); - const { data: summaries = [] } = clientApi.widget.dnsHole.summary.useQuery( - { - integrationIds, - }, - { - staleTime: 5 * 1000, - refetchOnWindowFocus: false, - refetchOnReconnect: false, - retry: false, - }, - ); + const { data: summaries = [] } = clientApi.widget.dnsHole.summary.useQuery({ + integrationIds, + }); const utils = clientApi.useUtils(); - // Subscribe to summary updates - clientApi.widget.dnsHole.subscribeToSummary.useSubscription( - { - integrationIds, - }, - { - onData: (data) => { - utils.widget.dnsHole.summary.setData( - { - integrationIds, - }, - (prevData) => { - if (!prevData) return undefined; - const newData = prevData.map((summary) => - summary.integration.id === data.integration.id - ? { - integration: { - ...summary.integration, - updatedAt: new Date(), - }, - summary: data.summary, - } - : summary, - ); - - return newData; - }, - ); - }, - }, - ); - - // Mutations for dnsHole state, set to undefined on click, and change again on settle const { mutate: enableDns } = clientApi.widget.dnsHole.enable.useMutation({ - onSettled: (_, error, { integrationId }) => { - utils.widget.dnsHole.summary.setData( - { - integrationIds, - }, - (prevData) => { - if (!prevData) return []; - - return prevData.map((item) => - item.integration.id === integrationId - ? { - ...item, - summary: { - ...item.summary, - status: error ? "disabled" : "enabled", - }, - } - : item, - ); - }, - ); - }, + onSettled: () => void utils.widget.dnsHole.summary.invalidate(), }); const { mutate: disableDns } = clientApi.widget.dnsHole.disable.useMutation({ - onSettled: (_, error, { integrationId }) => { - utils.widget.dnsHole.summary.setData( - { - integrationIds, - }, - (prevData) => { - if (!prevData) return []; - - return prevData.map((item) => - item.integration.id === integrationId - ? { - ...item, - summary: { - ...item.summary, - status: error ? "enabled" : "disabled", - }, - } - : item, - ); - }, - ); - }, + onSettled: () => void utils.widget.dnsHole.summary.invalidate(), }); const toggleDns = (integrationId: string) => { const integrationStatus = summaries.find(({ integration }) => integration.id === integrationId); diff --git a/packages/widgets/src/dns-hole/summary/component.tsx b/packages/widgets/src/dns-hole/summary/component.tsx index 4a4c2879c..4bcc82568 100644 --- a/packages/widgets/src/dns-hole/summary/component.tsx +++ b/packages/widgets/src/dns-hole/summary/component.tsx @@ -20,46 +20,12 @@ import type { widgetKind } from "."; import type { WidgetComponentProps, WidgetProps } from "../../definition"; export default function DnsHoleSummaryWidget({ options, integrationIds }: WidgetComponentProps) { - const { data: summaries = [] } = clientApi.widget.dnsHole.summary.useQuery( - { - integrationIds, - }, - { - staleTime: 5 * 1000, - refetchOnWindowFocus: false, - refetchOnReconnect: false, - retry: false, - }, - ); - const utils = clientApi.useUtils(); + const { data: summaries = [] } = clientApi.widget.dnsHole.summary.useQuery({ + integrationIds, + }); const t = useI18n(); - clientApi.widget.dnsHole.subscribeToSummary.useSubscription( - { - integrationIds, - }, - { - onData: (data) => { - utils.widget.dnsHole.summary.setData( - { - integrationIds, - }, - (prevData) => { - if (!prevData) { - return undefined; - } - - const newData = prevData.map((item) => - item.integration.id === data.integration.id ? { ...item, summary: data.summary } : item, - ); - return newData; - }, - ); - }, - }, - ); - const data = useMemo(() => summaries.flatMap(({ summary }) => summary), [summaries]); return ( diff --git a/packages/widgets/src/docker/component.tsx b/packages/widgets/src/docker/component.tsx index d6c1d004d..42db221a2 100644 --- a/packages/widgets/src/docker/component.tsx +++ b/packages/widgets/src/docker/component.tsx @@ -1,10 +1,10 @@ "use client"; import { useMemo } from "react"; -import { ActionIcon, Avatar, Badge, Group, Stack, Text, Tooltip } from "@mantine/core"; +import { ActionIcon, Avatar, Badge, Center, Group, Stack, Text, Tooltip } from "@mantine/core"; import type { IconProps } from "@tabler/icons-react"; import { IconBrandDocker, IconPlayerPlay, IconPlayerStop, IconRotateClockwise } from "@tabler/icons-react"; -import type { MRT_ColumnDef } from "mantine-react-table"; +import type { MRT_ColumnDef, MRT_VisibilityState } from "mantine-react-table"; import { MantineReactTable } from "mantine-react-table"; import type { RouterOutputs } from "@homarr/api"; @@ -184,20 +184,13 @@ export default function DockerWidget({ options, width, isEditMode }: WidgetCompo const t = useScopedI18n("docker"); const isTiny = width <= 256; - const utils = clientApi.useUtils(); const { data } = clientApi.docker.getContainers.useQuery(undefined, { - staleTime: 20 * 1000, + refetchInterval: 30_000, }); const containers = data?.containers ?? []; const timestamp = useMemo(() => data?.timestamp ?? new Date(), [data?.timestamp]); const relativeTime = useTimeAgo(timestamp); - clientApi.docker.subscribeContainers.useSubscription(undefined, { - onData(data) { - utils.docker.getContainers.setData(undefined, { containers: data, timestamp: new Date() }); - }, - }); - const totalContainers = containers.length; const totals = useMemo(() => { @@ -213,6 +206,15 @@ export default function DockerWidget({ options, width, isEditMode }: WidgetCompo const columns = useMemo(() => createColumns(t), [t]); + const columnVisibility: MRT_VisibilityState = { + name: options.columns.includes("name"), + state: options.columns.includes("state"), + host: options.columns.includes("host"), + cpuUsage: options.columns.includes("cpuUsage"), + memoryUsage: options.columns.includes("memoryUsage"), + actions: options.columns.includes("actions"), + }; + const table = useTranslatedMantineReactTable({ columns, data: containers, @@ -233,6 +235,9 @@ export default function DockerWidget({ options, width, isEditMode }: WidgetCompo sorting: [{ id: options.defaultSort, desc: options.descendingDefaultSort }], density: "xs", }, + state: { + columnVisibility, + }, mantinePaperProps: { flex: 1, withBorder: false, @@ -260,6 +265,13 @@ export default function DockerWidget({ options, width, isEditMode }: WidgetCompo }, }); + if (options.columns.length === 0) + return ( +
+ {t("error.noColumns")} +
+ ); + return ( diff --git a/packages/widgets/src/docker/index.ts b/packages/widgets/src/docker/index.ts index 1a0964af3..76cdb97f3 100644 --- a/packages/widgets/src/docker/index.ts +++ b/packages/widgets/src/docker/index.ts @@ -12,10 +12,29 @@ const columnsList = [ "memoryUsage", ] as const satisfies (keyof RouterOutputs["docker"]["getContainers"]["containers"][number])[]; +const allColumnsList = ["name", "state", "host", "cpuUsage", "memoryUsage", "actions"] as const; + +const columnTranslationKeyMap = { + name: "docker.field.name.label", + state: "docker.field.state.label", + host: "docker.field.host.label", + cpuUsage: "docker.field.stats.cpu.label", + memoryUsage: "docker.field.stats.memory.label", + actions: "docker.action.title", +} as const satisfies Record<(typeof allColumnsList)[number], string>; + export const { definition, componentLoader } = createWidgetDefinition("dockerContainers", { icon: IconBrandDocker, createOptions() { return optionsBuilder.from((factory) => ({ + columns: factory.multiSelect({ + defaultValue: [...allColumnsList], + options: allColumnsList.map((value) => ({ + value, + label: (t) => t(columnTranslationKeyMap[value]), + })), + searchable: true, + }), enableRowSorting: factory.switch({ defaultValue: false, }), diff --git a/packages/widgets/src/downloads/component.tsx b/packages/widgets/src/downloads/component.tsx index 60e970c22..0edfb9ba5 100644 --- a/packages/widgets/src/downloads/component.tsx +++ b/packages/widgets/src/downloads/component.tsx @@ -1,6 +1,7 @@ "use client"; import "../widgets-common.css"; +import "./styles.css"; import { useCallback, useMemo, useState } from "react"; import type { MantineStyleProp } from "@mantine/core"; @@ -87,19 +88,10 @@ export default function DownloadClientsWidget({ integrationIds.includes(id) ? [id] : [], ); - const { data: currentItems = [] } = clientApi.widget.downloads.getJobsAndStatuses.useQuery( - { - integrationIds, - limitPerIntegration: options.limitPerIntegration, - }, - { - staleTime: 5 * 1000, - refetchOnWindowFocus: false, - refetchOnReconnect: false, - retry: false, - }, - ); - const utils = clientApi.useUtils(); + const { data: currentItems = [] } = clientApi.widget.downloads.getJobsAndStatuses.useQuery({ + integrationIds, + limitPerIntegration: options.limitPerIntegration, + }); const t = useScopedI18n("widget.downloads"); const tCommon = useScopedI18n("common"); @@ -120,40 +112,13 @@ export default function DownloadClientsWidget({ return statuses; }, [currentItems]); - //Get API mutation functions - const { mutate: mutateResumeItem } = clientApi.widget.downloads.resumeItem.useMutation(); - const { mutate: mutatePauseItem } = clientApi.widget.downloads.pauseItem.useMutation(); - const { mutate: mutateDeleteItem } = clientApi.widget.downloads.deleteItem.useMutation(); + const utils = clientApi.useUtils(); + const invalidateDownloads = { onSettled: () => void utils.widget.downloads.getJobsAndStatuses.invalidate() }; + const { mutate: mutateResumeItem } = clientApi.widget.downloads.resumeItem.useMutation(invalidateDownloads); + const { mutate: mutatePauseItem } = clientApi.widget.downloads.pauseItem.useMutation(invalidateDownloads); + const { mutate: mutateDeleteItem } = clientApi.widget.downloads.deleteItem.useMutation(invalidateDownloads); - //Subscribe to dynamic data changes - clientApi.widget.downloads.subscribeToJobsAndStatuses.useSubscription( - { - integrationIds, - limitPerIntegration: options.limitPerIntegration, - }, - { - onData: (data) => { - utils.widget.downloads.getJobsAndStatuses.setData( - { integrationIds, limitPerIntegration: options.limitPerIntegration }, - (prevData) => { - return prevData?.map((item) => { - if (item.integration.id !== data.integration.id) return item; - - return { - data: data.data, - integration: { - ...data.integration, - updatedAt: new Date(), - }, - }; - }); - }, - ); - }, - }, - ); - - //Flatten Data array for which each element has it's integration, data (base + calculated) and actions. Memoized on data subscription + //Flatten Data array for which each element has its integration, data (base + calculated) and actions const data = useMemo( () => currentItems @@ -577,7 +542,7 @@ export default function DownloadClientsWidget({ enableColumnOrdering: isEditMode, enableRowVirtualization: true, rowVirtualizerOptions: { overscan: 5 }, - mantinePaperProps: { flex: 1, withBorder: false, shadow: undefined }, + mantinePaperProps: { flex: 1, withBorder: false, shadow: undefined, bg: "transparent" }, mantineTableContainerProps: { style: { height: "100%" } }, mantineTableProps: { className: "downloads-widget-table", @@ -784,8 +749,10 @@ const ClientsControl = ({ clients, filters, setFilters, availableStatuses }: Cli "/s", ); - const { mutate: mutateResumeQueue } = clientApi.widget.downloads.resume.useMutation(); - const { mutate: mutatePauseQueue } = clientApi.widget.downloads.pause.useMutation(); + const utils = clientApi.useUtils(); + const invalidateDownloads = { onSettled: () => void utils.widget.downloads.getJobsAndStatuses.invalidate() }; + const { mutate: mutateResumeQueue } = clientApi.widget.downloads.resume.useMutation(invalidateDownloads); + const { mutate: mutatePauseQueue } = clientApi.widget.downloads.pause.useMutation(invalidateDownloads); const [opened, { open, close }] = useDisclosure(false); const t = useScopedI18n("widget.downloads"); return ( diff --git a/packages/widgets/src/downloads/styles.css b/packages/widgets/src/downloads/styles.css new file mode 100644 index 000000000..ae9bee8c1 --- /dev/null +++ b/packages/widgets/src/downloads/styles.css @@ -0,0 +1,12 @@ +/* Make the MantineReactTable transparent so the board card's opacity-controlled + background shows through. MRT defaults to var(--mantine-color-body) which is opaque. */ +.downloads-widget-table, +.downloads-widget-table .mantine-Table-table, +.downloads-widget-table .mantine-Table-thead, +.downloads-widget-table .mantine-Table-tbody, +.downloads-widget-table .mantine-Table-tfoot, +.downloads-widget-table .mantine-Table-tr, +.downloads-widget-table th, +.downloads-widget-table td { + background-color: transparent !important; +} diff --git a/packages/widgets/src/firewall/component.tsx b/packages/widgets/src/firewall/component.tsx index 7318cd3d3..523eadd8d 100644 --- a/packages/widgets/src/firewall/component.tsx +++ b/packages/widgets/src/firewall/component.tsx @@ -29,10 +29,16 @@ export default function FirewallWidget({ integrationIds, width, itemId }: Widget } }, []); - const firewallsCpuData = useUpdatingCpuStatus(integrationIds); - const firewallsMemoryData = useUpdatingMemoryStatus(integrationIds); - const firewallsVersionData = useUpdatingVersionStatus(integrationIds); - const firewallsInterfacesData = useUpdatingInterfacesStatus(integrationIds); + const { data: firewallsCpuData = [] } = clientApi.widget.firewall.getFirewallCpuStatus.useQuery({ integrationIds }); + const { data: firewallsMemoryData = [] } = clientApi.widget.firewall.getFirewallMemoryStatus.useQuery({ + integrationIds, + }); + const { data: firewallsVersionData = [] } = clientApi.widget.firewall.getFirewallVersionStatus.useQuery({ + integrationIds, + }); + const { data: firewallsInterfacesData = [] } = clientApi.widget.firewall.getFirewallInterfacesStatus.useQuery({ + integrationIds, + }); const initialSelectedFirewall = firewallsVersionData[0] ? firewallsVersionData[0].integration.id : "undefined"; const isTiny = width < 256; @@ -182,168 +188,6 @@ export default function FirewallWidget({ integrationIds, width, itemId }: Widget ); } -export const useUpdatingCpuStatus = (integrationIds: string[]) => { - const utils = clientApi.useUtils(); - const { data: firewallsCpuData = [] } = clientApi.widget.firewall.getFirewallCpuStatus.useQuery( - { - integrationIds, - }, - { - staleTime: 5 * 1000, - refetchOnWindowFocus: false, - refetchOnReconnect: false, - retry: false, - }, - ); - - clientApi.widget.firewall.subscribeFirewallCpuStatus.useSubscription( - { - integrationIds, - }, - { - onData: (data) => { - utils.widget.firewall.getFirewallCpuStatus.setData( - { - integrationIds, - }, - (prevData) => { - if (!prevData) { - return undefined; - } - - return prevData.map((item) => - item.integration.id === data.integration.id ? { ...item, summary: data.summary } : item, - ); - }, - ); - }, - }, - ); - - return firewallsCpuData; -}; - -export const useUpdatingMemoryStatus = (integrationIds: string[]) => { - const utils = clientApi.useUtils(); - const { data: firewallsMemoryData = [] } = clientApi.widget.firewall.getFirewallMemoryStatus.useQuery( - { - integrationIds, - }, - { - staleTime: 15 * 1000, - refetchOnWindowFocus: false, - refetchOnReconnect: false, - retry: false, - }, - ); - - clientApi.widget.firewall.subscribeFirewallMemoryStatus.useSubscription( - { - integrationIds, - }, - { - onData: (data) => { - utils.widget.firewall.getFirewallMemoryStatus.setData( - { - integrationIds, - }, - (prevData) => { - if (!prevData) { - return undefined; - } - - return prevData.map((item) => - item.integration.id === data.integration.id ? { ...item, summary: data.summary } : item, - ); - }, - ); - }, - }, - ); - - return firewallsMemoryData; -}; - -export const useUpdatingVersionStatus = (integrationIds: string[]) => { - const utils = clientApi.useUtils(); - const { data: firewallsVersionData = [] } = clientApi.widget.firewall.getFirewallVersionStatus.useQuery( - { - integrationIds, - }, - { - staleTime: 60 * 60 * 1000, - refetchOnWindowFocus: false, - refetchOnReconnect: false, - retry: false, - }, - ); - - clientApi.widget.firewall.subscribeFirewallVersionStatus.useSubscription( - { - integrationIds, - }, - { - onData: (data) => { - utils.widget.firewall.getFirewallVersionStatus.setData( - { - integrationIds, - }, - (prevData) => { - if (!prevData) { - return undefined; - } - - return prevData.map((item) => - item.integration.id === data.integration.id ? { ...item, summary: data.summary } : item, - ); - }, - ); - }, - }, - ); - return firewallsVersionData; -}; - -export const useUpdatingInterfacesStatus = (integrationIds: string[]) => { - const utils = clientApi.useUtils(); - const { data: firewallsInterfacesData = [] } = clientApi.widget.firewall.getFirewallInterfacesStatus.useQuery( - { - integrationIds, - }, - { - staleTime: 30 * 1000, - refetchOnWindowFocus: false, - refetchOnReconnect: false, - retry: false, - }, - ); - - clientApi.widget.firewall.subscribeFirewallInterfacesStatus.useSubscription( - { - integrationIds, - }, - { - onData: (data) => { - utils.widget.firewall.getFirewallInterfacesStatus.setData( - { - integrationIds, - }, - (prevData) => { - if (!prevData) { - return undefined; - } - return prevData.map((item) => - item.integration.id === data.integration.id ? { ...item, summary: data.summary } : item, - ); - }, - ); - }, - }, - ); - - return firewallsInterfacesData; -}; - export function formatBitsPerSec(bytes: number, decimals: number): string { if (bytes === 0) return "0 b/s"; diff --git a/packages/widgets/src/health-monitoring/cluster/cluster-health.tsx b/packages/widgets/src/health-monitoring/cluster/cluster-health.tsx index daa0b40ec..1ac7922d5 100644 --- a/packages/widgets/src/health-monitoring/cluster/cluster-health.tsx +++ b/packages/widgets/src/health-monitoring/cluster/cluster-health.tsx @@ -35,27 +35,9 @@ export const ClusterHealthMonitoring = ({ width, }: WidgetComponentProps<"healthMonitoring"> & { integrationId: string }) => { const t = useI18n(); - const { data: healthData } = clientApi.widget.healthMonitoring.getClusterHealthStatus.useQuery( - { - integrationId, - }, - { - staleTime: 5 * 1000, - refetchOnWindowFocus: false, - refetchOnReconnect: false, - retry: false, - }, - ); - - const utils = clientApi.useUtils(); - clientApi.widget.healthMonitoring.subscribeClusterHealthStatus.useSubscription( - { integrationId }, - { - onData(data) { - utils.widget.healthMonitoring.getClusterHealthStatus.setData({ integrationId }, data); - }, - }, - ); + const { data: healthData } = clientApi.widget.healthMonitoring.getClusterHealthStatus.useQuery({ + integrationId, + }); if (!healthData) return ; diff --git a/packages/widgets/src/health-monitoring/system-health.tsx b/packages/widgets/src/health-monitoring/system-health.tsx index 405838cac..8b77b1d2f 100644 --- a/packages/widgets/src/health-monitoring/system-health.tsx +++ b/packages/widgets/src/health-monitoring/system-health.tsx @@ -52,39 +52,12 @@ export const SystemHealthMonitoring = ({ width, }: WidgetComponentProps<"healthMonitoring">) => { const t = useI18n(); - const { data: healthData = [] } = clientApi.widget.healthMonitoring.getSystemHealthStatus.useQuery( - { - integrationIds, - }, - { - staleTime: 5 * 1000, - refetchOnWindowFocus: false, - refetchOnReconnect: false, - retry: false, - }, - ); + const { data: healthData = [] } = clientApi.widget.healthMonitoring.getSystemHealthStatus.useQuery({ + integrationIds, + }); const [opened, { open, close }] = useDisclosure(false); - const utils = clientApi.useUtils(); const board = useRequiredBoard(); - clientApi.widget.healthMonitoring.subscribeSystemHealthStatus.useSubscription( - { integrationIds }, - { - onData(data) { - utils.widget.healthMonitoring.getSystemHealthStatus.setData({ integrationIds }, (prevData) => { - if (!prevData) { - return undefined; - } - return prevData.map((item) => - item.integrationId === data.integrationId - ? { ...item, healthInfo: data.healthInfo, updatedAt: data.timestamp } - : item, - ); - }); - }, - }, - ); - const isTiny = width < 256; if (healthData.length === 0) return ; diff --git a/packages/widgets/src/immich/album-carousel/component.tsx b/packages/widgets/src/immich/album-carousel/component.tsx index 94795991c..ee7085757 100644 --- a/packages/widgets/src/immich/album-carousel/component.tsx +++ b/packages/widgets/src/immich/album-carousel/component.tsx @@ -1,7 +1,7 @@ "use client"; import type { Dispatch, SetStateAction } from "react"; -import { useEffect, useState } from "react"; +import { useEffect, useMemo, useState } from "react"; import { Box, Center, Group, Image, Stack, Text } from "@mantine/core"; import { IconAlertCircle, IconCalendar } from "@tabler/icons-react"; @@ -23,9 +23,18 @@ export default function ImmichAlbumCarouselWidget({ integrationId: integrationIds[0] ?? "", albumId: options.albumId ?? "", }, - { enabled: Boolean(options.albumId), staleTime: 15 * 60 * 1000 }, + { enabled: Boolean(options.albumId) }, ); + const photoAssets = useMemo(() => { + const assets = album?.assets.filter((asset) => asset.type === "IMAGE") ?? []; + return options.randomizePhotos ? shuffle(assets) : assets; + }, [album?.assets, options.randomizePhotos]); + + useEffect(() => { + setCurrentPhotoIndex(0); + }, [photoAssets]); + if (!options.albumId) { return ; } @@ -36,8 +45,6 @@ export default function ImmichAlbumCarouselWidget({ return ; } - const photoAssets = album.assets.filter((asset) => asset.type === "IMAGE"); - if (photoAssets.length === 0) { return ; } @@ -53,10 +60,22 @@ export default function ImmichAlbumCarouselWidget({ ); } +function shuffle(items: T[]) { + const shuffled = [...items]; + for (let index = shuffled.length - 1; index > 0; index--) { + const randomIndex = Math.floor(Math.random() * (index + 1)); + const current = shuffled[index]; + const random = shuffled[randomIndex]; + if (current === undefined || random === undefined) continue; + shuffled[index] = random; + shuffled[randomIndex] = current; + } + return shuffled; +} + interface CarouselProps { assets: { id: string; - deviceAssetId: string; originalPath: string; fileModifiedAt: string; publicLink: string; diff --git a/packages/widgets/src/immich/album-carousel/index.ts b/packages/widgets/src/immich/album-carousel/index.ts index 6ad37c50a..9f8110ffc 100644 --- a/packages/widgets/src/immich/album-carousel/index.ts +++ b/packages/widgets/src/immich/album-carousel/index.ts @@ -1,6 +1,8 @@ import { IconPhoto } from "@tabler/icons-react"; import z from "zod"; +import { clientApi } from "@homarr/api/client"; + import { createWidgetDefinition } from "../../definition"; import { optionsBuilder } from "../../options"; @@ -10,9 +12,20 @@ export const { definition, componentLoader } = createWidgetDefinition("immich-al integrationsRequired: true, createOptions() { return optionsBuilder.from((factory) => ({ - albumId: factory.text({ - defaultValue: "", + albumId: factory.integrationSelect({ withDescription: true, + clearable: true, + useOptions: (integrationIds: string[]) => { + const { + data = [], + isPending, + isError, + } = clientApi.widget.immich.getAlbums.useQuery( + { integrationId: integrationIds[0] ?? "" }, + { enabled: integrationIds.length > 0, staleTime: 15 * 60 * 1000 }, + ); + return { data: data.map((album) => ({ value: album.id, label: album.albumName })), isPending, isError }; + }, }), rotationIntervalSeconds: factory.number({ defaultValue: 5, @@ -23,6 +36,10 @@ export const { definition, componentLoader } = createWidgetDefinition("immich-al defaultValue: false, withDescription: true, }), + randomizePhotos: factory.switch({ + defaultValue: false, + withDescription: true, + }), })); }, }).withDynamicImport(() => import("./component")); diff --git a/packages/widgets/src/immich/server-stats/component.tsx b/packages/widgets/src/immich/server-stats/component.tsx index e3e1596ea..919b0caf0 100644 --- a/packages/widgets/src/immich/server-stats/component.tsx +++ b/packages/widgets/src/immich/server-stats/component.tsx @@ -17,12 +17,9 @@ export default function ImmichServerStatsWidget({ options, }: WidgetComponentProps<"immich-serverStats">) { const t = useI18n(); - const { data: stats } = clientApi.widget.immich.getServerStats.useQuery( - { - integrationId: integrationIds[0] ?? "", - }, - { staleTime: 15 * 60 * 1000 }, - ); + const { data: stats } = clientApi.widget.immich.getServerStats.useQuery({ + integrationId: integrationIds[0] ?? "", + }); if (!stats) return ; diff --git a/packages/widgets/src/index.tsx b/packages/widgets/src/index.tsx index 17e9b8eed..ca8a5f62f 100644 --- a/packages/widgets/src/index.tsx +++ b/packages/widgets/src/index.tsx @@ -61,7 +61,12 @@ import * as video from "./video"; import * as weather from "./weather"; import * as customApi from "./custom-api"; -export type { WidgetDefinition, WidgetOptionsSettings } from "./definition"; +export type { + WidgetDefinition, + WidgetContextMenuAction, + WidgetContextActionProps, + WidgetOptionsSettings, +} from "./definition"; export type { WidgetComponentProps }; export type { WidgetOptionDefinition, WidgetOptionType } from "./options"; diff --git a/packages/widgets/src/indexer-manager/component.tsx b/packages/widgets/src/indexer-manager/component.tsx index 51ddb1805..4e3fb7c7e 100644 --- a/packages/widgets/src/indexer-manager/component.tsx +++ b/packages/widgets/src/indexer-manager/component.tsx @@ -18,33 +18,13 @@ export default function IndexerManagerWidget({ height, }: WidgetComponentProps<"indexerManager">) { const t = useI18n(); - const { data: indexersData = [] } = clientApi.widget.indexerManager.getIndexersStatus.useQuery( - { integrationIds }, - { - staleTime: 5 * 60 * 1000, - refetchOnWindowFocus: false, - refetchOnReconnect: false, - retry: false, - }, - ); + const { data: indexersData = [] } = clientApi.widget.indexerManager.getIndexersStatus.useQuery({ integrationIds }); + const utils = clientApi.useUtils(); - - const { mutate: testAll, isPending } = clientApi.widget.indexerManager.testAllIndexers.useMutation(); + const { mutate: testAll, isPending } = clientApi.widget.indexerManager.testAllIndexers.useMutation({ + onSettled: () => void utils.widget.indexerManager.getIndexersStatus.invalidate(), + }); const board = useRequiredBoard(); - - clientApi.widget.indexerManager.subscribeIndexersStatus.useSubscription( - { integrationIds }, - { - onData(newData) { - utils.widget.indexerManager.getIndexersStatus.setData({ integrationIds }, (previousData) => - previousData?.map((item) => - item.integrationId === newData.integrationId ? { ...item, indexers: newData.indexers } : item, - ), - ); - }, - }, - ); - const hasSmallWidth = width < 256; const hasSmallHeight = height < 256; diff --git a/packages/widgets/src/media-releases/component.tsx b/packages/widgets/src/media-releases/component.tsx index 61fc9216d..05ba18fe5 100644 --- a/packages/widgets/src/media-releases/component.tsx +++ b/packages/widgets/src/media-releases/component.tsx @@ -6,7 +6,7 @@ import { IconBook, IconCalendar, IconClock, IconStarFilled } from "@tabler/icons import type { RouterOutputs } from "@homarr/api"; import { clientApi } from "@homarr/api/client"; -import { getMantineColor } from "@homarr/common"; +import { getMantineColor, toValidDate } from "@homarr/common"; import { getIconUrl } from "@homarr/definitions"; import type { MediaRelease } from "@homarr/integrations/types"; import { mediaTypeConfigurations } from "@homarr/integrations/types"; @@ -18,12 +18,9 @@ import { OverflowBadge } from "@homarr/ui"; import type { WidgetComponentProps } from "../definition"; export default function MediaReleasesWidget({ options, integrationIds }: WidgetComponentProps<"mediaReleases">) { - const { data: releases = [] } = clientApi.widget.mediaRelease.getMediaReleases.useQuery( - { - integrationIds, - }, - { staleTime: 5 * 60 * 1000 }, - ); + const { data: releases = [] } = clientApi.widget.mediaRelease.getMediaReleases.useQuery({ + integrationIds, + }); return ( @@ -42,6 +39,19 @@ interface ItemProps { options: WidgetComponentProps<"mediaReleases">["options"]; } +const formatReleaseDate = (value: unknown, locale: string) => { + const date = toValidDate(value); + if (!date) return "—"; + return Intl.DateTimeFormat(locale, { + month: "2-digit", + year: "numeric", + day: "2-digit", + hour: "2-digit", + minute: "2-digit", + hour12: false, + }).format(date); +}; + const Item = ({ item, options }: ItemProps) => { const locale = useCurrentLocale(); const t = useI18n(); @@ -94,17 +104,7 @@ const Item = ({ item, options }: ItemProps) => { )} - + {length !== undefined && ( <> diff --git a/packages/widgets/src/media-requests/list/component.tsx b/packages/widgets/src/media-requests/list/component.tsx index 3bc24cf70..bbbd0047e 100644 --- a/packages/widgets/src/media-requests/list/component.tsx +++ b/packages/widgets/src/media-requests/list/component.tsx @@ -19,6 +19,7 @@ import { IconSearch, IconThumbDown, IconThumbUp } from "@tabler/icons-react"; import type { RouterInputs, RouterOutputs } from "@homarr/api"; import { clientApi } from "@homarr/api/client"; import { useRequiredBoard } from "@homarr/boards/context"; +import { toValidDate } from "@homarr/common"; import type { MediaRequestStatus } from "@homarr/integrations/types"; import { mediaAvailabilityConfiguration, mediaRequestStatusConfiguration } from "@homarr/integrations/types"; import { openMediaRequestSearch } from "@homarr/spotlight"; @@ -35,44 +36,9 @@ export default function MediaServerWidget({ options, width, }: WidgetComponentProps<"mediaRequests-requestList">) { - const { data: mediaRequests } = clientApi.widget.mediaRequests.getLatestRequests.useQuery( - { - integrationIds, - }, - { - staleTime: 60 * 1000, - refetchOnWindowFocus: false, - refetchOnReconnect: false, - }, - ); - const utils = clientApi.useUtils(); - clientApi.widget.mediaRequests.subscribeToLatestRequests.useSubscription( - { - integrationIds, - }, - { - onData(data) { - utils.widget.mediaRequests.getLatestRequests.setData({ integrationIds }, (prevData) => { - if (!prevData) return []; - - const filteredData = prevData.filter(({ integrationId }) => integrationId !== data.integrationId); - const newData = filteredData.concat( - data.requests.map((request) => ({ ...request, integrationId: data.integrationId })), - ); - return newData.toSorted((dataA, dataB) => { - if (dataA.status === dataB.status) { - return dataB.createdAt.getTime() - dataA.createdAt.getTime(); - } - - return ( - mediaRequestStatusConfiguration[dataA.status].position - - mediaRequestStatusConfiguration[dataB.status].position - ); - }); - }); - }, - }, - ); + const { data: mediaRequests } = clientApi.widget.mediaRequests.getLatestRequests.useQuery({ + integrationIds, + }); if (!mediaRequests) return ; if (mediaRequests.length === 0) throw new NoIntegrationDataError(); @@ -169,7 +135,7 @@ const MediaRequestCard = ({ request, isTiny, options }: MediaRequestCardProps) = - {request.airDate?.getFullYear() ?? t("toBeDetermined")} + {toValidDate(request.airDate)?.getFullYear() ?? t("toBeDetermined")} {!isTiny && ( { - const { mutate: mutateRequestAnswer } = clientApi.widget.mediaRequests.answerRequest.useMutation(); + const utils = clientApi.useUtils(); + const { mutate: mutateRequestAnswer } = clientApi.widget.mediaRequests.answerRequest.useMutation({ + onSettled: () => void utils.widget.mediaRequests.invalidate(), + }); const t = useScopedI18n("widget.mediaRequests-requestList"); const handleDecision = (answer: RouterInputs["widget"]["mediaRequests"]["answerRequest"]["answer"]) => { mutateRequestAnswer({ diff --git a/packages/widgets/src/media-requests/list/index.ts b/packages/widgets/src/media-requests/list/index.ts index 8f1a88575..d4e0fcd54 100644 --- a/packages/widgets/src/media-requests/list/index.ts +++ b/packages/widgets/src/media-requests/list/index.ts @@ -1,6 +1,7 @@ -import { IconZoomQuestion } from "@tabler/icons-react"; +import { IconSearch, IconZoomQuestion } from "@tabler/icons-react"; import { getIntegrationKindsByCategory } from "@homarr/definitions"; +import { openMediaRequestSearch } from "@homarr/spotlight"; import { createWidgetDefinition } from "../../definition"; import { optionsBuilder } from "../../options"; @@ -14,5 +15,15 @@ export const { componentLoader, definition } = createWidgetDefinition("mediaRequ }), })); }, + contextActions: ({ integrationIds }) => [ + { + key: "search", + label: (t) => t("search.mode.media.action.search.label"), + icon: IconSearch, + onClick: () => { + openMediaRequestSearch({ integrationIds }); + }, + }, + ], supportedIntegrations: getIntegrationKindsByCategory("mediaRequest"), }).withDynamicImport(() => import("./component")); diff --git a/packages/widgets/src/media-requests/stats/component.tsx b/packages/widgets/src/media-requests/stats/component.tsx index 51a3e3d5e..5348b845a 100644 --- a/packages/widgets/src/media-requests/stats/component.tsx +++ b/packages/widgets/src/media-requests/stats/component.tsx @@ -36,16 +36,9 @@ export default function MediaServerWidget({ width, }: WidgetComponentProps<"mediaRequests-requestStats">) { const t = useScopedI18n("widget.mediaRequests-requestStats"); - const { data: requestStats } = clientApi.widget.mediaRequests.getStats.useQuery( - { - integrationIds, - }, - { - staleTime: 60 * 1000, - refetchOnWindowFocus: false, - refetchOnReconnect: false, - }, - ); + const { data: requestStats } = clientApi.widget.mediaRequests.getStats.useQuery({ + integrationIds, + }); const board = useRequiredBoard(); diff --git a/packages/widgets/src/media-requests/stats/index.ts b/packages/widgets/src/media-requests/stats/index.ts index 25911c894..d49cb8495 100644 --- a/packages/widgets/src/media-requests/stats/index.ts +++ b/packages/widgets/src/media-requests/stats/index.ts @@ -1,6 +1,7 @@ -import { IconChartBar } from "@tabler/icons-react"; +import { IconChartBar, IconSearch } from "@tabler/icons-react"; import { getIntegrationKindsByCategory } from "@homarr/definitions"; +import { openMediaRequestSearch } from "@homarr/spotlight"; import { createWidgetDefinition } from "../../definition"; @@ -9,5 +10,15 @@ export const { componentLoader, definition } = createWidgetDefinition("mediaRequ createOptions() { return {}; }, + contextActions: ({ integrationIds }) => [ + { + key: "search", + label: (t) => t("search.mode.media.action.search.label"), + icon: IconSearch, + onClick: () => { + openMediaRequestSearch({ integrationIds }); + }, + }, + ], supportedIntegrations: getIntegrationKindsByCategory("mediaRequest"), }).withDynamicImport(() => import("./component")); diff --git a/packages/widgets/src/media-server/component.tsx b/packages/widgets/src/media-server/component.tsx index efa2c2415..e63c12a90 100644 --- a/packages/widgets/src/media-server/component.tsx +++ b/packages/widgets/src/media-server/component.tsx @@ -18,23 +18,11 @@ import { useTranslatedMantineReactTable } from "@homarr/ui/hooks"; import type { WidgetComponentProps } from "../definition"; -export default function MediaServerWidget({ - options, - integrationIds, - isEditMode, -}: WidgetComponentProps<"mediaServer">) { - const { data: currentStreams = [] } = clientApi.widget.mediaServer.getCurrentStreams.useQuery( - { - integrationIds, - showOnlyPlaying: options.showOnlyPlaying, - }, - { - staleTime: 5 * 1000, - refetchOnWindowFocus: false, - refetchOnReconnect: false, - }, - ); - const utils = clientApi.useUtils(); +export default function MediaServerWidget({ options, integrationIds }: WidgetComponentProps<"mediaServer">) { + const { data: currentStreams = [] } = clientApi.widget.mediaServer.getCurrentStreams.useQuery({ + integrationIds, + showOnlyPlaying: options.showOnlyPlaying, + }); const t = useScopedI18n("widget.mediaServer"); const columns = useMemo[]>( @@ -83,32 +71,6 @@ export default function MediaServerWidget({ [t], ); - clientApi.widget.mediaServer.subscribeToCurrentStreams.useSubscription( - { - integrationIds, - showOnlyPlaying: options.showOnlyPlaying, - }, - { - enabled: !isEditMode, - onData(data) { - utils.widget.mediaServer.getCurrentStreams.setData( - { integrationIds, showOnlyPlaying: options.showOnlyPlaying }, - (previousData) => { - return previousData?.map((pair) => { - if (pair.integrationId === data.integrationId) { - return { - ...pair, - sessions: data.data, - }; - } - return pair; - }); - }, - ); - }, - }, - ); - // Only render the flat list of sessions when the currentStreams change // Otherwise it will always create a new array reference and cause the table to re-render const flatSessions = useMemo( diff --git a/packages/widgets/src/media-transcoding/component.tsx b/packages/widgets/src/media-transcoding/component.tsx index 8b0ba6591..fe4873e45 100644 --- a/packages/widgets/src/media-transcoding/component.tsx +++ b/packages/widgets/src/media-transcoding/component.tsx @@ -38,18 +38,7 @@ export default function MediaTranscodingWidget({ pageSize: queuePageSize, page: queuePage, }; - const { data: transcodingData } = clientApi.widget.mediaTranscoding.getDataAsync.useQuery(input, { - staleTime: 5 * 60 * 1000, - refetchOnWindowFocus: false, - refetchOnReconnect: false, - }); - - const utils = clientApi.useUtils(); - clientApi.widget.mediaTranscoding.subscribeData.useSubscription(input, { - onData(data) { - utils.widget.mediaTranscoding.getDataAsync.setData(input, data); - }, - }); + const { data: transcodingData } = clientApi.widget.mediaTranscoding.getDataAsync.useQuery(input); const [view, setView] = useState(options.defaultView); const t = useI18n("widget.mediaTranscoding"); diff --git a/packages/widgets/src/minecraft/server-status/component.tsx b/packages/widgets/src/minecraft/server-status/component.tsx index ddf4d6e4b..20214e3ac 100644 --- a/packages/widgets/src/minecraft/server-status/component.tsx +++ b/packages/widgets/src/minecraft/server-status/component.tsx @@ -11,18 +11,7 @@ import { WidgetEmptyState } from "../../common/empty-state"; import type { WidgetComponentProps } from "../../definition"; export default function MinecraftServerStatusWidget({ options }: WidgetComponentProps<"minecraftServerStatus">) { - const { data: result } = clientApi.widget.minecraft.getServerStatus.useQuery(options, { - staleTime: 5 * 60 * 1000, - }); - const utils = clientApi.useUtils(); - clientApi.widget.minecraft.subscribeServerStatus.useSubscription(options, { - onData(data) { - utils.widget.minecraft.getServerStatus.setData(options, { - data, - timestamp: new Date(), - }); - }, - }); + const { data: result } = clientApi.widget.minecraft.getServerStatus.useQuery(options); const tStatus = useScopedI18n("widget.minecraftServerStatus.status"); if (!result) return ; diff --git a/packages/widgets/src/modals/widget-edit-modal.tsx b/packages/widgets/src/modals/widget-edit-modal.tsx index be0d3a178..d1713298c 100644 --- a/packages/widgets/src/modals/widget-edit-modal.tsx +++ b/packages/widgets/src/modals/widget-edit-modal.tsx @@ -38,6 +38,7 @@ interface ModalProps { integrationData: IntegrationSelectOption[]; integrationSupport: boolean; settings: SettingsContextProps; + itemId?: string; appId?: string; } @@ -146,6 +147,7 @@ export const WidgetEditModal = createModal>(({ actions, i property={key} options={value as never} initialOptions={innerProps.value.options} + itemId={innerProps.itemId} /> ); })} diff --git a/packages/widgets/src/network-controller/network-status/component.tsx b/packages/widgets/src/network-controller/network-status/component.tsx index 846d1128f..c4e892dba 100644 --- a/packages/widgets/src/network-controller/network-status/component.tsx +++ b/packages/widgets/src/network-controller/network-status/component.tsx @@ -21,48 +21,9 @@ export default function NetworkControllerNetworkStatusWidget({ options, integrationIds, }: WidgetComponentProps<"networkControllerStatus">) { - const { data: summaries = [] } = clientApi.widget.networkController.summary.useQuery( - { - integrationIds, - }, - { - staleTime: 5 * 60 * 1000, - refetchOnWindowFocus: false, - refetchOnReconnect: false, - retry: false, - }, - ); - const utils = clientApi.useUtils(); - - clientApi.widget.networkController.subscribeToSummary.useSubscription( - { - integrationIds, - }, - { - onData: (data) => { - utils.widget.networkController.summary.setData( - { - integrationIds, - }, - (prevData) => { - if (!prevData) { - return undefined; - } - - return prevData.map((item) => - item.integration.id === data.integration.id - ? { - ...item, - summary: data.summary, - updatedAt: new Date(), - } - : item, - ); - }, - ); - }, - }, - ); + const { data: summaries = [] } = clientApi.widget.networkController.summary.useQuery({ + integrationIds, + }); const data = useMemo(() => summaries.flatMap(({ summary }) => summary), [summaries]); diff --git a/packages/widgets/src/network-controller/summary/component.tsx b/packages/widgets/src/network-controller/summary/component.tsx index 21a4b6a93..cb5dc7545 100644 --- a/packages/widgets/src/network-controller/summary/component.tsx +++ b/packages/widgets/src/network-controller/summary/component.tsx @@ -20,45 +20,12 @@ dayjs.extend(duration); export default function NetworkControllerSummaryWidget({ integrationIds, }: WidgetComponentProps<"networkControllerSummary">) { - const { data: summaries = [] } = clientApi.widget.networkController.summary.useQuery( - { - integrationIds, - }, - { - staleTime: 5 * 60 * 1000, - refetchOnWindowFocus: false, - refetchOnReconnect: false, - retry: false, - }, - ); - const utils = clientApi.useUtils(); + const { data: summaries = [] } = clientApi.widget.networkController.summary.useQuery({ + integrationIds, + }); const t = useI18n(); - clientApi.widget.networkController.subscribeToSummary.useSubscription( - { - integrationIds, - }, - { - onData: (data) => { - utils.widget.networkController.summary.setData( - { - integrationIds, - }, - (prevData) => { - if (!prevData) { - return undefined; - } - - return prevData.map((item) => - item.integration.id === data.integration.id ? { ...item, summary: data.summary } : item, - ); - }, - ); - }, - }, - ); - const data = useMemo(() => summaries.flatMap(({ summary }) => summary), [summaries]); return ( diff --git a/packages/widgets/src/notebook/notebook.tsx b/packages/widgets/src/notebook/notebook.tsx index 97721daf7..229b3730a 100644 --- a/packages/widgets/src/notebook/notebook.tsx +++ b/packages/widgets/src/notebook/notebook.tsx @@ -1,5 +1,6 @@ "use client"; +import type { MouseEvent as ReactMouseEvent } from "react"; import { useCallback, useEffect, useRef, useState } from "react"; import { ActionIcon, @@ -263,8 +264,21 @@ export function Notebook({ options, setOptions, isEditMode, boardId, itemId }: W setIsEditing(handleEditToggleCallback); }, [setIsEditing, handleEditToggleCallback]); + const handleDoubleClick = useCallback( + (event: ReactMouseEvent) => { + if (!canChange || isEditing) return; + // Ignore double-clicks bubbling up from interactive controls (e.g. the + // edit/save ActionIcon), which would otherwise toggle edit mode twice. + if (event.target instanceof Element && event.target.closest("button, a")) { + return; + } + setIsEditing(handleEditToggleCallback); + }, + [canChange, isEditing, setIsEditing, handleEditToggleCallback], + ); + return ( - + ) { - const { data: notificationIntegrations = [] } = clientApi.widget.notifications.getNotifications.useQuery( - { - ...options, - integrationIds, - }, - { - staleTime: 5 * 60 * 1000, - refetchOnWindowFocus: false, - refetchOnReconnect: false, - retry: false, - }, - ); - const utils = clientApi.useUtils(); - - clientApi.widget.notifications.subscribeNotifications.useSubscription( - { - ...options, - integrationIds, - }, - { - onData: (data) => { - utils.widget.notifications.getNotifications.setData({ ...options, integrationIds }, (prevData) => { - return prevData?.map((item) => { - if (item.integration.id !== data.integration.id) return item; - - return { - data: data.data, - integration: { - ...data.integration, - updatedAt: new Date(), - }, - }; - }); - }); - }, - }, - ); + const { data: notificationIntegrations = [] } = clientApi.widget.notifications.getNotifications.useQuery({ + ...options, + integrationIds, + }); const t = useScopedI18n("widget.notifications"); diff --git a/packages/widgets/src/options.ts b/packages/widgets/src/options.ts index 1d19f628a..bf82ef226 100644 --- a/packages/widgets/src/options.ts +++ b/packages/widgets/src/options.ts @@ -13,6 +13,7 @@ import type { ReleasesRepository } from "./releases/releases-repository"; interface CommonInput { defaultValue?: TType; withDescription?: boolean; + skipContextMenu?: boolean; } interface TextInput extends CommonInput { @@ -54,6 +55,7 @@ interface DynamicSelectInput extends CommonInput { useOptions: ( query: string, integrationIds: string[], + options: Record, ) => { isPending: boolean; options: DynamicSelectOption[]; @@ -91,6 +93,7 @@ const optionsFactory = { type: "switch" as const, defaultValue: input?.defaultValue ?? false, withDescription: input?.withDescription ?? false, + skipContextMenu: input?.skipContextMenu ?? false, }), text: (input?: TextInput) => ({ type: "text" as const, diff --git a/packages/widgets/src/paperless-ngx/component.tsx b/packages/widgets/src/paperless-ngx/component.tsx index 3b476aa0d..08bcee3fb 100644 --- a/packages/widgets/src/paperless-ngx/component.tsx +++ b/packages/widgets/src/paperless-ngx/component.tsx @@ -72,10 +72,9 @@ const heroPartVisibility = { export default function PaperlessNgxWidget({ integrationIds, options, width }: WidgetComponentProps<"paperlessNgx">) { const t = useScopedI18n("widget.paperlessNgx"); - const { data: stats } = clientApi.widget.paperlessNgx.getStats.useQuery( - { integrationId: integrationIds[0] ?? "" }, - { staleTime: 5 * 60 * 1000 }, - ); + const { data: stats } = clientApi.widget.paperlessNgx.getStats.useQuery({ + integrationId: integrationIds[0] ?? "", + }); if (!stats) return ; diff --git a/packages/widgets/src/releases/component.tsx b/packages/widgets/src/releases/component.tsx index cc41e8b68..f0e32df7d 100644 --- a/packages/widgets/src/releases/component.tsx +++ b/packages/widgets/src/releases/component.tsx @@ -20,8 +20,9 @@ import ReactMarkdown from "react-markdown"; import { clientApi } from "@homarr/api/client"; import { useRequiredBoard } from "@homarr/boards/context"; -import { isDateWithin, isNullOrWhitespace, splitToChunksWithNItems } from "@homarr/common"; -import { getIconUrl } from "@homarr/definitions"; +import { createId, isDateWithin, isNullOrWhitespace, splitToChunksWithNItems } from "@homarr/common"; +import type { ReleaseProviderKind } from "@homarr/definitions"; +import { getReleaseProviderIconUrl, getReleaseProviderName } from "@homarr/definitions"; import { useScopedI18n } from "@homarr/translation/client"; import { MaskedOrNormalImage } from "@homarr/ui"; @@ -35,7 +36,7 @@ const formatRelativeDate = (value: string): string => { return isMonths ? value.toUpperCase() : isOtherUnits ? value.toLowerCase() : value; }; -export default function ReleasesWidget({ options }: WidgetComponentProps<"releases">) { +export default function ReleasesWidget({ options, itemId }: WidgetComponentProps<"releases">) { const t = useScopedI18n("widget.releases"); const now = useNow(); const formatter = useFormatter(); @@ -55,11 +56,16 @@ export default function ReleasesWidget({ options }: WidgetComponentProps<"releas [options.newReleaseWithin, options.staleReleaseWithin], ); - // Group repositories by integration + const normalizedRepositories = useMemo( + () => options.repositories.map((repo) => (repo.id ? repo : { ...repo, id: createId() })), + [options.repositories], + ); + + // Group repositories by provider const groupedRepositories = useMemo(() => { - return options.repositories.reduce( + return normalizedRepositories.reduce( (acc, repo) => { - const key = repo.providerIntegrationId; + const key = repo.provider; if (!key) return acc; acc[key] ??= []; @@ -67,28 +73,30 @@ export default function ReleasesWidget({ options }: WidgetComponentProps<"releas return acc; }, - {} as Record, + {} as Partial>, ); - }, [options.repositories]); + }, [normalizedRepositories]); // For each group, split into chunks of 5 const batchedRepositories = useMemo(() => { - return Object.entries(groupedRepositories).flatMap(([integrationId, group]) => + return Object.entries(groupedRepositories).flatMap(([provider, group]) => splitToChunksWithNItems(group, 5).map((chunk) => ({ - integrationId, + provider: provider as ReleaseProviderKind, repositories: chunk, })), ); }, [groupedRepositories]); - const queryResults = clientApi.useQueries((t) => - batchedRepositories.flatMap(({ integrationId, repositories }) => - t.widget.releases.getLatest({ - integrationId, + const queryResults = clientApi.useQueries((trpc) => + batchedRepositories.flatMap(({ provider, repositories }) => + trpc.widget.releases.getLatest({ + itemId, repositories: repositories.map((repository) => ({ id: repository.id, + provider, identifier: repository.identifier, versionFilter: repository.versionFilter, + providerUrl: repository.providerUrl, })), }), ), @@ -100,15 +108,15 @@ export default function ReleasesWidget({ options }: WidgetComponentProps<"releas ); const repositories = useMemo(() => { - const formattedResults = options.repositories + const formattedResults = normalizedRepositories .map((repository) => { - if (!repository.providerIntegrationId) return { ...repository, error: { code: "noProviderSeleceted" } }; + if (!repository.provider) return { ...repository, error: { code: "noProviderSelected" } }; const repositoryResult = results.flat().find(({ id }) => id === repository.id); if (!repositoryResult) return { ...repository, error: { code: "noProviderResponse" } }; if (!repositoryResult.success) return { ...repository, error: repositoryResult.error }; - const { data: release, integration } = repositoryResult; + const { data: release, provider } = repositoryResult; const isReleaseWithin = (relativeDate: string) => Boolean(relativeDate) && isDateWithin(release.latestReleaseAt, relativeDate); @@ -116,7 +124,7 @@ export default function ReleasesWidget({ options }: WidgetComponentProps<"releas return { ...repository, ...release, - integration: { name: integration.name, iconUrl: getIconUrl(integration.kind) }, + providerMetadata: { name: getReleaseProviderName(provider), iconUrl: getReleaseProviderIconUrl(provider) }, isNewRelease: isReleaseWithin(relativeDateOptions.newReleaseWithin), isStaleRelease: !isReleaseWithin(relativeDateOptions.staleReleaseWithin), viewed: releasesViewedList[repository.id] === release.latestRelease, @@ -139,7 +147,7 @@ export default function ReleasesWidget({ options }: WidgetComponentProps<"releas return formattedResults; }, [ results, - options.repositories, + normalizedRepositories, options.showOnlyHighlighted, options.topReleases, relativeDateOptions.newReleaseWithin, @@ -173,7 +181,7 @@ export default function ReleasesWidget({ options }: WidgetComponentProps<"releas className={combineClasses( "releases-repository", // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - `releases-repository-${repository.integration?.name ?? "error"}-${repository.name || repository.identifier.replace(/[^a-zA-Z0-9]/g, "_")}`, + `releases-repository-${repository.providerMetadata?.name ?? "error"}-${repository.name || repository.identifier.replace(/[^a-zA-Z0-9]/g, "_")}`, classes.releasesRepository, )} gap={0} @@ -187,7 +195,7 @@ export default function ReleasesWidget({ options }: WidgetComponentProps<"releas > {hasError ? ( @@ -529,28 +537,23 @@ const ExpandedDisplay = ({ p={10} > - + {repository.identifier} - {repository.integration && ( + {repository.providerMetadata && ( - - {repository.integration.name} + + {repository.providerMetadata.name} )} @@ -564,7 +567,7 @@ const ExpandedDisplay = ({ {formatter.relativeTime(repository.createdAt, { now, - style: "narrow", + style: "long", })} @@ -614,7 +617,7 @@ const ExpandedDisplay = ({ {repository.error && ( <> - + <Title className="releases-repository-expanded-error-title" order={4} ta="center" c="red"> {t("error.label")} - {repository.error.code ? t(`error.messages.${repository.error.code}` as never) : repository.error.message} + {repository.error.message ?? + (repository.error.code ? t(`error.messages.${repository.error.code}` as never) : null)} )} diff --git a/packages/widgets/src/releases/index.ts b/packages/widgets/src/releases/index.ts index 8de5227ad..2c3d1d3a2 100644 --- a/packages/widgets/src/releases/index.ts +++ b/packages/widgets/src/releases/index.ts @@ -1,6 +1,9 @@ import { IconRocket } from "@tabler/icons-react"; import { z } from "zod/v4"; +import { createId } from "@homarr/common"; +import { releaseProviderKinds } from "@homarr/definitions"; + import { createWidgetDefinition } from "../definition"; import { optionsBuilder } from "../options"; @@ -39,10 +42,20 @@ export const { definition, componentLoader } = createWidgetDefinition("releases" validate: z.number().min(0), }), repositories: factory.multiReleasesRepositories({ - defaultValue: [], + defaultValue: [ + { + id: createId(), + provider: "github" as const, + identifier: "homarr-labs/homarr", + name: "Homarr", + iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/png/homarr.png", + versionFilter: { prefix: "v", precision: 3 }, + }, + ], validate: z.array( z.object({ - providerIntegrationId: z.string().optional(), + id: z.string().default(() => createId()), + provider: z.enum(releaseProviderKinds).optional(), identifier: z.string().min(1), name: z.string().optional(), versionFilter: z @@ -53,6 +66,7 @@ export const { definition, componentLoader } = createWidgetDefinition("releases" }) .optional(), iconUrl: z.string().optional(), + providerUrl: z.string().url().optional(), }), ), }), diff --git a/packages/widgets/src/releases/releases-repository.ts b/packages/widgets/src/releases/releases-repository.ts index 157d9a146..3da40932a 100644 --- a/packages/widgets/src/releases/releases-repository.ts +++ b/packages/widgets/src/releases/releases-repository.ts @@ -1,3 +1,5 @@ +import type { ReleaseProviderKind } from "@homarr/definitions"; + export interface ReleasesVersionFilter { prefix?: string; precision: number; @@ -6,11 +8,12 @@ export interface ReleasesVersionFilter { export interface ReleasesRepository { id: string; - providerIntegrationId?: string; + provider?: ReleaseProviderKind; identifier: string; name?: string; versionFilter?: ReleasesVersionFilter; iconUrl?: string; + providerUrl?: string; } export interface ReleasesRepositoryResponse extends ReleasesRepository { @@ -32,7 +35,7 @@ export interface ReleasesRepositoryResponse extends ReleasesRepository { forksCount?: number; openIssues?: number; - integration?: { + providerMetadata?: { name: string; iconUrl?: string; }; diff --git a/packages/widgets/src/rssFeed/component.tsx b/packages/widgets/src/rssFeed/component.tsx index a2cf07ebb..626f87328 100644 --- a/packages/widgets/src/rssFeed/component.tsx +++ b/packages/widgets/src/rssFeed/component.tsx @@ -12,29 +12,7 @@ import type { WidgetComponentProps } from "../definition"; import classes from "./component.module.scss"; const useLiveFeedEntries = (input: RouterInputs["widget"]["rssFeed"]["getFeeds"]) => { - const { data: feedEntries = [] } = clientApi.widget.rssFeed.getFeeds.useQuery(input, { - staleTime: 5 * 60 * 1000, - refetchOnWindowFocus: false, - refetchOnReconnect: false, - retry: false, - }); - const utils = clientApi.useUtils(); - - clientApi.widget.rssFeed.subscribeFeeds.useSubscription(input, { - onData(updatedData) { - utils.widget.rssFeed.getFeeds.setData(input, (oldData) => { - return oldData - ?.filter((entry) => entry.feedUrl !== updatedData.url) - .concat(updatedData.entries) - .toSorted((entryA, entryB) => { - return entryA.published && entryB.published - ? new Date(entryB.published).getTime() - new Date(entryA.published).getTime() - : 0; - }) - .slice(0, input.maximumAmountPosts); - }); - }, - }); + const { data: feedEntries = [] } = clientApi.widget.rssFeed.getFeeds.useQuery(input); return feedEntries; }; diff --git a/packages/widgets/src/smart-home/entity-state/component.tsx b/packages/widgets/src/smart-home/entity-state/component.tsx index 2ad45b7a3..f1d6b6e18 100644 --- a/packages/widgets/src/smart-home/entity-state/component.tsx +++ b/packages/widgets/src/smart-home/entity-state/component.tsx @@ -23,20 +23,13 @@ export default function SmartHomeEntityStateWidget({ entityId: options.entityId, integrationId, }; - const { data: entityState } = clientApi.widget.smartHome.entityState.useQuery(input, { - staleTime: 60 * 1000, - }); + const { data: entityState } = clientApi.widget.smartHome.entityState.useQuery(input); const utils = clientApi.useUtils(); - - clientApi.widget.smartHome.subscribeEntityState.useSubscription(input, { - onData(data) { - utils.widget.smartHome.entityState.setData(input, data.state); - }, + const { mutate } = clientApi.widget.smartHome.switchEntity.useMutation({ + onSettled: () => void utils.widget.smartHome.entityState.invalidate(input), }); - const { mutate } = clientApi.widget.smartHome.switchEntity.useMutation(); - const attribute = options.entityUnit.length > 0 ? " " + options.entityUnit : ""; const handleClick = useCallback(() => { diff --git a/packages/widgets/src/speedtest-tracker/component.tsx b/packages/widgets/src/speedtest-tracker/component.tsx index dcde145a4..4b2b83dbb 100644 --- a/packages/widgets/src/speedtest-tracker/component.tsx +++ b/packages/widgets/src/speedtest-tracker/component.tsx @@ -14,34 +14,9 @@ import { mergeStats } from "./helpers"; import { LatestResultSection } from "./latest-result"; import { RecentResultsSection } from "./recent-results"; -export default function SpeedtestTrackerWidget({ - options, - integrationIds, - isEditMode, -}: WidgetComponentProps<"speedtestTracker">) { +export default function SpeedtestTrackerWidget({ options, integrationIds }: WidgetComponentProps<"speedtestTracker">) { const t = useScopedI18n("widget.speedtestTracker"); - const { data: dashboardData = [] } = clientApi.widget.speedtestTracker.getDashboard.useQuery( - { integrationIds }, - { staleTime: 10 * 60 * 1000 }, - ); - - const utils = clientApi.useUtils(); - clientApi.widget.speedtestTracker.subscribeToDashboard.useSubscription( - { integrationIds }, - { - enabled: !isEditMode, - onData(newData) { - utils.widget.speedtestTracker.getDashboard.setData({ integrationIds }, (prevData) => { - if (!prevData) return prevData; - return prevData.map((instance) => - instance.integrationId === newData.integrationId - ? { ...instance, dashboard: newData.dashboard, updatedAt: newData.timestamp } - : instance, - ); - }); - }, - }, - ); + const { data: dashboardData = [] } = clientApi.widget.speedtestTracker.getDashboard.useQuery({ integrationIds }); const combined = useMemo( () => diff --git a/packages/widgets/src/stocks/component.tsx b/packages/widgets/src/stocks/component.tsx index 99078b4d2..918851ce8 100644 --- a/packages/widgets/src/stocks/component.tsx +++ b/packages/widgets/src/stocks/component.tsx @@ -25,9 +25,7 @@ function calculateChangePercentage(currentPrice: number, previousClose: number) export default function StockPriceWidget({ options, width, height }: WidgetComponentProps<"stockPrice">) { const t = useScopedI18n("widget.stockPrice"); const theme = useMantineTheme(); - const { data: result } = clientApi.widget.stockPrice.getPriceHistory.useQuery(options, { - staleTime: 5 * 60 * 1000, - }); + const { data: result } = clientApi.widget.stockPrice.getPriceHistory.useQuery(options); if (!result) return ; const { data } = result; diff --git a/packages/widgets/src/system-disks/component.tsx b/packages/widgets/src/system-disks/component.tsx index efe2fe73d..d24a68c37 100644 --- a/packages/widgets/src/system-disks/component.tsx +++ b/packages/widgets/src/system-disks/component.tsx @@ -118,18 +118,7 @@ const SystemDiskCard = ({ export default function SystemResources({ integrationIds, options }: WidgetComponentProps<"systemDisks">) { const queryInput = { integrationIds }; - const { data = [] } = clientApi.widget.healthMonitoring.getSystemHealthStatus.useQuery(queryInput, { - staleTime: 5 * 1000, - }); - const utils = clientApi.useUtils(); - - clientApi.widget.healthMonitoring.subscribeSystemHealthStatus.useSubscription(queryInput, { - onData(data) { - utils.widget.healthMonitoring.getSystemHealthStatus.setData(queryInput, (oldData) => - oldData?.map((item) => (item.integrationId === data.integrationId ? { ...item, ...data } : item)), - ); - }, - }); + const { data = [] } = clientApi.widget.healthMonitoring.getSystemHealthStatus.useQuery(queryInput); const lastItem = data.at(-1); diff --git a/packages/widgets/src/system-resources/component.tsx b/packages/widgets/src/system-resources/component.tsx index d9e5a8604..1d3854c89 100644 --- a/packages/widgets/src/system-resources/component.tsx +++ b/packages/widgets/src/system-resources/component.tsx @@ -1,6 +1,6 @@ "use client"; -import { useState } from "react"; +import { useEffect, useRef, useState } from "react"; import { Box, Group, Stack } from "@mantine/core"; import { useElementSize } from "@mantine/hooks"; @@ -15,56 +15,49 @@ import { NetworkTrafficChart } from "./chart/network-traffic"; const MAX_QUEUE_SIZE = 15; +const toChartItem = (healthInfo: { + cpuUtilization: number; + memUsedInBytes: number; + gpu: { processorUtilization: number }[]; + network: { up: number; down: number } | null; +}) => ({ + cpu: healthInfo.cpuUtilization, + memory: healthInfo.memUsedInBytes, + gpu: + healthInfo.gpu.length > 0 + ? healthInfo.gpu.reduce((acc, g) => acc + g.processorUtilization, 0) / healthInfo.gpu.length + : 0, + network: healthInfo.network, +}); + export default function SystemResources({ integrationIds, options }: WidgetComponentProps<"systemResources">) { const { ref, width } = useElementSize(); - const { data = [] } = clientApi.widget.healthMonitoring.getSystemHealthStatus.useQuery( - { - integrationIds, - }, - { - staleTime: 5 * 1000, - }, - ); + const { data = [], dataUpdatedAt } = clientApi.widget.healthMonitoring.getSystemHealthStatus.useQuery({ + integrationIds, + }); const memoryCapacityInBytes = (data[0]?.healthInfo.memAvailableInBytes ?? 0) + (data[0]?.healthInfo.memUsedInBytes ?? 0); const [items, setItems] = useState< { cpu: number; memory: number; gpu: number; network: { up: number; down: number } | null }[] - >( - data.map((item) => ({ - cpu: item.healthInfo.cpuUtilization, - memory: item.healthInfo.memUsedInBytes, - gpu: - item.healthInfo.gpu.length > 0 - ? item.healthInfo.gpu.reduce((acc, g) => acc + g.processorUtilization, 0) / item.healthInfo.gpu.length - : 0, - network: item.healthInfo.network, - })), - ); + >(() => (data[0] ? [toChartItem(data[0].healthInfo)] : [])); - clientApi.widget.healthMonitoring.subscribeSystemHealthStatus.useSubscription( - { - integrationIds, - }, - { - onData(data) { - setItems((previousItems) => { - const next = { - cpu: data.healthInfo.cpuUtilization, - memory: data.healthInfo.memUsedInBytes, - gpu: - data.healthInfo.gpu.length > 0 - ? data.healthInfo.gpu.reduce((acc, g) => acc + g.processorUtilization, 0) / data.healthInfo.gpu.length - : 0, - network: data.healthInfo.network, - }; + const prevIntegrationIds = useRef(integrationIds); + useEffect(() => { + if (prevIntegrationIds.current === integrationIds) return; + prevIntegrationIds.current = integrationIds; + setItems([]); + }, [integrationIds]); - return [...previousItems, next].slice(-MAX_QUEUE_SIZE); - }); - }, - }, - ); + const lastUpdatedAt = useRef(dataUpdatedAt); + useEffect(() => { + if (dataUpdatedAt === lastUpdatedAt.current) return; + lastUpdatedAt.current = dataUpdatedAt; + const firstItem = data[0]; + if (!firstItem) return; + setItems((prev) => [...prev, toChartItem(firstItem.healthInfo)].slice(-MAX_QUEUE_SIZE)); + }, [dataUpdatedAt, data]); const showNetwork = items.length === 0 || (items.every((item) => item.network !== null) && options.visibleCharts.includes("network")); diff --git a/packages/widgets/src/timetable/component.tsx b/packages/widgets/src/timetable/component.tsx index 539d7e6c7..47751a751 100644 --- a/packages/widgets/src/timetable/component.tsx +++ b/packages/widgets/src/timetable/component.tsx @@ -7,35 +7,27 @@ import { useScopedI18n } from "@homarr/translation/client"; import type { DynamicSelectOption } from "../_inputs/widget-dynamic-select-input"; import type { WidgetComponentProps } from "../definition"; -export default function TimetableWidget({ options, integrationIds }: WidgetComponentProps<"timetable">) { - // It will always have at least one integration as otherwise the NoIntegrationSelectedError would be thrown in item-content.tsx - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - const integrationId = integrationIds[0]!; +export default function TimetableWidget({ options }: WidgetComponentProps<"timetable">) { const t = useScopedI18n("widget.timetable"); if (!options.station) { return
{t("noStation")}
; } - return ; + return ; } interface TimetableWidgetInnerProps { station: DynamicSelectOption; - integrationId: string; + baseUrl: string; } -const TimetableWidgetInner = ({ station, integrationId }: TimetableWidgetInnerProps) => { - const { data: timetable } = clientApi.widget.timetable.getTimetable.useQuery( - { - integrationId, - stationId: station.value, - limit: 10, - }, - { - staleTime: 60 * 1000, - }, - ); +const TimetableWidgetInner = ({ station, baseUrl }: TimetableWidgetInnerProps) => { + const { data: timetable } = clientApi.widget.timetable.getTimetable.useQuery({ + baseUrl, + stationId: station.value, + limit: 10, + }); const t = useScopedI18n("widget.timetable"); return ( diff --git a/packages/widgets/src/timetable/index.ts b/packages/widgets/src/timetable/index.ts index f303b8c62..cee44323c 100644 --- a/packages/widgets/src/timetable/index.ts +++ b/packages/widgets/src/timetable/index.ts @@ -1,4 +1,5 @@ import { IconBusStop } from "@tabler/icons-react"; +import { z } from "zod/v4"; import { clientApi } from "@homarr/api/client"; @@ -7,20 +8,18 @@ import { optionsBuilder } from "../options"; export const { componentLoader, definition } = createWidgetDefinition("timetable", { icon: IconBusStop, - supportedIntegrations: ["searchCh"], createOptions() { return optionsBuilder.from((factory) => ({ + baseUrl: factory.text({ + defaultValue: "https://search.ch", + validate: z.string().url(), + }), station: factory.dynamicSelect({ - useOptions(query, integrationIds) { - const { data: stations, isPending } = clientApi.widget.timetable.searchStations.useQuery( - { - integrationId: integrationIds[0] ?? "", - query, - }, - { - enabled: integrationIds.length > 0, - }, - ); + useOptions(query, _integrationIds, options) { + const { data: stations, isPending } = clientApi.widget.timetable.searchStations.useQuery({ + baseUrl: typeof options.baseUrl === "string" ? options.baseUrl : "https://search.ch", + query, + }); return { isPending, diff --git a/packages/widgets/src/tracearr/component.tsx b/packages/widgets/src/tracearr/component.tsx index 0ff5e9132..f3b70192a 100644 --- a/packages/widgets/src/tracearr/component.tsx +++ b/packages/widgets/src/tracearr/component.tsx @@ -13,50 +13,23 @@ import { StatsBar } from "./stats-section"; import { StreamsList } from "./streams-section"; import { ViolationsList } from "./violations-section"; -export default function TracearrWidget({ - options, - integrationIds, - width, - isEditMode, -}: WidgetComponentProps<"tracearr">) { +export default function TracearrWidget({ options, integrationIds, width }: WidgetComponentProps<"tracearr">) { if (integrationIds.length === 0) { throw new NoIntegrationDataError(); } - return ; + return ; } interface TracearrContentProps { integrationIds: string[]; options: WidgetComponentProps<"tracearr">["options"]; width: number; - isEditMode: boolean; } -function TracearrContent({ integrationIds, options, width, isEditMode }: TracearrContentProps) { +function TracearrContent({ integrationIds, options, width }: TracearrContentProps) { const t = useScopedI18n("widget.tracearr"); - const { data: dashboardData = [] } = clientApi.widget.tracearr.getDashboard.useQuery( - { integrationIds }, - { staleTime: 5 * 1000 }, - ); - - const utils = clientApi.useUtils(); - clientApi.widget.tracearr.subscribeToDashboard.useSubscription( - { integrationIds }, - { - enabled: !isEditMode, - onData(newData) { - utils.widget.tracearr.getDashboard.setData({ integrationIds }, (prevData) => { - if (!prevData) return prevData; - return prevData.map((instance) => - instance.integrationId === newData.integrationId - ? { ...instance, dashboard: newData.dashboard, updatedAt: newData.timestamp } - : instance, - ); - }); - }, - }, - ); + const { data: dashboardData = [] } = clientApi.widget.tracearr.getDashboard.useQuery({ integrationIds }); // Merge data from all integrations const combined = dashboardData.reduce( diff --git a/packages/widgets/src/umami/umami-content.tsx b/packages/widgets/src/umami/umami-content.tsx index 2f8dea22c..2a187c3d5 100644 --- a/packages/widgets/src/umami/umami-content.tsx +++ b/packages/widgets/src/umami/umami-content.tsx @@ -10,7 +10,7 @@ import { useScopedI18n } from "@homarr/translation/client"; import { UmamiEventsContent } from "./umami-events-content"; import { UmamiTopPagesContent, UmamiTopReferrersContent } from "./umami-top-list"; -import { formatTimeFrameLabel, formatXLabel } from "./umami-utils"; +import { formatTimeFrameLabel, formatXLabel, umamiQueryOptions } from "./umami-utils"; import type { TimeFrame } from "./umami-utils"; interface UmamiContentProps { @@ -41,29 +41,19 @@ export function UmamiContent({ const tickColor = colorScheme === "dark" ? "#c1c2c5" : "#495057"; const { data: results = [] } = clientApi.widget.umami.getVisitorStats.useQuery( - { - integrationIds, - websiteId, - timeFrame, - eventName, - }, - { staleTime: 5 * 60 * 1000 }, + { integrationIds, websiteId, timeFrame, eventName }, + umamiQueryOptions, ); const activeVisitorsInput = { integrationId: integrationIds[0] ?? "", websiteId }; - const { data: activeVisitors = 0 } = clientApi.widget.umami.getActiveVisitors.useQuery(activeVisitorsInput, { - staleTime: 30 * 1000, - }); - const utils = clientApi.useUtils(); - clientApi.widget.umami.subscribeActiveVisitors.useSubscription(activeVisitorsInput, { - onData(count) { - utils.widget.umami.getActiveVisitors.setData(activeVisitorsInput, () => count); - }, - }); + const { data: activeVisitors = 0 } = clientApi.widget.umami.getActiveVisitors.useQuery( + activeVisitorsInput, + umamiQueryOptions, + ); const { data: multiEventSeries } = clientApi.widget.umami.getMultiEventTimeSeries.useQuery( { integrationId: integrationIds[0] ?? "", websiteId, timeFrame, eventNames: [...eventNames].toSorted() }, - { enabled: viewMode === "events" && eventNames.length > 0, staleTime: 5 * 60 * 1000 }, + { enabled: viewMode === "events" && eventNames.length > 0, ...umamiQueryOptions }, ); const multiEventTotal = multiEventSeries diff --git a/packages/widgets/src/umami/umami-events-content.tsx b/packages/widgets/src/umami/umami-events-content.tsx index 0351e906c..ef91df422 100644 --- a/packages/widgets/src/umami/umami-events-content.tsx +++ b/packages/widgets/src/umami/umami-events-content.tsx @@ -7,7 +7,7 @@ import { clientApi } from "@homarr/api/client"; import type { UmamiEventSeries } from "@homarr/integrations/types"; import { useScopedI18n } from "@homarr/translation/client"; -import { EVENT_COLORS, formatXLabel } from "./umami-utils"; +import { EVENT_COLORS, formatXLabel, umamiQueryOptions } from "./umami-utils"; interface UmamiEventsContentProps { integrationIds: string[]; @@ -29,13 +29,8 @@ export function UmamiEventsContent({ const tickColor = colorScheme === "dark" ? "#c1c2c5" : "#495057"; const { data: series = [] } = clientApi.widget.umami.getMultiEventTimeSeries.useQuery( - { - integrationId: integrationIds[0] ?? "", - websiteId, - timeFrame, - eventNames: [...eventNames].toSorted(), - }, - { staleTime: 5 * 60 * 1000 }, + { integrationId: integrationIds[0] ?? "", websiteId, timeFrame, eventNames: [...eventNames].toSorted() }, + umamiQueryOptions, ); if (eventNames.length === 0) { diff --git a/packages/widgets/src/umami/umami-top-list.tsx b/packages/widgets/src/umami/umami-top-list.tsx index 125115f0c..3ce4b8730 100644 --- a/packages/widgets/src/umami/umami-top-list.tsx +++ b/packages/widgets/src/umami/umami-top-list.tsx @@ -6,6 +6,8 @@ import { clientApi } from "@homarr/api/client"; import type { UmamiMetricItem } from "@homarr/integrations/types"; import { useScopedI18n } from "@homarr/translation/client"; +import { umamiQueryOptions } from "./umami-utils"; + interface UmamiTopListProps { integrationIds: string[]; websiteId: string; @@ -16,13 +18,8 @@ interface UmamiTopListProps { export function UmamiTopPagesContent({ integrationIds, websiteId, timeFrame, limit }: UmamiTopListProps) { const t = useScopedI18n("widget.umami"); const { data = [] } = clientApi.widget.umami.getTopPages.useQuery( - { - integrationId: integrationIds[0] ?? "", - websiteId, - timeFrame, - limit, - }, - { staleTime: 5 * 60 * 1000 }, + { integrationId: integrationIds[0] ?? "", websiteId, timeFrame, limit }, + umamiQueryOptions, ); return ; } @@ -30,13 +27,8 @@ export function UmamiTopPagesContent({ integrationIds, websiteId, timeFrame, lim export function UmamiTopReferrersContent({ integrationIds, websiteId, timeFrame, limit }: UmamiTopListProps) { const t = useScopedI18n("widget.umami"); const { data = [] } = clientApi.widget.umami.getTopReferrers.useQuery( - { - integrationId: integrationIds[0] ?? "", - websiteId, - timeFrame, - limit, - }, - { staleTime: 5 * 60 * 1000 }, + { integrationId: integrationIds[0] ?? "", websiteId, timeFrame, limit }, + umamiQueryOptions, ); return ( = { type UpsLayout = "mini" | "compact" | "full"; -export default function UpsWidget({ options, integrationIds, isEditMode, width }: WidgetComponentProps<"ups">) { +export default function UpsWidget({ options, integrationIds, width }: WidgetComponentProps<"ups">) { if (integrationIds.length === 0) { throw new NoIntegrationSelectedError(); } - return ; + return ; } interface UpsContentProps { integrationIds: string[]; options: WidgetComponentProps<"ups">["options"]; - isEditMode: boolean; width: number; } -function UpsContent({ integrationIds, options, isEditMode, width }: UpsContentProps) { +function UpsContent({ integrationIds, options, width }: UpsContentProps) { const t = useScopedI18n("widget.ups"); - const { data } = clientApi.widget.ups.getSummaries.useQuery({ integrationIds }, { staleTime: 30 * 1000 }); - - const utils = clientApi.useUtils(); - clientApi.widget.ups.subscribeSummaries.useSubscription( - { integrationIds }, - { - enabled: !isEditMode, - onData(newData) { - utils.widget.ups.getSummaries.setData({ integrationIds }, (prevData) => { - if (!prevData) return prevData; - return prevData.map((instance) => - instance.integrationId === newData.integrationId - ? { ...instance, summaries: newData.summaries, updatedAt: newData.timestamp } - : instance, - ); - }); - }, - }, - ); + const { data } = clientApi.widget.ups.getSummaries.useQuery({ integrationIds }); if (!data) return ; diff --git a/packages/widgets/src/uptime-kuma/component.tsx b/packages/widgets/src/uptime-kuma/component.tsx index 2a3b6136b..5719f2f13 100644 --- a/packages/widgets/src/uptime-kuma/component.tsx +++ b/packages/widgets/src/uptime-kuma/component.tsx @@ -85,51 +85,23 @@ const heroVariantByRing = { false: classes.heroTextOnly, } as const; -export default function UptimeKumaWidget({ - integrationIds, - isEditMode, - options, - width, -}: WidgetComponentProps<"uptimeKuma">) { +export default function UptimeKumaWidget({ integrationIds, options, width }: WidgetComponentProps<"uptimeKuma">) { if (integrationIds.length === 0) { throw new NoIntegrationDataError(); } - return ; + return ; } interface UptimeKumaContentProps { integrationIds: string[]; - isEditMode: boolean; options: WidgetComponentProps<"uptimeKuma">["options"]; width: number; } -function UptimeKumaContent({ integrationIds, isEditMode, options, width }: UptimeKumaContentProps) { +function UptimeKumaContent({ integrationIds, options, width }: UptimeKumaContentProps) { const t = useScopedI18n("widget.uptimeKuma"); - const { data: dashboardData } = clientApi.widget.uptimeKuma.getDashboard.useQuery( - { integrationIds }, - { staleTime: 30 * 1000 }, - ); - - const utils = clientApi.useUtils(); - clientApi.widget.uptimeKuma.subscribeToDashboard.useSubscription( - { integrationIds }, - { - enabled: !isEditMode, - onData(newData) { - utils.widget.uptimeKuma.getDashboard.setData({ integrationIds }, (prevData) => { - if (!prevData) return prevData; - return prevData.map((instance) => { - if (instance.integrationId === newData.integrationId) { - return { ...instance, dashboard: newData.dashboard, updatedAt: newData.timestamp }; - } - return instance; - }); - }); - }, - }, - ); + const { data: dashboardData } = clientApi.widget.uptimeKuma.getDashboard.useQuery({ integrationIds }); if (!dashboardData) return ; diff --git a/packages/widgets/src/vpn/component.tsx b/packages/widgets/src/vpn/component.tsx index 85ee4e3cf..79ed5d695 100644 --- a/packages/widgets/src/vpn/component.tsx +++ b/packages/widgets/src/vpn/component.tsx @@ -1,53 +1,20 @@ "use client"; -import React from "react"; import { Flex, ScrollArea, Stack } from "@mantine/core"; import { clientApi } from "@homarr/api/client"; import type { WidgetComponentProps } from "../definition"; -import { updateVpnInfoFromSubscription } from "./helpers"; import { VpnIntegrationCard } from "./vpn-card"; export default function VpnWidget({ options, integrationIds }: WidgetComponentProps<"vpn">) { - const [integrations] = clientApi.widget.vpn.getSummaries.useSuspenseQuery( - { - ...options, - integrationIds, - }, - { - // Initial load only. Live updates arrive via the subscription below, which is - // fed by the VPN cron job publishing to the integration channel. - refetchOnMount: false, - refetchOnWindowFocus: false, - refetchOnReconnect: false, - retry: false, - }, - ); - const utils = clientApi.useUtils(); - - clientApi.widget.vpn.subscribeSummaries.useSubscription( - { - integrationIds, - }, - { - onData: (data) => { - utils.widget.vpn.getSummaries.setData( - { - ...options, - integrationIds, - }, - (prevData) => updateVpnInfoFromSubscription(prevData, data), - ); - }, - }, - ); - - if (integrations.length === 1) { - // It will always have at least one integration as otherwise the NoIntegrationSelectedError would be thrown in item-content.tsx - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - const vpn = integrations[0]!; + const { data: integrations = [] } = clientApi.widget.vpn.getSummaries.useQuery({ + ...options, + integrationIds, + }); + const [vpn] = integrations; + if (integrations.length === 1 && vpn) { return ( diff --git a/packages/widgets/src/vpn/helpers.ts b/packages/widgets/src/vpn/helpers.ts index 24a81720b..9bc801429 100644 --- a/packages/widgets/src/vpn/helpers.ts +++ b/packages/widgets/src/vpn/helpers.ts @@ -1,22 +1,5 @@ -import type { RouterOutputs } from "@homarr/api"; - export const RUNNING_STATUS = "running"; -type VpnInfoItem = RouterOutputs["widget"]["vpn"]["getSummaries"][number]; - export function getStatusColor(status: string) { return status === RUNNING_STATUS ? "green" : "red"; } - -export function updateVpnInfoFromSubscription( - prevData: VpnInfoItem[] | undefined, - data: { integration: { id: string }; summary: VpnInfoItem["summary"] }, -): VpnInfoItem[] | undefined { - if (!prevData) { - return undefined; - } - - return prevData.map((item) => - item.integration.id === data.integration.id ? { ...item, summary: data.summary } : item, - ); -} diff --git a/packages/widgets/src/weather/component.tsx b/packages/widgets/src/weather/component.tsx index a2e9778ca..6e76ef8f2 100644 --- a/packages/widgets/src/weather/component.tsx +++ b/packages/widgets/src/weather/component.tsx @@ -19,16 +19,7 @@ export default function WeatherWidget({ isEditMode, options }: WidgetComponentPr latitude: options.location.latitude, longitude: options.location.longitude, }; - const { data: weather } = clientApi.widget.weather.atLocation.useQuery(input, { - staleTime: 60 * 1000, - refetchOnWindowFocus: false, - refetchOnReconnect: false, - }); - - const utils = clientApi.useUtils(); - clientApi.widget.weather.subscribeAtLocation.useSubscription(input, { - onData: (data) => utils.widget.weather.atLocation.setData(input, data), - }); + const { data: weather } = clientApi.widget.weather.atLocation.useQuery(input); if (!weather) return ; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 682dce7d5..5b0c57a37 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -40,11 +40,8 @@ catalogs: specifier: 7.2.1 version: 7.2.1 '@gfazioli/mantine-onboarding-tour': - specifier: ^4.0.5 - version: 4.0.5 - '@gitbeaker/rest': - specifier: ^43.8.0 - version: 43.8.0 + specifier: ^4.0.6 + version: 4.0.6 '@homarr/gridstack': specifier: ^1.12.0 version: 1.12.0 @@ -52,8 +49,8 @@ catalogs: specifier: ^2.6.0 version: 2.6.0 '@immich/sdk': - specifier: ^2.7.5 - version: 2.7.5 + specifier: ^3.0.0 + version: 3.0.0 '@jellyfin/sdk': specifier: ^0.13.0 version: 0.13.0 @@ -61,53 +58,50 @@ catalogs: specifier: ^1.4.0 version: 1.4.0 '@mantine/charts': - specifier: ^9.3.2 - version: 9.3.2 + specifier: ^9.4.0 + version: 9.4.1 '@mantine/colors-generator': - specifier: ^9.3.2 - version: 9.3.2 + specifier: ^9.4.0 + version: 9.4.1 '@mantine/core': - specifier: ^9.3.2 - version: 9.3.2 + specifier: ^9.4.0 + version: 9.4.1 '@mantine/dates': - specifier: ^9.3.2 - version: 9.3.2 + specifier: ^9.4.0 + version: 9.4.1 '@mantine/dropzone': - specifier: ^9.3.2 - version: 9.3.2 + specifier: ^9.4.0 + version: 9.4.1 '@mantine/form': - specifier: ^9.3.2 - version: 9.3.2 + specifier: ^9.4.0 + version: 9.4.1 '@mantine/hooks': - specifier: ^9.3.2 - version: 9.3.2 + specifier: ^9.4.0 + version: 9.4.1 '@mantine/modals': - specifier: ^9.3.2 - version: 9.3.2 + specifier: ^9.4.0 + version: 9.4.1 '@mantine/notifications': - specifier: ^9.3.2 - version: 9.3.2 + specifier: ^9.4.0 + version: 9.4.1 '@mantine/spotlight': - specifier: ^9.3.2 - version: 9.3.2 + specifier: ^9.4.0 + version: 9.4.1 '@mantine/tiptap': - specifier: ^9.3.2 - version: 9.3.2 + specifier: ^9.4.0 + version: 9.4.1 '@modelcontextprotocol/sdk': specifier: 1.29.0 version: 1.29.0 - '@octokit/auth-app': - specifier: ^8.2.0 - version: 8.2.0 '@paralleldrive/cuid2': specifier: ^3.3.0 version: 3.3.0 '@playwright/test': - specifier: ^1.61.0 - version: 1.61.0 + specifier: ^1.61.1 + version: 1.61.1 '@scalar/api-reference-react': - specifier: ^0.9.47 - version: 0.9.47 + specifier: ^0.9.49 + version: 0.9.49 '@semantic-release/changelog': specifier: ^6.0.3 version: 6.0.3 @@ -132,7 +126,7 @@ catalogs: '@tabler/icons-react': specifier: ^3.44.0 version: 3.44.0 - '@tanstack/query-persist-client-core': + '@tanstack/query-async-storage-persister': specifier: ^5.101.0 version: 5.101.0 '@tanstack/react-query': @@ -144,6 +138,9 @@ catalogs: '@tanstack/react-query-next-experimental': specifier: ^5.101.0 version: 5.101.0 + '@tanstack/react-query-persist-client': + specifier: ^5.101.0 + version: 5.101.0 '@testcontainers/mysql': specifier: ^12.0.3 version: 12.0.3 @@ -217,8 +214,8 @@ catalogs: specifier: ^11.17.0 version: 11.17.0 '@turbo/gen': - specifier: ^2.9.18 - version: 2.9.18 + specifier: ^2.10.0 + version: 2.10.0 '@types/adm-zip': specifier: 0.5.8 version: 0.5.8 @@ -271,8 +268,8 @@ catalogs: specifier: ^0.4.14 version: 0.4.14 '@vitejs/plugin-react': - specifier: ^6.0.2 - version: 6.0.2 + specifier: ^6.0.3 + version: 6.0.3 '@vitest/coverage-v8': specifier: ^4.1.9 version: 4.1.9 @@ -337,8 +334,8 @@ catalogs: specifier: ^0.2.9 version: 0.2.9 dockerode: - specifier: ^5.0.0 - version: 5.0.0 + specifier: ^5.0.1 + version: 5.0.1 dotenv: specifier: ^17.4.2 version: 17.4.2 @@ -433,11 +430,11 @@ catalogs: specifier: ^5.0.5 version: 5.0.5 oxfmt: - specifier: ^0.55.0 - version: 0.55.0 + specifier: ^0.56.0 + version: 0.56.0 oxlint: - specifier: ^1.70.0 - version: 1.70.0 + specifier: ^1.71.0 + version: 1.71.0 pg: specifier: ^8.22.0 version: 8.22.0 @@ -445,8 +442,8 @@ catalogs: specifier: ^1.18.0 version: 1.18.0 posthog-js: - specifier: ^1.391.2 - version: 1.391.2 + specifier: ^1.395.0 + version: 1.395.0 posthog-node: specifier: ^4.18.0 version: 4.18.0 @@ -478,8 +475,8 @@ catalogs: specifier: ^0.14.1 version: 0.14.1 recharts: - specifier: ^3.8.1 - version: 3.8.1 + specifier: ^3.9.0 + version: 3.9.0 sass: specifier: ^1.101.0 version: 1.101.0 @@ -517,8 +514,8 @@ catalogs: specifier: 4.20.4 version: 4.20.4 turbo: - specifier: ^2.9.18 - version: 2.9.18 + specifier: ^2.10.0 + version: 2.10.0 typescript: specifier: ^6.0.3 version: 6.0.3 @@ -532,8 +529,8 @@ catalogs: specifier: ^8.23.9 version: 8.23.9 vite: - specifier: ^8.0.16 - version: 8.0.16 + specifier: ^8.1.0 + version: 8.1.0 vite-tsconfig-paths: specifier: ^6.1.1 version: 6.1.1 @@ -565,23 +562,23 @@ catalogs: overrides: '@babel/helpers@<7.26.10': '>=7.29.7' '@babel/runtime@<7.26.10': '>=7.29.7' - axios@>=1.0.0 <1.8.2: '>=1.18.0' + axios@>=1.0.0 <1.8.2: '>=1.18.1' brace-expansion@>=2.0.0 <=2.0.1: '>=5.0.6' brace-expansion@>=1.0.0 <=1.1.11: '>=5.0.6' esbuild@<=0.24.2: '>=0.28.0' form-data@>=4.0.0 <4.0.4: '>=4.0.6' - hono@<4.6.5: '>=4.12.26' + hono@<4.6.5: '>=4.12.27' linkifyjs@<4.3.2: '>=4.3.3' - nanoid@>=4.0.0 <5.0.9: '>=5.1.15' + nanoid@>=4.0.0 <5.0.9: '>=5.1.16' prismjs@<1.30.0: '>=1.30.0' proxmox-api>undici: 7.27.1 react-is: ^19.2.7 rollup@>=4.0.0 <4.22.4: '>=4.62.2' sha.js@<=2.4.11: '>=2.4.12' - tar-fs@>=3.0.0 <3.0.9: '>=3.1.2' - tar-fs@>=2.0.0 <2.1.3: '>=3.1.2' + tar-fs@>=3.0.0 <3.0.9: '>=3.1.3' + tar-fs@>=2.0.0 <2.1.3: '>=3.1.3' tmp@<=0.2.3: '>=0.2.7' - vite@>=5.0.0 <=5.4.18: '>=8.0.16' + vite@>=5.0.0 <=5.4.18: '>=8.1.0' patchedDependencies: '@types/node-unifi': 5e6ae51e2a17a7f9729bfa30b0eb3d0842a5810ac6db47603ab4a6efa1ed84c5 @@ -597,7 +594,7 @@ importers: devDependencies: '@playwright/test': specifier: 'catalog:' - version: 1.61.0 + version: 1.61.1 '@semantic-release/changelog': specifier: 'catalog:' version: 6.0.3(semantic-release@25.0.5(typescript@6.0.3)) @@ -621,10 +618,10 @@ importers: version: 12.0.3 '@turbo/gen': specifier: 'catalog:' - version: 2.9.18(@types/node@24.13.2) + version: 2.10.0(@types/node@24.13.2) '@vitejs/plugin-react': specifier: 'catalog:' - version: 6.0.2(babel-plugin-react-compiler@1.0.0)(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.101.0)(sugarss@5.0.0(postcss@8.5.15))(terser@5.46.1)(tsx@4.21.0)(yaml@2.9.0)) + version: 6.0.3(babel-plugin-react-compiler@1.0.0)(vite@8.1.0(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(sugarss@5.0.0(postcss@8.5.16))(terser@5.46.1)(tsx@4.21.0)(yaml@2.9.0)) '@vitest/coverage-v8': specifier: 'catalog:' version: 4.1.9(vitest@4.1.9) @@ -645,10 +642,10 @@ importers: version: 2.2.3 oxfmt: specifier: 'catalog:' - version: 0.55.0 + version: 0.56.0 oxlint: specifier: 'catalog:' - version: 1.70.0 + version: 1.71.0 semantic-release: specifier: 'catalog:' version: 25.0.5(typescript@6.0.3) @@ -657,43 +654,43 @@ importers: version: 12.0.3 turbo: specifier: 'catalog:' - version: 2.9.18 + version: 2.10.0 typescript: specifier: 'catalog:' version: 6.0.3 vite: specifier: 'catalog:' - version: 8.0.16(@types/node@24.13.2)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.101.0)(sugarss@5.0.0(postcss@8.5.15))(terser@5.46.1)(tsx@4.21.0)(yaml@2.9.0) + version: 8.1.0(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(sugarss@5.0.0(postcss@8.5.16))(terser@5.46.1)(tsx@4.21.0)(yaml@2.9.0) vite-tsconfig-paths: specifier: 'catalog:' - version: 6.1.1(typescript@6.0.3)(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.101.0)(sugarss@5.0.0(postcss@8.5.15))(terser@5.46.1)(tsx@4.21.0)(yaml@2.9.0)) + version: 6.1.1(typescript@6.0.3)(vite@8.1.0(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(sugarss@5.0.0(postcss@8.5.16))(terser@5.46.1)(tsx@4.21.0)(yaml@2.9.0)) vitest: specifier: 'catalog:' - version: 4.1.9(@opentelemetry/api@1.9.0)(@types/node@24.13.2)(@vitest/coverage-v8@4.1.9)(@vitest/ui@4.1.9)(jsdom@29.1.1(@noble/hashes@2.0.1))(msw@2.14.6(@types/node@24.13.2)(typescript@6.0.3))(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.101.0)(sugarss@5.0.0(postcss@8.5.15))(terser@5.46.1)(tsx@4.21.0)(yaml@2.9.0)) + version: 4.1.9(@opentelemetry/api@1.9.0)(@types/node@24.13.2)(@vitest/coverage-v8@4.1.9)(@vitest/ui@4.1.9)(jsdom@29.1.1(@noble/hashes@2.0.1))(msw@2.14.6(@types/node@24.13.2)(typescript@6.0.3))(vite@8.1.0(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(sugarss@5.0.0(postcss@8.5.16))(terser@5.46.1)(tsx@4.21.0)(yaml@2.9.0)) apps/docs: dependencies: '@docusaurus/core': specifier: ^3.10.1 - version: 3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.0)(postcss@8.5.15))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3) + version: 3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.1)(postcss@8.5.16))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3) '@docusaurus/faster': specifier: ^3.10.1 - version: 3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.0)(postcss@8.5.15) + version: 3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.1)(postcss@8.5.16) '@docusaurus/plugin-content-docs': specifier: ^3.10.1 - version: 3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.0)(postcss@8.5.15))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3) + version: 3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.1)(postcss@8.5.16))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3) '@docusaurus/plugin-sitemap': specifier: ^3.10.1 - version: 3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.0)(postcss@8.5.15))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3) + version: 3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.1)(postcss@8.5.16))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3) '@docusaurus/preset-classic': specifier: ^3.10.1 - version: 3.10.1(@algolia/client-search@5.53.0)(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.0)(postcss@8.5.15))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(@types/react@19.2.17)(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(search-insights@2.17.3)(typescript@6.0.3) + version: 3.10.1(@algolia/client-search@5.53.0)(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.1)(postcss@8.5.16))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(@types/react@19.2.17)(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(search-insights@2.17.3)(typescript@6.0.3) '@docusaurus/theme-common': specifier: ^3.10.1 - version: 3.10.1(@docusaurus/plugin-content-docs@3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.0)(postcss@8.5.15))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + version: 3.10.1(@docusaurus/plugin-content-docs@3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.1)(postcss@8.5.16))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@docusaurus/theme-mermaid': specifier: ^3.10.1 - version: 3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.0)(postcss@8.5.15))(@docusaurus/plugin-content-docs@3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.0)(postcss@8.5.15))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3) + version: 3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.1)(postcss@8.5.16))(@docusaurus/plugin-content-docs@3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.1)(postcss@8.5.16))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3) '@homarr/definitions': specifier: workspace:^ version: link:../../packages/definitions @@ -705,10 +702,10 @@ importers: version: 0.99.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@scalar/api-reference-react': specifier: 'catalog:' - version: 0.9.47(nprogress@0.2.0)(react@19.2.7)(tailwindcss@4.3.1)(typescript@6.0.3)(zod@4.4.3) + version: 0.9.49(nprogress@0.2.0)(react@19.2.7)(tailwindcss@4.3.1)(typescript@6.0.3)(zod@4.4.3) '@signalwire/docusaurus-plugin-llms-txt': specifier: ^1.2.2 - version: 1.2.2(@docusaurus/core@3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.0)(postcss@8.5.15))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3)) + version: 1.2.2(@docusaurus/core@3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.1)(postcss@8.5.16))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3)) '@tabler/icons-react': specifier: 'catalog:' version: 3.44.0(react@19.2.7) @@ -723,13 +720,13 @@ importers: version: 1.11.21 docusaurus-plugin-image-zoom: specifier: ^3.0.1 - version: 3.0.1(@docusaurus/theme-classic@3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.0)(postcss@8.5.15))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(@types/react@19.2.17)(esbuild@0.28.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3)) + version: 3.0.1(@docusaurus/theme-classic@3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.1)(postcss@8.5.16))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(@types/react@19.2.17)(esbuild@0.28.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3)) leader-line-new: specifier: ^1.1.9 version: 1.1.9 postcss: - specifier: ^8.5.15 - version: 8.5.15 + specifier: ^8.5.16 + version: 8.5.16 posthog-docusaurus: specifier: ^2.0.5 version: 2.0.5 @@ -754,13 +751,13 @@ importers: devDependencies: '@docusaurus/module-type-aliases': specifier: ^3.10.1 - version: 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + version: 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@docusaurus/tsconfig': specifier: ^3.10.1 version: 3.10.1 '@docusaurus/types': specifier: ^3.10.1 - version: 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.15))(esbuild@0.28.0)(html-minifier-terser@7.2.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + version: 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.16))(esbuild@0.28.1)(html-minifier-terser@7.2.0)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@fec/remark-a11y-emoji': specifier: ^4.0.2 version: 4.0.2 @@ -799,7 +796,7 @@ importers: version: 3.2.2(react@19.2.7) '@gfazioli/mantine-onboarding-tour': specifier: 'catalog:' - version: 4.0.5(@mantine/core@9.3.2(@mantine/hooks@9.3.2(react@19.2.7))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@mantine/hooks@9.3.2(react@19.2.7))(@tabler/icons-react@3.44.0(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + version: 4.0.6(@mantine/core@9.4.1(@mantine/hooks@9.4.1(react@19.2.7))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@mantine/hooks@9.4.1(react@19.2.7))(@tabler/icons-react@3.44.0(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@homarr/analytics': specifier: workspace:^0.1.0 version: link:../../packages/analytics @@ -895,32 +892,32 @@ importers: version: link:../../packages/widgets '@mantine/colors-generator': specifier: 'catalog:' - version: 9.3.2(chroma-js@3.2.0) + version: 9.4.1(chroma-js@3.2.0) '@mantine/core': specifier: 'catalog:' - version: 9.3.2(@mantine/hooks@9.3.2(react@19.2.7))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + version: 9.4.1(@mantine/hooks@9.4.1(react@19.2.7))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@mantine/dropzone': specifier: 'catalog:' - version: 9.3.2(@mantine/core@9.3.2(@mantine/hooks@9.3.2(react@19.2.7))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@mantine/hooks@9.3.2(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + version: 9.4.1(@mantine/core@9.4.1(@mantine/hooks@9.4.1(react@19.2.7))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@mantine/hooks@9.4.1(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@mantine/hooks': specifier: 'catalog:' - version: 9.3.2(react@19.2.7) + version: 9.4.1(react@19.2.7) '@mantine/modals': specifier: 'catalog:' - version: 9.3.2(@mantine/core@9.3.2(@mantine/hooks@9.3.2(react@19.2.7))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@mantine/hooks@9.3.2(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + version: 9.4.1(@mantine/core@9.4.1(@mantine/hooks@9.4.1(react@19.2.7))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@mantine/hooks@9.4.1(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@mantine/tiptap': specifier: 'catalog:' - version: 9.3.2(@mantine/core@9.3.2(@mantine/hooks@9.3.2(react@19.2.7))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@mantine/hooks@9.3.2(react@19.2.7))(@tiptap/extension-link@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1))(@tiptap/react@3.27.1(@floating-ui/dom@1.7.6)(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1)(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + version: 9.4.1(@mantine/core@9.4.1(@mantine/hooks@9.4.1(react@19.2.7))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@mantine/hooks@9.4.1(react@19.2.7))(@tiptap/extension-link@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1))(@tiptap/react@3.27.1(@floating-ui/dom@1.7.6)(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1)(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@modelcontextprotocol/sdk': specifier: 'catalog:' version: 1.29.0(zod@4.4.3) '@scalar/api-reference-react': specifier: 'catalog:' - version: 0.9.47(nprogress@0.2.0)(react@19.2.7)(tailwindcss@4.3.1)(typescript@6.0.3)(zod@4.4.3) + version: 0.9.49(nprogress@0.2.0)(react@19.2.7)(tailwindcss@4.3.1)(typescript@6.0.3)(zod@4.4.3) '@tabler/icons-react': specifier: 'catalog:' version: 3.44.0(react@19.2.7) - '@tanstack/query-persist-client-core': + '@tanstack/query-async-storage-persister': specifier: 'catalog:' version: 5.101.0 '@tanstack/react-query': @@ -931,13 +928,16 @@ importers: version: 5.101.0(@tanstack/react-query@5.101.0(react@19.2.7))(react@19.2.7) '@tanstack/react-query-next-experimental': specifier: 'catalog:' - version: 5.101.0(@tanstack/react-query@5.101.0(react@19.2.7))(next@16.2.9(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.61.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(sass@1.101.0))(react@19.2.7) + version: 5.101.0(@tanstack/react-query@5.101.0(react@19.2.7))(next@16.2.9(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.61.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(sass@1.101.0))(react@19.2.7) + '@tanstack/react-query-persist-client': + specifier: 'catalog:' + version: 5.101.0(@tanstack/react-query@5.101.0(react@19.2.7))(react@19.2.7) '@trpc/client': specifier: 'catalog:' version: 11.17.0(@trpc/server@11.17.0(typescript@6.0.3))(typescript@6.0.3) '@trpc/next': specifier: 'catalog:' - version: 11.17.0(@tanstack/react-query@5.101.0(react@19.2.7))(@trpc/client@11.17.0(@trpc/server@11.17.0(typescript@6.0.3))(typescript@6.0.3))(@trpc/react-query@11.17.0(@tanstack/react-query@5.101.0(react@19.2.7))(@trpc/client@11.17.0(@trpc/server@11.17.0(typescript@6.0.3))(typescript@6.0.3))(@trpc/server@11.17.0(typescript@6.0.3))(react@19.2.7)(typescript@6.0.3))(@trpc/server@11.17.0(typescript@6.0.3))(next@16.2.9(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.61.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(sass@1.101.0))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3) + version: 11.17.0(@tanstack/react-query@5.101.0(react@19.2.7))(@trpc/client@11.17.0(@trpc/server@11.17.0(typescript@6.0.3))(typescript@6.0.3))(@trpc/react-query@11.17.0(@tanstack/react-query@5.101.0(react@19.2.7))(@trpc/client@11.17.0(@trpc/server@11.17.0(typescript@6.0.3))(typescript@6.0.3))(@trpc/server@11.17.0(typescript@6.0.3))(react@19.2.7)(typescript@6.0.3))(@trpc/server@11.17.0(typescript@6.0.3))(next@16.2.9(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.61.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(sass@1.101.0))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3) '@trpc/react-query': specifier: 'catalog:' version: 11.17.0(@tanstack/react-query@5.101.0(react@19.2.7))(@trpc/client@11.17.0(@trpc/server@11.17.0(typescript@6.0.3))(typescript@6.0.3))(@trpc/server@11.17.0(typescript@6.0.3))(react@19.2.7)(typescript@6.0.3) @@ -997,22 +997,22 @@ importers: version: 1.21.0(react@19.2.7) mantine-datatable: specifier: 'catalog:' - version: 9.3.1(@mantine/core@9.3.2(@mantine/hooks@9.3.2(react@19.2.7))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@mantine/hooks@9.3.2(react@19.2.7))(clsx@2.1.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + version: 9.3.1(@mantine/core@9.4.1(@mantine/hooks@9.4.1(react@19.2.7))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@mantine/hooks@9.4.1(react@19.2.7))(clsx@2.1.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) mantine-react-table: specifier: 'catalog:' - version: mantine-react-table-open@9.0.3(@mantine/core@9.3.2(@mantine/hooks@9.3.2(react@19.2.7))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@mantine/dates@9.3.2(@mantine/core@9.3.2(@mantine/hooks@9.3.2(react@19.2.7))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@mantine/hooks@9.3.2(react@19.2.7))(dayjs@1.11.21)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@mantine/hooks@9.3.2(react@19.2.7))(@tabler/icons-react@3.44.0(react@19.2.7))(clsx@2.1.1)(dayjs@1.11.21)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + version: mantine-react-table-open@9.0.3(@mantine/core@9.4.1(@mantine/hooks@9.4.1(react@19.2.7))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@mantine/dates@9.4.1(@mantine/core@9.4.1(@mantine/hooks@9.4.1(react@19.2.7))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@mantine/hooks@9.4.1(react@19.2.7))(dayjs@1.11.21)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@mantine/hooks@9.4.1(react@19.2.7))(@tabler/icons-react@3.44.0(react@19.2.7))(clsx@2.1.1)(dayjs@1.11.21)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) mcp-handler: specifier: 'catalog:' - version: 1.1.0(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3))(next@16.2.9(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.61.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(sass@1.101.0)) + version: 1.1.0(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3))(next@16.2.9(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.61.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(sass@1.101.0)) next: specifier: 'catalog:' - version: 16.2.9(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.61.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(sass@1.101.0) + version: 16.2.9(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.61.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(sass@1.101.0) postcss-preset-mantine: specifier: 'catalog:' version: 1.18.0(postcss@8.5.15) posthog-js: specifier: 'catalog:' - version: 1.391.2 + version: 1.395.0 prismjs: specifier: 'catalog:' version: 1.30.0 @@ -1039,7 +1039,7 @@ importers: version: 2.2.6 trpc-to-mcp: specifier: 'catalog:' - version: 1.3.2(@trpc/server@11.17.0(typescript@6.0.3))(better-auth@1.6.15(@opentelemetry/api@1.9.0)(better-sqlite3@12.10.0)(drizzle-kit@0.31.10)(drizzle-orm@0.45.2(@libsql/client-wasm@0.14.0)(@opentelemetry/api@1.9.0)(@types/better-sqlite3@7.6.13)(@types/pg@8.20.0)(better-sqlite3@12.10.0)(gel@2.0.0)(kysely@0.29.2)(mysql2@3.22.5(@types/node@24.13.2))(pg@8.22.0)(sql.js@1.14.1))(mysql2@3.22.5(@types/node@24.13.2))(next@16.2.9(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.61.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(sass@1.101.0))(pg@8.22.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(vitest@4.1.9)(vue@3.5.35(typescript@6.0.3)))(mcp-handler@1.1.0(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3))(next@16.2.9(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.61.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(sass@1.101.0)))(zod@4.4.3) + version: 1.3.2(@trpc/server@11.17.0(typescript@6.0.3))(better-auth@1.6.15(@opentelemetry/api@1.9.0)(better-sqlite3@12.10.0)(drizzle-kit@0.31.10)(drizzle-orm@0.45.2(@libsql/client-wasm@0.14.0)(@opentelemetry/api@1.9.0)(@types/better-sqlite3@7.6.13)(@types/pg@8.20.0)(better-sqlite3@12.10.0)(gel@2.0.0)(kysely@0.29.2)(mysql2@3.22.5(@types/node@24.13.2))(pg@8.22.0)(sql.js@1.14.1))(mysql2@3.22.5(@types/node@24.13.2))(next@16.2.9(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.61.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(sass@1.101.0))(pg@8.22.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(vitest@4.1.9)(vue@3.5.35(typescript@6.0.3)))(mcp-handler@1.1.0(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3))(next@16.2.9(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.61.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(sass@1.101.0)))(zod@4.4.3) use-deep-compare-effect: specifier: 'catalog:' version: 1.8.1(react@19.2.7) @@ -1079,7 +1079,7 @@ importers: version: 10.0.3 node-loader: specifier: 'catalog:' - version: 2.1.0(webpack@5.107.1(esbuild@0.28.0)(postcss@8.5.15)) + version: 2.1.0(webpack@5.107.1(esbuild@0.28.1)(postcss@8.5.16)) typescript: specifier: 'catalog:' version: 6.0.3 @@ -1334,7 +1334,7 @@ importers: version: 4.5.0 next: specifier: 'catalog:' - version: 16.2.9(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.61.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(sass@1.101.0) + version: 16.2.9(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.61.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(sass@1.101.0) react: specifier: 'catalog:' version: 19.2.7 @@ -1346,7 +1346,7 @@ importers: version: 2.2.6 trpc-to-mcp: specifier: 'catalog:' - version: 1.3.2(@trpc/server@11.17.0(typescript@6.0.3))(better-auth@1.6.15(@opentelemetry/api@1.9.0)(better-sqlite3@12.10.0)(drizzle-kit@0.31.10)(drizzle-orm@0.45.2(@libsql/client-wasm@0.14.0)(@opentelemetry/api@1.9.0)(@types/better-sqlite3@7.6.13)(@types/pg@8.20.0)(better-sqlite3@12.10.0)(gel@2.0.0)(kysely@0.29.2)(mysql2@3.22.5(@types/node@24.13.2))(pg@8.22.0)(sql.js@1.14.1))(mysql2@3.22.5(@types/node@24.13.2))(next@16.2.9(@opentelemetry/api@1.9.0)(@playwright/test@1.61.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(sass@1.101.0))(pg@8.22.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(vitest@4.1.9)(vue@3.5.35(typescript@6.0.3)))(mcp-handler@1.1.0(@modelcontextprotocol/sdk@1.26.0(zod@4.4.3))(next@16.2.9(@opentelemetry/api@1.9.0)(@playwright/test@1.61.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(sass@1.101.0)))(zod@4.4.3) + version: 1.3.2(@trpc/server@11.17.0(typescript@6.0.3))(better-auth@1.6.15(@opentelemetry/api@1.9.0)(better-sqlite3@12.10.0)(drizzle-kit@0.31.10)(drizzle-orm@0.45.2(@libsql/client-wasm@0.14.0)(@opentelemetry/api@1.9.0)(@types/better-sqlite3@7.6.13)(@types/pg@8.20.0)(better-sqlite3@12.10.0)(gel@2.0.0)(kysely@0.29.2)(mysql2@3.22.5(@types/node@24.13.2))(pg@8.22.0)(sql.js@1.14.1))(mysql2@3.22.5(@types/node@24.13.2))(next@16.2.9(@opentelemetry/api@1.9.0)(@playwright/test@1.61.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(sass@1.101.0))(pg@8.22.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(vitest@4.1.9)(vue@3.5.35(typescript@6.0.3)))(mcp-handler@1.1.0(@modelcontextprotocol/sdk@1.26.0(zod@4.4.3))(next@16.2.9(@opentelemetry/api@1.9.0)(@playwright/test@1.61.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(sass@1.101.0)))(zod@4.4.3) trpc-to-openapi: specifier: 'catalog:' version: 3.3.0(patch_hash=2ca3c16af0fcca0c736697ad4fe553a14f794524fa9ce0d5c3e8ee4aea76090c)(@trpc/server@11.17.0(typescript@6.0.3))(zod-openapi@5.3.0(zod@4.4.3))(zod@4.4.3) @@ -1395,10 +1395,10 @@ importers: version: 8.1.8 next: specifier: 'catalog:' - version: 16.2.9(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.61.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(sass@1.101.0) + version: 16.2.9(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.61.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(sass@1.101.0) next-auth: specifier: 'catalog:' - version: 5.0.0-beta.31(next@16.2.9(@opentelemetry/api@1.9.0)(@playwright/test@1.61.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(sass@1.101.0))(react@19.2.7) + version: 5.0.0-beta.31(next@16.2.9(@opentelemetry/api@1.9.0)(@playwright/test@1.61.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(sass@1.101.0))(react@19.2.7) react: specifier: 'catalog:' version: 19.2.7 @@ -1488,7 +1488,7 @@ importers: version: 1.11.21 next: specifier: 'catalog:' - version: 16.2.9(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.61.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(sass@1.101.0) + version: 16.2.9(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.61.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(sass@1.101.0) octokit: specifier: 'catalog:' version: 5.0.5 @@ -1682,7 +1682,7 @@ importers: version: link:../validation '@mantine/core': specifier: 'catalog:' - version: 9.3.2(@mantine/hooks@9.3.2(react@19.2.7))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + version: 9.4.1(@mantine/hooks@9.4.1(react@19.2.7))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@paralleldrive/cuid2': specifier: 'catalog:' version: 3.3.0 @@ -1771,7 +1771,7 @@ importers: version: link:../core dockerode: specifier: 'catalog:' - version: 5.0.0 + version: 5.0.1 devDependencies: '@homarr/tsconfig': specifier: workspace:^0.1.0 @@ -1796,7 +1796,7 @@ importers: version: link:../validation '@mantine/form': specifier: 'catalog:' - version: 9.3.2(react@19.2.7) + version: 9.4.1(react@19.2.7) zod: specifier: 'catalog:' version: 4.4.3 @@ -1836,7 +1836,7 @@ importers: version: link:../validation '@mantine/core': specifier: 'catalog:' - version: 9.3.2(@mantine/hooks@9.3.2(react@19.2.7))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + version: 9.4.1(@mantine/hooks@9.4.1(react@19.2.7))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) react: specifier: 'catalog:' version: 19.2.7 @@ -1906,9 +1906,6 @@ importers: '@ctrl/transmission': specifier: 'catalog:' version: 7.4.0 - '@gitbeaker/rest': - specifier: 'catalog:' - version: 43.8.0 '@homarr/common': specifier: workspace:^0.1.0 version: link:../common @@ -1938,13 +1935,10 @@ importers: version: link:../validation '@immich/sdk': specifier: 'catalog:' - version: 2.7.5 + version: 3.0.0 '@jellyfin/sdk': specifier: 'catalog:' version: 0.13.0(axios@1.15.0) - '@octokit/auth-app': - specifier: 'catalog:' - version: 8.2.0 ical.js: specifier: 'catalog:' version: 2.2.1 @@ -2002,10 +1996,10 @@ importers: version: link:../ui '@mantine/core': specifier: 'catalog:' - version: 9.3.2(@mantine/hooks@9.3.2(react@19.2.7))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + version: 9.4.1(@mantine/hooks@9.4.1(react@19.2.7))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@mantine/hooks': specifier: 'catalog:' - version: 9.3.2(react@19.2.7) + version: 9.4.1(react@19.2.7) react: specifier: 'catalog:' version: 19.2.7 @@ -2054,7 +2048,7 @@ importers: version: link:../validation '@mantine/core': specifier: 'catalog:' - version: 9.3.2(@mantine/hooks@9.3.2(react@19.2.7))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + version: 9.4.1(@mantine/hooks@9.4.1(react@19.2.7))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@tabler/icons-react': specifier: 'catalog:' version: 3.44.0(react@19.2.7) @@ -2063,7 +2057,7 @@ importers: version: 1.11.21 next: specifier: 'catalog:' - version: 16.2.9(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.61.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(sass@1.101.0) + version: 16.2.9(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.61.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(sass@1.101.0) react: specifier: 'catalog:' version: 19.2.7 @@ -2088,7 +2082,7 @@ importers: version: link:../ui '@mantine/notifications': specifier: 'catalog:' - version: 9.3.2(@mantine/core@9.3.2(@mantine/hooks@9.3.2(react@19.2.7))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@mantine/hooks@9.3.2(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + version: 9.4.1(@mantine/core@9.4.1(@mantine/hooks@9.4.1(react@19.2.7))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@mantine/hooks@9.4.1(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@tabler/icons-react': specifier: 'catalog:' version: 3.44.0(react@19.2.7) @@ -2137,16 +2131,16 @@ importers: version: link:../validation '@mantine/core': specifier: 'catalog:' - version: 9.3.2(@mantine/hooks@9.3.2(react@19.2.7))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + version: 9.4.1(@mantine/hooks@9.4.1(react@19.2.7))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@mantine/hooks': specifier: 'catalog:' - version: 9.3.2(react@19.2.7) + version: 9.4.1(react@19.2.7) adm-zip: specifier: 'catalog:' version: 0.5.17 next: specifier: 'catalog:' - version: 16.2.9(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.61.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(sass@1.101.0) + version: 16.2.9(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.61.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(sass@1.101.0) react: specifier: 'catalog:' version: 19.2.7 @@ -2308,10 +2302,10 @@ importers: version: link:../server-settings '@mantine/dates': specifier: 'catalog:' - version: 9.3.2(@mantine/core@9.3.2(@mantine/hooks@9.3.2(react@19.2.7))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@mantine/hooks@9.3.2(react@19.2.7))(dayjs@1.11.21)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + version: 9.4.1(@mantine/core@9.4.1(@mantine/hooks@9.4.1(react@19.2.7))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@mantine/hooks@9.4.1(react@19.2.7))(dayjs@1.11.21)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) next: specifier: 'catalog:' - version: 16.2.9(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.61.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(sass@1.101.0) + version: 16.2.9(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.61.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(sass@1.101.0) react: specifier: 'catalog:' version: 19.2.7 @@ -2363,13 +2357,13 @@ importers: version: link:../ui '@mantine/core': specifier: 'catalog:' - version: 9.3.2(@mantine/hooks@9.3.2(react@19.2.7))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + version: 9.4.1(@mantine/hooks@9.4.1(react@19.2.7))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@mantine/hooks': specifier: 'catalog:' - version: 9.3.2(react@19.2.7) + version: 9.4.1(react@19.2.7) '@mantine/spotlight': specifier: 'catalog:' - version: 9.3.2(@mantine/core@9.3.2(@mantine/hooks@9.3.2(react@19.2.7))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@mantine/hooks@9.3.2(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + version: 9.4.1(@mantine/core@9.4.1(@mantine/hooks@9.4.1(react@19.2.7))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@mantine/hooks@9.4.1(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@tabler/icons-react': specifier: 'catalog:' version: 3.44.0(react@19.2.7) @@ -2378,7 +2372,7 @@ importers: version: 2.20.1(@babel/core@7.29.0)(@babel/template@7.29.7)(@types/react@19.2.17)(react@19.2.7) next: specifier: 'catalog:' - version: 16.2.9(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.61.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(sass@1.101.0) + version: 16.2.9(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.61.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(sass@1.101.0) react: specifier: 'catalog:' version: 19.2.7 @@ -2412,13 +2406,13 @@ importers: version: 4.3.1 mantine-react-table: specifier: 'catalog:' - version: mantine-react-table-open@9.0.3(@mantine/core@9.3.2(@mantine/hooks@9.3.2(react@19.2.7))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@mantine/dates@9.3.2(@mantine/core@9.3.2(@mantine/hooks@9.3.2(react@19.2.7))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@mantine/hooks@9.3.2(react@19.2.7))(dayjs@1.11.21)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@mantine/hooks@9.3.2(react@19.2.7))(@tabler/icons-react@3.44.0(react@19.2.7))(clsx@2.1.1)(dayjs@1.11.21)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + version: mantine-react-table-open@9.0.3(@mantine/core@9.4.1(@mantine/hooks@9.4.1(react@19.2.7))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@mantine/dates@9.4.1(@mantine/core@9.4.1(@mantine/hooks@9.4.1(react@19.2.7))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@mantine/hooks@9.4.1(react@19.2.7))(dayjs@1.11.21)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@mantine/hooks@9.4.1(react@19.2.7))(@tabler/icons-react@3.44.0(react@19.2.7))(clsx@2.1.1)(dayjs@1.11.21)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) next: specifier: 'catalog:' - version: 16.2.9(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.61.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(sass@1.101.0) + version: 16.2.9(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.61.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(sass@1.101.0) next-intl: specifier: 'catalog:' - version: 4.13.0(next@16.2.9(@opentelemetry/api@1.9.0)(@playwright/test@1.61.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(sass@1.101.0))(react@19.2.7)(typescript@6.0.3) + version: 4.13.0(next@16.2.9(@opentelemetry/api@1.9.0)(@playwright/test@1.61.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(sass@1.101.0))(react@19.2.7)(typescript@6.0.3) react: specifier: 'catalog:' version: 19.2.7 @@ -2452,13 +2446,13 @@ importers: version: link:../validation '@mantine/core': specifier: 'catalog:' - version: 9.3.2(@mantine/hooks@9.3.2(react@19.2.7))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + version: 9.4.1(@mantine/hooks@9.4.1(react@19.2.7))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@mantine/dates': specifier: 'catalog:' - version: 9.3.2(@mantine/core@9.3.2(@mantine/hooks@9.3.2(react@19.2.7))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@mantine/hooks@9.3.2(react@19.2.7))(dayjs@1.11.21)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + version: 9.4.1(@mantine/core@9.4.1(@mantine/hooks@9.4.1(react@19.2.7))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@mantine/hooks@9.4.1(react@19.2.7))(dayjs@1.11.21)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@mantine/hooks': specifier: 'catalog:' - version: 9.3.2(react@19.2.7) + version: 9.4.1(react@19.2.7) '@tabler/icons-react': specifier: 'catalog:' version: 3.44.0(react@19.2.7) @@ -2467,10 +2461,10 @@ importers: version: 4.2.0 mantine-react-table: specifier: 'catalog:' - version: mantine-react-table-open@9.0.3(@mantine/core@9.3.2(@mantine/hooks@9.3.2(react@19.2.7))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@mantine/dates@9.3.2(@mantine/core@9.3.2(@mantine/hooks@9.3.2(react@19.2.7))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@mantine/hooks@9.3.2(react@19.2.7))(dayjs@1.11.21)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@mantine/hooks@9.3.2(react@19.2.7))(@tabler/icons-react@3.44.0(react@19.2.7))(clsx@2.1.1)(dayjs@1.11.21)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + version: mantine-react-table-open@9.0.3(@mantine/core@9.4.1(@mantine/hooks@9.4.1(react@19.2.7))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@mantine/dates@9.4.1(@mantine/core@9.4.1(@mantine/hooks@9.4.1(react@19.2.7))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@mantine/hooks@9.4.1(react@19.2.7))(dayjs@1.11.21)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@mantine/hooks@9.4.1(react@19.2.7))(@tabler/icons-react@3.44.0(react@19.2.7))(clsx@2.1.1)(dayjs@1.11.21)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) next: specifier: 'catalog:' - version: 16.2.9(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.61.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(sass@1.101.0) + version: 16.2.9(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.61.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(sass@1.101.0) react: specifier: 'catalog:' version: 19.2.7 @@ -2589,16 +2583,16 @@ importers: version: link:../validation '@mantine/charts': specifier: 'catalog:' - version: 9.3.2(@mantine/core@9.3.2(@mantine/hooks@9.3.2(react@19.2.7))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@mantine/hooks@9.3.2(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(recharts@3.8.1(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react-is@19.2.7)(react@19.2.7)(redux@5.0.1)) + version: 9.4.1(@mantine/core@9.4.1(@mantine/hooks@9.4.1(react@19.2.7))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@mantine/hooks@9.4.1(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(recharts@3.9.0(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react-is@19.2.7)(react@19.2.7)(redux@5.0.1)) '@mantine/core': specifier: 'catalog:' - version: 9.3.2(@mantine/hooks@9.3.2(react@19.2.7))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + version: 9.4.1(@mantine/hooks@9.4.1(react@19.2.7))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@mantine/form': specifier: 'catalog:' - version: 9.3.2(react@19.2.7) + version: 9.4.1(react@19.2.7) '@mantine/hooks': specifier: 'catalog:' - version: 9.3.2(react@19.2.7) + version: 9.4.1(react@19.2.7) '@tabler/icons-react': specifier: 'catalog:' version: 3.44.0(react@19.2.7) @@ -2664,13 +2658,13 @@ importers: version: 1.21.0(react@19.2.7) mantine-datatable: specifier: 'catalog:' - version: 9.3.1(@mantine/core@9.3.2(@mantine/hooks@9.3.2(react@19.2.7))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@mantine/hooks@9.3.2(react@19.2.7))(clsx@2.1.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + version: 9.3.1(@mantine/core@9.4.1(@mantine/hooks@9.4.1(react@19.2.7))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@mantine/hooks@9.4.1(react@19.2.7))(clsx@2.1.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) mantine-react-table: specifier: 'catalog:' - version: mantine-react-table-open@9.0.3(@mantine/core@9.3.2(@mantine/hooks@9.3.2(react@19.2.7))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@mantine/dates@9.3.2(@mantine/core@9.3.2(@mantine/hooks@9.3.2(react@19.2.7))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@mantine/hooks@9.3.2(react@19.2.7))(dayjs@1.11.21)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@mantine/hooks@9.3.2(react@19.2.7))(@tabler/icons-react@3.44.0(react@19.2.7))(clsx@2.1.1)(dayjs@1.11.21)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + version: mantine-react-table-open@9.0.3(@mantine/core@9.4.1(@mantine/hooks@9.4.1(react@19.2.7))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@mantine/dates@9.4.1(@mantine/core@9.4.1(@mantine/hooks@9.4.1(react@19.2.7))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@mantine/hooks@9.4.1(react@19.2.7))(dayjs@1.11.21)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@mantine/hooks@9.4.1(react@19.2.7))(@tabler/icons-react@3.44.0(react@19.2.7))(clsx@2.1.1)(dayjs@1.11.21)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) next: specifier: 'catalog:' - version: 16.2.9(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.61.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(sass@1.101.0) + version: 16.2.9(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.61.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(sass@1.101.0) react: specifier: 'catalog:' version: 19.2.7 @@ -2688,7 +2682,7 @@ importers: version: 3.8.3(quill-delta@5.1.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) recharts: specifier: 'catalog:' - version: 3.8.1(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react-is@19.2.7)(react@19.2.7)(redux@5.0.1) + version: 3.9.0(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react-is@19.2.7)(react@19.2.7)(redux@5.0.1) video.js: specifier: 'catalog:' version: 8.23.9 @@ -4242,17 +4236,14 @@ packages: '@drizzle-team/brocli@0.12.0': resolution: {integrity: sha512-mlUE+rZ8CatQekLhnaiN91Iemdd+e2gFKooGlnRB3oPTL3VghLfX24dx7HrzMNeC1JrIB/0kpsfyty3f5HNfxQ==} - '@emnapi/core@1.10.0': - resolution: {integrity: sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==} - - '@emnapi/core@1.11.0': - resolution: {integrity: sha512-l9Oo58x0HOP5znGzVhYW9U3e5wVuA4LAZU2AGezTmkhO1CgQRFDhDg4nneHsu/t3WniXg9QrG2nIXL/ZS8ln8Q==} + '@emnapi/core@1.11.1': + resolution: {integrity: sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==} '@emnapi/runtime@1.10.0': resolution: {integrity: sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==} - '@emnapi/wasi-threads@1.2.1': - resolution: {integrity: sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==} + '@emnapi/runtime@1.11.1': + resolution: {integrity: sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==} '@emnapi/wasi-threads@1.2.2': resolution: {integrity: sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==} @@ -4286,6 +4277,12 @@ packages: cpu: [ppc64] os: [aix] + '@esbuild/aix-ppc64@0.28.1': + resolution: {integrity: sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + '@esbuild/android-arm64@0.25.12': resolution: {integrity: sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==} engines: {node: '>=18'} @@ -4304,6 +4301,12 @@ packages: cpu: [arm64] os: [android] + '@esbuild/android-arm64@0.28.1': + resolution: {integrity: sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + '@esbuild/android-arm@0.25.12': resolution: {integrity: sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==} engines: {node: '>=18'} @@ -4322,6 +4325,12 @@ packages: cpu: [arm] os: [android] + '@esbuild/android-arm@0.28.1': + resolution: {integrity: sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + '@esbuild/android-x64@0.25.12': resolution: {integrity: sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==} engines: {node: '>=18'} @@ -4340,6 +4349,12 @@ packages: cpu: [x64] os: [android] + '@esbuild/android-x64@0.28.1': + resolution: {integrity: sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + '@esbuild/darwin-arm64@0.25.12': resolution: {integrity: sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==} engines: {node: '>=18'} @@ -4358,6 +4373,12 @@ packages: cpu: [arm64] os: [darwin] + '@esbuild/darwin-arm64@0.28.1': + resolution: {integrity: sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + '@esbuild/darwin-x64@0.25.12': resolution: {integrity: sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==} engines: {node: '>=18'} @@ -4376,6 +4397,12 @@ packages: cpu: [x64] os: [darwin] + '@esbuild/darwin-x64@0.28.1': + resolution: {integrity: sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + '@esbuild/freebsd-arm64@0.25.12': resolution: {integrity: sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==} engines: {node: '>=18'} @@ -4394,6 +4421,12 @@ packages: cpu: [arm64] os: [freebsd] + '@esbuild/freebsd-arm64@0.28.1': + resolution: {integrity: sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + '@esbuild/freebsd-x64@0.25.12': resolution: {integrity: sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==} engines: {node: '>=18'} @@ -4412,6 +4445,12 @@ packages: cpu: [x64] os: [freebsd] + '@esbuild/freebsd-x64@0.28.1': + resolution: {integrity: sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + '@esbuild/linux-arm64@0.25.12': resolution: {integrity: sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==} engines: {node: '>=18'} @@ -4430,6 +4469,12 @@ packages: cpu: [arm64] os: [linux] + '@esbuild/linux-arm64@0.28.1': + resolution: {integrity: sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + '@esbuild/linux-arm@0.25.12': resolution: {integrity: sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==} engines: {node: '>=18'} @@ -4448,6 +4493,12 @@ packages: cpu: [arm] os: [linux] + '@esbuild/linux-arm@0.28.1': + resolution: {integrity: sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + '@esbuild/linux-ia32@0.25.12': resolution: {integrity: sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==} engines: {node: '>=18'} @@ -4466,6 +4517,12 @@ packages: cpu: [ia32] os: [linux] + '@esbuild/linux-ia32@0.28.1': + resolution: {integrity: sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + '@esbuild/linux-loong64@0.25.12': resolution: {integrity: sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==} engines: {node: '>=18'} @@ -4484,6 +4541,12 @@ packages: cpu: [loong64] os: [linux] + '@esbuild/linux-loong64@0.28.1': + resolution: {integrity: sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + '@esbuild/linux-mips64el@0.25.12': resolution: {integrity: sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==} engines: {node: '>=18'} @@ -4502,6 +4565,12 @@ packages: cpu: [mips64el] os: [linux] + '@esbuild/linux-mips64el@0.28.1': + resolution: {integrity: sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + '@esbuild/linux-ppc64@0.25.12': resolution: {integrity: sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==} engines: {node: '>=18'} @@ -4520,6 +4589,12 @@ packages: cpu: [ppc64] os: [linux] + '@esbuild/linux-ppc64@0.28.1': + resolution: {integrity: sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + '@esbuild/linux-riscv64@0.25.12': resolution: {integrity: sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==} engines: {node: '>=18'} @@ -4538,6 +4613,12 @@ packages: cpu: [riscv64] os: [linux] + '@esbuild/linux-riscv64@0.28.1': + resolution: {integrity: sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + '@esbuild/linux-s390x@0.25.12': resolution: {integrity: sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==} engines: {node: '>=18'} @@ -4556,6 +4637,12 @@ packages: cpu: [s390x] os: [linux] + '@esbuild/linux-s390x@0.28.1': + resolution: {integrity: sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + '@esbuild/linux-x64@0.25.12': resolution: {integrity: sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==} engines: {node: '>=18'} @@ -4574,6 +4661,12 @@ packages: cpu: [x64] os: [linux] + '@esbuild/linux-x64@0.28.1': + resolution: {integrity: sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + '@esbuild/netbsd-arm64@0.25.12': resolution: {integrity: sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==} engines: {node: '>=18'} @@ -4592,6 +4685,12 @@ packages: cpu: [arm64] os: [netbsd] + '@esbuild/netbsd-arm64@0.28.1': + resolution: {integrity: sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + '@esbuild/netbsd-x64@0.25.12': resolution: {integrity: sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==} engines: {node: '>=18'} @@ -4610,6 +4709,12 @@ packages: cpu: [x64] os: [netbsd] + '@esbuild/netbsd-x64@0.28.1': + resolution: {integrity: sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + '@esbuild/openbsd-arm64@0.25.12': resolution: {integrity: sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==} engines: {node: '>=18'} @@ -4628,6 +4733,12 @@ packages: cpu: [arm64] os: [openbsd] + '@esbuild/openbsd-arm64@0.28.1': + resolution: {integrity: sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + '@esbuild/openbsd-x64@0.25.12': resolution: {integrity: sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==} engines: {node: '>=18'} @@ -4646,6 +4757,12 @@ packages: cpu: [x64] os: [openbsd] + '@esbuild/openbsd-x64@0.28.1': + resolution: {integrity: sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + '@esbuild/openharmony-arm64@0.25.12': resolution: {integrity: sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==} engines: {node: '>=18'} @@ -4664,6 +4781,12 @@ packages: cpu: [arm64] os: [openharmony] + '@esbuild/openharmony-arm64@0.28.1': + resolution: {integrity: sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + '@esbuild/sunos-x64@0.25.12': resolution: {integrity: sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==} engines: {node: '>=18'} @@ -4682,6 +4805,12 @@ packages: cpu: [x64] os: [sunos] + '@esbuild/sunos-x64@0.28.1': + resolution: {integrity: sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + '@esbuild/win32-arm64@0.25.12': resolution: {integrity: sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==} engines: {node: '>=18'} @@ -4700,6 +4829,12 @@ packages: cpu: [arm64] os: [win32] + '@esbuild/win32-arm64@0.28.1': + resolution: {integrity: sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + '@esbuild/win32-ia32@0.25.12': resolution: {integrity: sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==} engines: {node: '>=18'} @@ -4718,6 +4853,12 @@ packages: cpu: [ia32] os: [win32] + '@esbuild/win32-ia32@0.28.1': + resolution: {integrity: sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + '@esbuild/win32-x64@0.25.12': resolution: {integrity: sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==} engines: {node: '>=18'} @@ -4736,6 +4877,12 @@ packages: cpu: [x64] os: [win32] + '@esbuild/win32-x64@0.28.1': + resolution: {integrity: sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + '@exodus/bytes@1.15.0': resolution: {integrity: sha512-UY0nlA+feH81UGSHv92sLEPLCeZFjXOuHhrIo0HQydScuQc8s0A7kL/UdgwgDq8g8ilksmuoF35YVTNphV2aBQ==} engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} @@ -4819,8 +4966,8 @@ packages: '@formatjs/intl-localematcher@0.8.1': resolution: {integrity: sha512-xwEuwQFdtSq1UKtQnyTZWC+eHdv7Uygoa+H2k/9uzBVQjDyp9r20LNDNKedWXll7FssT3GRHvqsdJGYSUWqYFA==} - '@gfazioli/mantine-onboarding-tour@4.0.5': - resolution: {integrity: sha512-KyzmbcUqnUuezGZKIpsb6zeTlLgjetBQksysfYhuP8wfuqObkAL+aD0DnHnaxrdHxR9GYFkiIDImhHDUG1p5Vw==} + '@gfazioli/mantine-onboarding-tour@4.0.6': + resolution: {integrity: sha512-EMXwq5VPdQO8ppgKGY3i/KbB3jfuqJir5xmbAx0RNV3Ua3uXfznXJb0mNLykP1gQ+qe6WceORNsFUtQS0tIZAw==} peerDependencies: '@mantine/core': '>=9.0.0' '@mantine/hooks': '>=9.0.0' @@ -4828,18 +4975,6 @@ packages: react: ^18.x || ^19.x react-dom: ^18.x || ^19.x - '@gitbeaker/core@43.8.0': - resolution: {integrity: sha512-H+LfKuf4dExBinb79c+CXViRBvTVQNf5BYLNSizm2SiqdED5JruhKX88payefleY0szp7G/mySlFSXPyGRH1dQ==} - engines: {node: '>=18.20.0'} - - '@gitbeaker/requester-utils@43.8.0': - resolution: {integrity: sha512-d/SiJdxijc+aH5ZBQOw83XLxNSXqsBZNm5k3nPu1EHxGxK0fajXmxdMl0/vNXbKRggnIquFCxURkrQSEzfjqxQ==} - engines: {node: '>=18.20.0'} - - '@gitbeaker/rest@43.8.0': - resolution: {integrity: sha512-xxqsNsUXaFang9b2e/NTIgqUeuUlifA2Opy1mOVqTDuJZZNIOTgUNyziwBJoleBhMC0XuvY3JNVMWthufcVjRw==} - engines: {node: '>=18.20.0'} - '@grpc/grpc-js@1.12.5': resolution: {integrity: sha512-d3iiHxdpg5+ZcJ6jnDSOT8Z0O0VMVGy34jAnYLUX8yd36b1qn8f1TwOA/Lc7TsOh03IkPJ38eGI5qD2EjNkoEA==} engines: {node: '>=12.10.0'} @@ -4881,7 +5016,7 @@ packages: resolution: {integrity: sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw==} engines: {node: '>=18.14.1'} peerDependencies: - hono: '>=4.12.26' + hono: '>=4.12.27' '@iconify/types@2.0.0': resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==} @@ -5042,8 +5177,8 @@ packages: cpu: [x64] os: [win32] - '@immich/sdk@2.7.5': - resolution: {integrity: sha512-gCTpFAWlFF+1Sx0jbqPVmwN+0JD+eZEHzhf3q+BbxuQF+ORpNqT0D453mOyonogvy17nJF1jGA2PtT6vOQiSnA==} + '@immich/sdk@3.0.0': + resolution: {integrity: sha512-HUT+L5LEIRsW65GtGj1hLqv9XQzIWzEyaAnAUsVVYHAc3hs+bD8p+KV9Km6Yw8Kg5eOqt+pDcp0peHYkgwnZJQ==} '@inquirer/ansi@1.0.2': resolution: {integrity: sha512-S8qNSZiYzFd0wAcyG5AXCvUHC5Sr7xpZ9wZ2py9XR88jUz8wooStVx5M6dRzczbBWjic9NP7+rY0Xi7qqK/aMQ==} @@ -5442,88 +5577,88 @@ packages: '@libsql/core@0.14.0': resolution: {integrity: sha512-nhbuXf7GP3PSZgdCY2Ecj8vz187ptHlZQ0VRc751oB2C1W8jQUXKKklvt7t1LJiUTQBVJuadF628eUk+3cRi4Q==} - '@mantine/charts@9.3.2': - resolution: {integrity: sha512-G3cFLLxmmRr0ij5fOHeO/jqR/U+DnMNZy0S7GEvs4lIzDRl871TDb1BogZyeHUGKuLnsLBYpTA719IcmswYGMw==} + '@mantine/charts@9.4.1': + resolution: {integrity: sha512-sGP1Org+nkkyJ81o6uBoUiAffVsy8Uemu+uwVVNaCVavDrRBGj3I4wCfSNLQdrwr+aoFbDLAyWaM0ON9bZWCbg==} peerDependencies: - '@mantine/core': 9.3.2 - '@mantine/hooks': 9.3.2 + '@mantine/core': 9.4.1 + '@mantine/hooks': 9.4.1 react: ^19.2.0 react-dom: ^19.2.0 recharts: '>=3.2.1' - '@mantine/colors-generator@9.3.2': - resolution: {integrity: sha512-q2sqR2vCwdkDD/OuTcmfvkg66Wc6avepYB+073qh75YP3+EPa60Ng/h3Vfgmt+G2iTsqURJgCAbCLaZZr/9g4Q==} + '@mantine/colors-generator@9.4.1': + resolution: {integrity: sha512-HEJAJACPfS5Y2XRf8ILOUpH7/nag7YN8v3SxGo+vTCyvWnNNqNM19V+weohrHSLnM+TLgHehFzHdG8CzmJkiXQ==} peerDependencies: chroma-js: '>=2.4.2' - '@mantine/core@9.3.2': - resolution: {integrity: sha512-Upy/Z9Sj2eW2dGrFgUy/2kISVsxMTBYTDfP2TFdsIA3PdPSBkdqfSOPX/ug3d3F3a4bnwQSqcO6+aPEpBIh8dg==} + '@mantine/core@9.4.1': + resolution: {integrity: sha512-lZWEICrum4+vwKxzh/mk4RB1N6BqZ0Cshdl6lhm7OYLiQzmOaxKtOgyaWz+vr65G8mqTXjalZalB+wmMVBYK2Q==} peerDependencies: - '@mantine/hooks': 9.3.2 + '@mantine/hooks': 9.4.1 react: ^19.2.0 react-dom: ^19.2.0 - '@mantine/dates@9.3.2': - resolution: {integrity: sha512-MzHrXGoOb3rCnHBlsI/BPAjC8K5tZ4f8pzg66tsTsupVCQBaBpHSVatQwNRJ6K2JC9pyCyTa9BL803C8AiWycA==} + '@mantine/dates@9.4.1': + resolution: {integrity: sha512-DoQDvQMAlIw6ohwhUwYuTYQevG9y5AQmYWkhOHUuavABFEZmtBglkwziBoATvxT09I/6I56UMPhproXcS6XUfg==} peerDependencies: - '@mantine/core': 9.3.2 - '@mantine/hooks': 9.3.2 + '@mantine/core': 9.4.1 + '@mantine/hooks': 9.4.1 dayjs: '>=1.0.0' react: ^19.2.0 react-dom: ^19.2.0 - '@mantine/dropzone@9.3.2': - resolution: {integrity: sha512-wx/6wSVLVW+OfPU9FgkMkfnaUd7roPZG1zOlzE6EIRYIqsHciY3OAlrhQDpYnTkowsseJpXDg4JG788l5kNDUQ==} + '@mantine/dropzone@9.4.1': + resolution: {integrity: sha512-QmtwDsViRmIdiwpc3d1loV3ryb8L6O4y3aiuMnAcLxL31a9hUPlHUZAs6KSuA+XbziZDG4BjxRVYjB70PxwORg==} peerDependencies: - '@mantine/core': 9.3.2 - '@mantine/hooks': 9.3.2 + '@mantine/core': 9.4.1 + '@mantine/hooks': 9.4.1 react: ^19.2.0 react-dom: ^19.2.0 - '@mantine/form@9.3.2': - resolution: {integrity: sha512-jWEXcx8hhddOsny+q/lFtVToNS3VBJYICrkfFMWJsN+WAZamnPItWTPLqulTYVp1T8uma9MsEZ1LX4kqWUFANA==} + '@mantine/form@9.4.1': + resolution: {integrity: sha512-e4tqC/D8ii7maPySWIWY306MNljPRprHPhu0/jaZrX7sRyJy+C8V9aU+ZFKYliM1CeiH1UX88+q4hWFUWVEdQw==} peerDependencies: react: ^19.2.0 - '@mantine/hooks@9.3.2': - resolution: {integrity: sha512-jOjpUe0x1A/k3XUiu2/aaSCasXRI5ZKZOucm3ypwsvm9F2u8C1xzwBvagrzlbNZwJRF5vNYIJtCaT7NunPyc0A==} + '@mantine/hooks@9.4.1': + resolution: {integrity: sha512-eTI8wmzPx3r98zgKIEuvukmoGTHBhmtI6+9E6o2DbTmEU2eM1bCdjE2vFdf0op2AlRO0KEYEcZhNQi4T/SJk0A==} peerDependencies: react: ^19.2.0 - '@mantine/modals@9.3.2': - resolution: {integrity: sha512-XorW2PcYU+DQQVZmY80OlwI/bgkBPzf9MscjEZep8Z3aowrlNpnJXFbu2RoPCPMwROU2nX4gt0HJP2OiuGr28A==} + '@mantine/modals@9.4.1': + resolution: {integrity: sha512-z5G3Biz7/5Ybz5Z8u5Xyvo9UBmFhAX/YXMf6U96ejvtAB11xVnCo8m37R5DJyzeFEqB4D1tvUgWiVVPBb+yadw==} peerDependencies: - '@mantine/core': 9.3.2 - '@mantine/hooks': 9.3.2 + '@mantine/core': 9.4.1 + '@mantine/hooks': 9.4.1 react: ^19.2.0 react-dom: ^19.2.0 - '@mantine/notifications@9.3.2': - resolution: {integrity: sha512-mnda8hE/DizPi5Bbiwd2M2jXxfSZSUeayvnLYMbFweGQKXM+QRmX1ootyDRVqWew8paltewTNp84cWLjj/6fKw==} + '@mantine/notifications@9.4.1': + resolution: {integrity: sha512-Sm1g8E8RkFmesxpNz9zFvOjkTVxOa7dJJjMGoHNIXH+9/wO0s9kInny4mVnxTbLJJsD6nTqIQlk1v8tq2C4Ftw==} peerDependencies: - '@mantine/core': 9.3.2 - '@mantine/hooks': 9.3.2 + '@mantine/core': 9.4.1 + '@mantine/hooks': 9.4.1 react: ^19.2.0 react-dom: ^19.2.0 - '@mantine/spotlight@9.3.2': - resolution: {integrity: sha512-JAJhW51qvYNiPTEnX9ewZX521gKDsVsmaecuuR2iNyk0NbrOPB5SWF9ZZrPSMwFVtqnqYHziq5BLukzlf+kG/Q==} + '@mantine/spotlight@9.4.1': + resolution: {integrity: sha512-gJ+7DdnGULeobtGXVGbVgvJSG3TeecxLXNxzpZo+ohl1OetPftCBGJOUrX2tYOOC6nJE1eHSIr/kpB31YIRjhQ==} peerDependencies: - '@mantine/core': 9.3.2 - '@mantine/hooks': 9.3.2 + '@mantine/core': 9.4.1 + '@mantine/hooks': 9.4.1 react: ^19.2.0 react-dom: ^19.2.0 - '@mantine/store@9.3.2': - resolution: {integrity: sha512-uNmvjkwfUHZJ5bvzQroLFvubuUAHjBM0rh69RBRsUCG5FR5K4f8y4SX8+lch0wS6UJuuvodsSIK8H5jvZuFSgQ==} + '@mantine/store@9.4.1': + resolution: {integrity: sha512-/DCZq0aqqIdm03J0RJ7pbzoau2cwY8ndSOjskHOKHhTeWMnOBdCKXA3oMO5aIDldDqJ9T7Io4Boh4Pt461zNQw==} peerDependencies: react: ^19.2.0 - '@mantine/tiptap@9.3.2': - resolution: {integrity: sha512-X344wqt3eusMLPANWuNSnKoFjTDlCEOUpYq6hPWU6uBvxEHJGHuJFXjtm/jg/D6aNEvWr+P+m1+ElE7eLT/G0A==} + '@mantine/tiptap@9.4.1': + resolution: {integrity: sha512-0qN6SFh9BuwXNOHUf4ygx8LPvWcgU1zUULY7IJ9zKiVHfa1K6b0BRa8LpLVKtVv7K5NLyj2uHJwv+mXB+ypOJw==} peerDependencies: - '@mantine/core': 9.3.2 - '@mantine/hooks': 9.3.2 + '@mantine/core': 9.4.1 + '@mantine/hooks': 9.4.1 '@tiptap/extension-link': '>=3.3.0' '@tiptap/react': '>=3.3.0' react: ^19.2.0 @@ -5589,8 +5724,8 @@ packages: '@napi-rs/wasm-runtime@1.0.7': resolution: {integrity: sha512-SeDnOO0Tk7Okiq6DbXmmBODgOAb9dp9gjlphokTUxmt8U3liIP1ZsozBahH69j/RJv+Rfs6IwUKHTgQYJ/HBAw==} - '@napi-rs/wasm-runtime@1.1.4': - resolution: {integrity: sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==} + '@napi-rs/wasm-runtime@1.1.6': + resolution: {integrity: sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==} peerDependencies: '@emnapi/core': ^1.7.1 '@emnapi/runtime': ^1.7.1 @@ -5860,249 +5995,249 @@ packages: resolution: {integrity: sha512-/UhIkaZgPutTFmQ7RnIJGgDXZmtEJ7Dvi86xNTFWcnRxVRNk/aotsqDJYeEvDP+FSMB2SdW+pQzNMcWP0rwuNA==} engines: {node: '>=14'} - '@oxc-project/types@0.133.0': - resolution: {integrity: sha512-KzkdCd6Uxqnf6l3HOw1xfatAlUURA0g14cvBYFyJ5SaNOQbOUvBr9PKArcPcrNIeRsBdgcUzOGrhKveVpvOIGA==} + '@oxc-project/types@0.137.0': + resolution: {integrity: sha512-WT+Gb24i8hmvo85AIv2oEYouEXkRlKAlT9WaCa3TfLgNCN+GhrJOGZuIlMouAh38Qe4QOx26eUOVsq70qXrywA==} - '@oxfmt/binding-android-arm-eabi@0.55.0': - resolution: {integrity: sha512-+rFDOqQe5LOWgxrAJaZgLRudr6GQm0wGI6gtu7vVkrdLGjNMUSGbAlaCr8j7F2H2Er97vYQCU8WDb30onqMM1g==} + '@oxfmt/binding-android-arm-eabi@0.56.0': + resolution: {integrity: sha512-CSCxi7ovYojgfdPOdUb9T508HKeAdDIKeRGg7x8IZwVJrWz9gVgX7MbUnFqtQAE4QvoNo07mj2JlwnOzJw4qqA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [android] - '@oxfmt/binding-android-arm64@0.55.0': - resolution: {integrity: sha512-ctulLq8s3x8Zmvw6+iccB09TIKERAklRSmbJ10gk8mlAn05qZxoyo52dj3Hi9IJcmDSwF54fQaTVh2CbL6PInw==} + '@oxfmt/binding-android-arm64@0.56.0': + resolution: {integrity: sha512-HYJFnd+PkDwf6S9ZPGzXXtjNqvRWFnnhdbWaouh4mi/SxU8wmDuzlMn3xo/wDTGnr4Q1VA7ZzOaE/D4biW0W6A==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [android] - '@oxfmt/binding-darwin-arm64@0.55.0': - resolution: {integrity: sha512-xDQczLH9pw/RBk1h/GH0qcGMm8hQtmtVHBNLSH3lk1gEIR09hZ4L+mJQl4VqiVAvPK9VG9PYrWWuSQLt7xTbiA==} + '@oxfmt/binding-darwin-arm64@0.56.0': + resolution: {integrity: sha512-sftR/bEOr+t1gs+evwsHi/Xbq2FAPA2uU3VMr8n6ZU9PoK/IMSfnfu7+OEe/uy1+knhrFl4Wvy7Vkm3uo9mJ7g==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] - '@oxfmt/binding-darwin-x64@0.55.0': - resolution: {integrity: sha512-JaNoFCkF2CJdGgpPSMbuO9HVyXyoNGIhMHPvp6NYAjeVKw9XEYc0HcUWJLPQa3Q69WV5wMa9m5jPMJPtbLtcRg==} + '@oxfmt/binding-darwin-x64@0.56.0': + resolution: {integrity: sha512-z66SdjLqa3MUPKvTp3Mbb5nSjKSbnYxJGeB+Wx987s8T5hPcIRiBMfnJ6zcPgYtQn3x5xjvdzNVkXrSeYH6ZFg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] - '@oxfmt/binding-freebsd-x64@0.55.0': - resolution: {integrity: sha512-DNbszhpg6S2MIzax5azdHFTTBIVkR5xr8yyRZuA4yoDAwOkzIp3tmldgKZM2+VlT+hJIG0xUksA+elISzMEAfA==} + '@oxfmt/binding-freebsd-x64@0.56.0': + resolution: {integrity: sha512-t2tkrV1vtZyaItSQ71dTi2ZVKZEI39b/LqLT12V5KMfIeXK6N32TUC1jhOXKVQmhECq9j2ZXMQV3JeT1kh9Vmg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] - '@oxfmt/binding-linux-arm-gnueabihf@0.55.0': - resolution: {integrity: sha512-2snoaoRfFFyGnbOcKUK36rREBYxe/Xgz3uHbiA5zbCB/s6R4DQj4mHqYAaWWhgizCUSDxV8cE9zAZ0XleNpKGw==} + '@oxfmt/binding-linux-arm-gnueabihf@0.56.0': + resolution: {integrity: sha512-+gCy+Tp3RHeXQ9y/QrS76lXIpZkbziTyp6hIgjB2MssCwfMph3vG/GEfkhO34Rai1vhYIaUkvv8UT1BcDorJPw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@oxfmt/binding-linux-arm-musleabihf@0.55.0': - resolution: {integrity: sha512-q1aktHF/WRpSK81BX1dE/9vWrS2jGw1Nax2kb4DBLGAewubCLcoNyp4Zl/NSMgbv3vUS46Z33wIQkBVYOP3PYg==} + '@oxfmt/binding-linux-arm-musleabihf@0.56.0': + resolution: {integrity: sha512-0kKkVvQ2I+FJ2sxQyUu1zJ0yWP5kcWse/yVFnGQSFCXMwSSkfEaUGu0dW774O7nyy3jrcBGap7OSc8dZmU/CdA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@oxfmt/binding-linux-arm64-gnu@0.55.0': - resolution: {integrity: sha512-VD0y36aENezl/3tsclA/4G53Cc7iV+7Uoh7gz4yvcOTaEYBtJpQsE6PKDGTtUtOvGS4kv51ybfXY/nWZejO5IA==} + '@oxfmt/binding-linux-arm64-gnu@0.56.0': + resolution: {integrity: sha512-npkA2siMbyWRh+wEhi1aTAx4RirukGcGNt8V4Ch86pG+xU9aurqS1MZOnKYMu03ISwat3rB6zkQx51SsB9obNw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [glibc] - '@oxfmt/binding-linux-arm64-musl@0.55.0': - resolution: {integrity: sha512-r8xlKJFcsRmn0H5jZrdORae6RX9jDBrZVvOoxF+bCQtampQJClv80aZEHsv+NsLsp2KCE5ql79O7DpPVzYWpXA==} + '@oxfmt/binding-linux-arm64-musl@0.56.0': + resolution: {integrity: sha512-UekqOjGkV4/MkqreCV9SPIB2jlR3/HbXrmhV1rVXJZ9wfDXMyCMriLtq3tHqLY4PkbVWNtfcm1kMojJ26KLSJw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [musl] - '@oxfmt/binding-linux-ppc64-gnu@0.55.0': - resolution: {integrity: sha512-GRKv/HXHcwIVld/WU61rF0g0R16hl5EJ+ScKdpjevT57lnLnagj/U2YUbXf2mT+2Pg1uCzWC+mvGicPV3CDdLQ==} + '@oxfmt/binding-linux-ppc64-gnu@0.56.0': + resolution: {integrity: sha512-XSzveSpeZMD5XJpew5lRFVtNnT04xd3rJxENXmk7wkZzN9oWzv2aFJyoNDhOtoz69BYaS/fg4SYl+CfEZRpB0Q==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] libc: [glibc] - '@oxfmt/binding-linux-riscv64-gnu@0.55.0': - resolution: {integrity: sha512-rdv57enTiPtpSYRMKfAiEbQb0Puw5t9N7isVinDoo5qeLDScro2gznmZqSgSWbVZRzLisTeCTW8Qwgw0bOHv3A==} + '@oxfmt/binding-linux-riscv64-gnu@0.56.0': + resolution: {integrity: sha512-EkQ0nJa7k7HDDIVuPF7WY+k4k+bzdclLYtyIXNt7/OqVghfNiMym6YGppFBgx1XRIHW6QylxBz5OogumPjPJbQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] libc: [glibc] - '@oxfmt/binding-linux-riscv64-musl@0.55.0': - resolution: {integrity: sha512-7v1nNrlD43VY6+sYQ6efYyb3lE6QY182304PD/768ZxTjOmFd/3dQa3u/nGBUAXYdGSWOQc5N3PnS0QzUXyEIA==} + '@oxfmt/binding-linux-riscv64-musl@0.56.0': + resolution: {integrity: sha512-dyjAGW8jKRge0ik6U/dgvQG0nVpA3iBlRskQTz5qJLvQWIrySxX5jpqzPetLBNIIZ231KA82fDdi1nLTk8ENCw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] libc: [musl] - '@oxfmt/binding-linux-s390x-gnu@0.55.0': - resolution: {integrity: sha512-f4lJLUSPOgScjFl9LiflKCTocyNRwE25JmTMbN4XQdDjoZzEHjqf3wA3VESF1/csg7i8m7+EQLbrZyYDqe10UQ==} + '@oxfmt/binding-linux-s390x-gnu@0.56.0': + resolution: {integrity: sha512-60ZGH3LtfqlW8X6vcLdSFY4lvCQYINurttYBKaALnHCDVAUCYJ1LsUgS6p1XOzVlzEDx3yNUZvDF1Lvt59zoZw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] libc: [glibc] - '@oxfmt/binding-linux-x64-gnu@0.55.0': - resolution: {integrity: sha512-MihqiPziJNoWy4MqNSV+jVA1g+07iQDjZiR0vaCaDoPgFEiJpCMsxamktzLV07cEeQsSJ04vQaU4CzCQwIvtDA==} + '@oxfmt/binding-linux-x64-gnu@0.56.0': + resolution: {integrity: sha512-u1suj1tgJHK4ZqB7buCtdbNef2n8+d0lXTPJwLHNmtyK6p+DTpsaoDvmqhQrA56fgKYv4LuRxNtL8YooebKOew==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [glibc] - '@oxfmt/binding-linux-x64-musl@0.55.0': - resolution: {integrity: sha512-Yqghym7KYAVjP9MmSrNZiDeerMuoejNjo0r3ox5H3GDKk8eAfl8VyJm9i+pWCLDCTnAbcTUMMN2ZKjUYXH1v3g==} + '@oxfmt/binding-linux-x64-musl@0.56.0': + resolution: {integrity: sha512-aYGLvlQHt80y+qKEtfJY/Nm27G0125Lv+qyh9SJ4Cjc6lXnXjD+ndfhqQnbV24POpMi7rNRi0jvx/0d70FRpCQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [musl] - '@oxfmt/binding-openharmony-arm64@0.55.0': - resolution: {integrity: sha512-s5SDvVVSbyQl1V5UU3Yl12M+XLUQ3rl5SglNqgAA2K4PXUtQhyNSS00wivONPEnNo5W01rCou8WkDNyvI/RGHg==} + '@oxfmt/binding-openharmony-arm64@0.56.0': + resolution: {integrity: sha512-H/re/gO+7ysVc+kywHNuzY3C33EN9sQcZhg0kp1ZwOZl7y998ZE5mhnBiuGR/nYI0pqLL5xQfrHVUOJ/cIJsCA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] - '@oxfmt/binding-win32-arm64-msvc@0.55.0': - resolution: {integrity: sha512-7p9FB5R32tw2KyyNX3wpQrR2WHwEHvMEiBlGXxeTCaRMCVNx3UtFMAUbaQ/pRNWIrEUZmYhJ6tcUH52uPTRYjQ==} + '@oxfmt/binding-win32-arm64-msvc@0.56.0': + resolution: {integrity: sha512-6qLNXfXmtAs8jXDvYMkxk6Wec5SUJoew+ZX1uOZmqaR7ks0EJFbAohuOCELDyJMWyVlxotVG8Xf8m74Bfq0O2w==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] - '@oxfmt/binding-win32-ia32-msvc@0.55.0': - resolution: {integrity: sha512-ZYqj3fDnOT1IaVGMP5kpmkQl4F3tQIm2ZyAxvqkJYmI0xgWWak4ss4XYwv3VDfM+TWXeC9K4uQ/wW5jm/5XABA==} + '@oxfmt/binding-win32-ia32-msvc@0.56.0': + resolution: {integrity: sha512-UXEXuKphAe15bsob4AswNMArCw38XSmUIs3wk1s6e6MX9OWGW/IRWU95s1hZDiVg09STy1jHgyN2qkqbu1FT0w==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ia32] os: [win32] - '@oxfmt/binding-win32-x64-msvc@0.55.0': - resolution: {integrity: sha512-eEYT5tivGnGbPHuOHuQpi6CGLObhh0re/5jcNQHihD2GRYkTM85dyi5a19zjP8Q00t1uqAx+/QGLUGdHeqzWyg==} + '@oxfmt/binding-win32-x64-msvc@0.56.0': + resolution: {integrity: sha512-HPyNDjky+NIOuaMvHZflR+kst3YWdUOH2JUQYkf99grqZ5mEBTQM6h9iGy501Z8Xt5xMScrwHOuVCOlqDrktRw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] - '@oxlint/binding-android-arm-eabi@1.70.0': - resolution: {integrity: sha512-zFh0P4cswmRvw6nkyb89dr18rRanuaCPAsEXsFDoQY8WdaquI8Pt4NWFjaMJg6L23cy5NeN8J9cBnREbWzZhaw==} + '@oxlint/binding-android-arm-eabi@1.71.0': + resolution: {integrity: sha512-ImGmd1njEg4FEJH03jhRnveEegtO3czCtfptvaHivKAZQIYATbVFBrrzbaYMYv0oJioTnxZAZVSyV+oL7W8S2g==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [android] - '@oxlint/binding-android-arm64@1.70.0': - resolution: {integrity: sha512-qI8o4HZjeGiBrWv+pJv4lH0Yi2Gl/JSp/EumBUApezJprIKa5PS4nU0lQsQngtky8k+SplQIOjv6hwu0SSxeyg==} + '@oxlint/binding-android-arm64@1.71.0': + resolution: {integrity: sha512-4A5BEexBrwY1YFF8Kiq/lp/wQPRG79G3BWIE1FuWaM5MvmpYSd+7ZySVcKkHdwo0UDzdQGddp6pD9mpctMqLnw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [android] - '@oxlint/binding-darwin-arm64@1.70.0': - resolution: {integrity: sha512-8KjgVVHI5F9nVwHCRwwA78Ty7zNKP4Wd9OeN5PSv3iu/F/u1RVXoOCgLhWqust6HmwQG6xc8c+RCyaWENy24+w==} + '@oxlint/binding-darwin-arm64@1.71.0': + resolution: {integrity: sha512-9wJA9GJulLwS2usU3CEisI/ESDO1n1z9eyTCvApMDrAkbJ1ve0mORgTMjcWWsKxkzkeZ2N/Gpra5IQE7x8tYgQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] - '@oxlint/binding-darwin-x64@1.70.0': - resolution: {integrity: sha512-WVydssv5PSUBXFJTdNBWlmGkbNmvPGaFt/2SUT/EZRB6bq6bEOHmMlbnupZD5jmlEvi9+mZJHi8TCw15lyfSfQ==} + '@oxlint/binding-darwin-x64@1.71.0': + resolution: {integrity: sha512-PlLCjS06V0PeJMAJwzjrExw1sYNW9Gch3JtNlcwwZDXGlTYDuwHNN89zYH8LTXFfgkVtsYvs2nv0FqrzyuFDzg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] - '@oxlint/binding-freebsd-x64@1.70.0': - resolution: {integrity: sha512-hJucmUf8OlinHNb1R7fI4Fw6WsAstOz7i8nmkWQfiHoZXtbufNm+MxiDTIMk1ggh2Ro4vLzgQ+bKvRY54MZoRA==} + '@oxlint/binding-freebsd-x64@1.71.0': + resolution: {integrity: sha512-Lhil7bWre0ncxbUoDoxfS0JzpTz17BRQKW7iwoAUY8GJ66+WwJEfYPCFJ1P0WgVZR5/O/b3Q2pENlHOjeXLOGQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] - '@oxlint/binding-linux-arm-gnueabihf@1.70.0': - resolution: {integrity: sha512-1BnS7wbCYDSXwWzJJ+mc3NURoha6m6m6RT5c6vgAY3oz7C3OVXP+S0awo2mRq97arrJkVvO3qRQfyAHL+76xtQ==} + '@oxlint/binding-linux-arm-gnueabihf@1.71.0': + resolution: {integrity: sha512-Oo9/L58PYD3RC0x05d2upAPLllHytTjHQGsnC06P6Ynn7jKkp5mdImQxXdJ3+FnBaKspNpGogzgVsi6g872LiA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@oxlint/binding-linux-arm-musleabihf@1.70.0': - resolution: {integrity: sha512-yKy/UdbR55+M2yEcuiV5DCNC/gdQAjr/GioUy50QwBzSrKm8ueWADqyRLS9Xk+qjNeCYGg6A8FvUBds56ttfqg==} + '@oxlint/binding-linux-arm-musleabihf@1.71.0': + resolution: {integrity: sha512-mSHfyfgJrEbyIR29ejaeS50BdPk+GoNPlC1dckpDiUZbJAIel68sjSMdOt4WY0/gva+ECC7FNITQkxMJU+vSBw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@oxlint/binding-linux-arm64-gnu@1.70.0': - resolution: {integrity: sha512-0A5XJ4alvmqFUFP/4oYSyaO+qLto/HrKEWTSaegiVl+HOufFngK2BjYw9x4RbwBt/du5QG6l5q1zeWiJYYG5yg==} + '@oxlint/binding-linux-arm64-gnu@1.71.0': + resolution: {integrity: sha512-n9yY4M2tiy3aij4AqtlnspzpfdpeT5JQfK2/w2d8oyp5W0FRwOb1dIeX99nORNcxGr08iD9bH8N5XFz3I2iy8w==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [glibc] - '@oxlint/binding-linux-arm64-musl@1.70.0': - resolution: {integrity: sha512-JiylyurlB0CLSedNtx1gzv3FvfWPF1h/2Y3BJszPLNt5XQFlBsH5ke0Jle3iJb3uqu5m2e7A/DwzpuCAHdiU+A==} + '@oxlint/binding-linux-arm64-musl@1.71.0': + resolution: {integrity: sha512-fJZrs5sDZtTaPIOiemRQQmo82Ezy+vOGXemPc4Ok7iVVsYsFa7SlW6Z5XN819VfsqBHRm3NJ3rTdnR8+bJYJdQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [musl] - '@oxlint/binding-linux-ppc64-gnu@1.70.0': - resolution: {integrity: sha512-J8VPG7I3/HmgaU4u8pNU2kFx2+0U+vPLS1dXFxXOaR/2TQ0f8AC7DRz0SRGRI1bfphnX2hVYTTtLuhL4nYKL+Q==} + '@oxlint/binding-linux-ppc64-gnu@1.71.0': + resolution: {integrity: sha512-cwl7VKGERIy9p+G+AvZdfy/06q0aHXaTt/mMRReC751iuNYJgqKjB7NydXSS30nBT9vtr2tunciOtrR4fD6FUA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] libc: [glibc] - '@oxlint/binding-linux-riscv64-gnu@1.70.0': - resolution: {integrity: sha512-N2+4lV2KLN+oXTIIIwmWDhwkrnvqf5oX7Hw0zPjk+RuIVgiBQSOlJWF7uQoFx2siEYX0ZQ5cfSbEAHm+J3t7Wg==} + '@oxlint/binding-linux-riscv64-gnu@1.71.0': + resolution: {integrity: sha512-eZ8ieVXvzGi8jr7+ybQGPK2STw3mldfxZlgA2738iflfB/rzA69sE6m5rDRpQaxC7dpm745Enlh1Tod0QAk9Gg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] libc: [glibc] - '@oxlint/binding-linux-riscv64-musl@1.70.0': - resolution: {integrity: sha512-1e2L7cFCvx9QDzq6NPP+0tABKb5z6nWHyddWTNKprEsjO9xNrAtPowuCGpjNXxkTdsMiZ4jc8YQ5SstZd4XK6g==} + '@oxlint/binding-linux-riscv64-musl@1.71.0': + resolution: {integrity: sha512-puMDbQYe6+NXwfMusojoA7CXGn2b3utukmd23PQqc1E3XhVCwyZ+FueSMzDYeNgDV2dUfIVXAAKZBcFDeCL6sA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] libc: [musl] - '@oxlint/binding-linux-s390x-gnu@1.70.0': - resolution: {integrity: sha512-Kwu/l/8GcYibCWA9m9N5pRXMIKVSsL/YbgpLzYkqDhWTiqdRfnNJ/+nqIKRKQiFbHWsdlHEhzMwruJK+qcEruA==} + '@oxlint/binding-linux-s390x-gnu@1.71.0': + resolution: {integrity: sha512-4NJLxBs1ujISCt3L/1FcywLs73PWtJuw+piD6feK2V6h6OS6P7xu9/sWt1DTRLibe6QCzmfZzmM/2HPORoV/Lg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] libc: [glibc] - '@oxlint/binding-linux-x64-gnu@1.70.0': - resolution: {integrity: sha512-tap04CsHYOl0nSAQJfPNIuBxqEPB2HnhQqwaOXLg1jnp2XfRo8Fa814dA4QC4zpvTWXCjAAaCY1W5LOORkEQuQ==} + '@oxlint/binding-linux-x64-gnu@1.71.0': + resolution: {integrity: sha512-cFDaiR8L3430qp88tfZnvFlt3KotFhR/DlbIL0nHOMMYiG/9Wy4l+6f7t8G8pTa9bd8Lt8+M0y/qjRQ/xcB74g==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [glibc] - '@oxlint/binding-linux-x64-musl@1.70.0': - resolution: {integrity: sha512-hzJa/WgvtJpbBD9rgfy0qe+MjbxOXNUT0bfR1S6EQQzfTtBFA9xg5q8KSwRrQ2QfSS+TaP4j+4mVPQrfNc6UNg==} + '@oxlint/binding-linux-x64-musl@1.71.0': + resolution: {integrity: sha512-orfixdt76KlpNly9z0PkWBBNfwjKz+JFVLP/7wnVchlKNU9Dpt9InU/ZggeSej6fC7qwHmHNOGlhLnQXcYoGuA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [musl] - '@oxlint/binding-openharmony-arm64@1.70.0': - resolution: {integrity: sha512-xbsaNSNzVSnaJACCUYr1HQMyY/Q/Q1LkePmHG3UvZPvGCYGNxrsZp9OmtA6ick8xH47ltRRbRrPCM1YXYcyC+A==} + '@oxlint/binding-openharmony-arm64@1.71.0': + resolution: {integrity: sha512-9emQu2lAp6yhPB3XuI+++vR+l/o6JR1X+EpxwcumPdQXBWXEPAsquPGL7l158EqU8SebQMXTUa/S5zN98juyHw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] - '@oxlint/binding-win32-arm64-msvc@1.70.0': - resolution: {integrity: sha512-icAEsUI7JbW1TMRdEXV83mVAInhRVQYuuAlPpxdGwJ95chNdnCzjloRW8GglT0WvzOEZSio6fnYSk2DJ2Hv7LQ==} + '@oxlint/binding-win32-arm64-msvc@1.71.0': + resolution: {integrity: sha512-bd5kI8spYwTm3BILDtGhi73zoup5dw8MlPQNT8YB3BD5UIsjNe3K9/4ctrzQMX4SZMoK5HgzVLkLJzacEXB7fA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] - '@oxlint/binding-win32-ia32-msvc@1.70.0': - resolution: {integrity: sha512-FHMSWbVsPVs/f+Jcl04ws4JJ2wUnauyTzlpxWRG/lSO/8GpX08Fo2gQZqdA6CrRFI+zvkxl+N/KwJGWfUwYVZA==} + '@oxlint/binding-win32-ia32-msvc@1.71.0': + resolution: {integrity: sha512-W4HvOHGzVLHcrmFu+bMrJlho+/yrlX5ZNdJZqGe8MEldkQG+RHYhxxad9P4jvWAYFmIqUA5i9DQ8QsJqSU9GIw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ia32] os: [win32] - '@oxlint/binding-win32-x64-msvc@1.70.0': - resolution: {integrity: sha512-ptOlKwCz7n4AKs5VweMqG6DAg677FmKOK+vBkkL9DMNgFATIQ+upqUYBTOEwRQyRAx1ncGlPlXleV2hIcm3z4g==} + '@oxlint/binding-win32-x64-msvc@1.71.0': + resolution: {integrity: sha512-D2kyEIPHk/G/wiZLnwTVC/sVst+T/lKldVOjAFpgTIBUAOlry72e5OiapDbDBF4LfJLkN5ypJb/8Eu6yJzkveQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] @@ -6245,8 +6380,8 @@ packages: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} - '@playwright/test@1.61.0': - resolution: {integrity: sha512-cKA5B6lpFEMyMGjxF54QihfYpB4FkEGH+qZhtArDEG+wezQAJY8Pq6C7T1SjWz+FFzt3TbyoXBQYk/0292TdJA==} + '@playwright/test@1.61.1': + resolution: {integrity: sha512-8nKv6+0RJSL9FE4jYOEGXnPeM/Hg12qZpmqzZjRh3qM0Y7c3z1mrOTfFLids72RDQYVh9WpLEfR5WdpNX4fkig==} engines: {node: '>=18'} hasBin: true @@ -6265,11 +6400,11 @@ packages: '@polka/url@1.0.0-next.25': resolution: {integrity: sha512-j7P6Rgr3mmtdkeDGTe0E/aYyWEWVtc5yFXtHCRHs28/jptDEWfaVOc5T7cblqy1XKPPfCxJc/8DwQ5YgLOZOVQ==} - '@posthog/core@1.35.3': - resolution: {integrity: sha512-EsGPbSLl39Jgo2KZ+kI9UAxFnh5nddaN5bNm2rXvUwF+vGmam9eN1EXeNbxhRU7ulEeIiGdm7XjoU7pzavkgIQ==} + '@posthog/core@1.38.0': + resolution: {integrity: sha512-QLrJh0hMVpEJXHNyiJR9YhvIO5tzLDedw4UHdvB+ub4fXHMtHCA8H44qgl11HWR3ajpjxtJ8hs3HyOGVF3Zc1w==} - '@posthog/types@1.390.2': - resolution: {integrity: sha512-WcfKz2GNn2vfDX8vXmJYbKxegPxVWHuDQ/pHdAn0HoZDXDFnEp/+x3qBQA+fEvtbPjjtjgAt2wIgJMlM7asx7g==} + '@posthog/types@1.391.1': + resolution: {integrity: sha512-ASwd7Nf4pViqdYRYaNRyPYRVKWa1CcHUAUWR0XeQJLGdNnsWACBwe0sSieb/cHnKsRXjRwO/23KIY83lm/Ccpw==} '@protobufjs/aspromise@1.1.2': resolution: {integrity: sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==} @@ -6379,97 +6514,97 @@ packages: '@codemirror/state': ^6.0.0 '@codemirror/view': ^6.0.0 - '@rolldown/binding-android-arm64@1.0.3': - resolution: {integrity: sha512-454rs7jHngixp/NMxd5srYD57OnzSlZ/eFTETjORQHLwJG1lRtmNOJcBerZlfu4GjKqeq8aCCIQrMdHyhI51Hw==} + '@rolldown/binding-android-arm64@1.1.3': + resolution: {integrity: sha512-DT6Z3PhvioeHMvxo+xHc3KtqggrI7CCTXCmC2h/5zUlp5jVitv7XEy+9q5/7v8IolhlioawpMo8Kg0EEBy7J0g==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [android] - '@rolldown/binding-darwin-arm64@1.0.3': - resolution: {integrity: sha512-PcAhP+ynjURNyy8SKGl5DQP94aGuB/7JrXJb/t7P+hanXvQVMWzUvRRhBAcg/lNRadBhoUPqSoP4xw5tR/KBEA==} + '@rolldown/binding-darwin-arm64@1.1.3': + resolution: {integrity: sha512-0NwgwsjM7LrsuVnXMK3koTpagBNOhloc/BNjKqZjv4V5zI5r13qx69uVhRx+o5Z0yy4Hzq+lpy7TAgUG/ocvrw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] - '@rolldown/binding-darwin-x64@1.0.3': - resolution: {integrity: sha512-9YpfeUvSE2RS7wysJ81uOZkXJz7f7Q55H2Gvp3VEw/EsahqDtrphrZ0EwDLK5vvKOzaCrBsjF8JmnMLcUt78Gg==} + '@rolldown/binding-darwin-x64@1.1.3': + resolution: {integrity: sha512-YtiBp4disu6V560loT6PjMdiRaWmVvDNrUunAalbiFx2ggeJwxdAsgZMcoGP17uyAsTwAj5V1niksxlHnVQ1Sw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] - '@rolldown/binding-freebsd-x64@1.0.3': - resolution: {integrity: sha512-yB1IlAsSNHncV6SCTL27/MVGR5htvQsoGxIv5KMGXALp+Ll1wYsn+x98M9MW7qa+NdSbvrrY7ANI4wLJ0n1e6g==} + '@rolldown/binding-freebsd-x64@1.1.3': + resolution: {integrity: sha512-yD3EkEdXk2LypPxnf/kSZHirarsI8gcPzc62SukhR9VJTyvV+F9Q/GxWNuCojc7sXyuVC4DxRGhdDK4X8VSsbw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] - '@rolldown/binding-linux-arm-gnueabihf@1.0.3': - resolution: {integrity: sha512-Yi30IVAAfLUCy2MseFjbB1jAMDl1VMCAas5StnYp8da9+CKvMd2H2cbEjWcw5NPaPqzvYkVIaF1nNUG+b7u/sw==} + '@rolldown/binding-linux-arm-gnueabihf@1.1.3': + resolution: {integrity: sha512-c+8vieQbsD7HNAHKIA34w0GJ9FedFFuJGD+7E6vz7Q3uqAIugL5p45fhlsj4UaAsHpcmlqugBWMhA0/j7o0sIg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@rolldown/binding-linux-arm64-gnu@1.0.3': - resolution: {integrity: sha512-jsO7R8To+AdlYgUmN5sHSCZbfhtMBkO0WUx8iORQnPcMMdgr7qM2DQmMwgabs3GhNztdmoKkMKQFHD6DTMCIQw==} + '@rolldown/binding-linux-arm64-gnu@1.1.3': + resolution: {integrity: sha512-50jD0uUwLvur7Zz9LHz17kaAdTPjn5wN93hEgjvmYFRZwiR7ZJYovTd5ipyWJDAnXKvZ+wgc+/Ika6dwSF5OcA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [glibc] - '@rolldown/binding-linux-arm64-musl@1.0.3': - resolution: {integrity: sha512-VWkUHwWriDciit80wleYwKILoR/KMvxh/IdwS/paX+ZgpuRpCrKLUdadJbc0NpBEiyhpYawsJ73j9aCvOH+f7Q==} + '@rolldown/binding-linux-arm64-musl@1.1.3': + resolution: {integrity: sha512-BO9+oPL8K9poZJBfYPsXNtYjPE5uM3qeehT3aFcW4LITOl+iSqhp0abzjR2nWBUNjIZeKXjAEWBZ64WjNoHd6w==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [musl] - '@rolldown/binding-linux-ppc64-gnu@1.0.3': - resolution: {integrity: sha512-5f1laC0SlIR0yDbFCd8acUhvJIag6N3zC5P7oUPN6wX0aOma+uKJ0wBDH5aq7I1PVI2ttTlhJwzwRIBnLiSGEg==} + '@rolldown/binding-linux-ppc64-gnu@1.1.3': + resolution: {integrity: sha512-f3VpLB1vQ0Eo6ecr/6cekLnvYMFF4YBFoVGkfkvPLq1bAkbAwHYQPZKoAmG6OJyTcxxoC+AvezGx/S1obNC0Mw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] libc: [glibc] - '@rolldown/binding-linux-s390x-gnu@1.0.3': - resolution: {integrity: sha512-Iq4ko0r4XsgbrF/LunNgHtAGLRRVE2kXonAXQ/MV0mC6jQpMOhW1SvtZja2EhC/kd05++bP78dsqBeIQyYJ6Yg==} + '@rolldown/binding-linux-s390x-gnu@1.1.3': + resolution: {integrity: sha512-AmurZ26Pqx/RI9N1gzEOCklkKXl927yjfXWUUS0O7Puh8ARM/Ob8qfrD3qnWksScdw6cSrW5PSHE9DyLu7+PtA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] libc: [glibc] - '@rolldown/binding-linux-x64-gnu@1.0.3': - resolution: {integrity: sha512-B8m6tD5+/N5FeNQFbKlLA/2yVq9ycQP1SeedyEYYKWBNR3ZQbkvIUcNnDNM03lO1l5F2roiiFJGgvoLLyZXtSg==} + '@rolldown/binding-linux-x64-gnu@1.1.3': + resolution: {integrity: sha512-JJpqs8bRGITDOdbkNKnlojzBabbOHrqjSvDr0IVsZObE1lBcPjxItUEY9eWIDbxaJ3cGrXPWGfGkIxFijg/URg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [glibc] - '@rolldown/binding-linux-x64-musl@1.0.3': - resolution: {integrity: sha512-pSdpdUJHkuCxun9LE7jvgUB9qsRgaiyNNCX7m/AvHTcq67AiT/Yhoxvw5zPfhrM8k/BfP8ce/hMOpthKDpEUow==} + '@rolldown/binding-linux-x64-musl@1.1.3': + resolution: {integrity: sha512-rSJcdjPxzA/by/6/rYs+v+bXU7UjvnbUWz8MJb6kh6+knqB1dCrtHg0uu7C/4haqJvqdkYHQ5IGn+tCH9GLW/g==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [musl] - '@rolldown/binding-openharmony-arm64@1.0.3': - resolution: {integrity: sha512-OXXS3RKJgX2uLwM+gYyuH5omcH8fL1LJs96pZGgtetVCahON57+d4SJHzTgZiOjxgGkSnpXpOsWuPDGAKAigEg==} + '@rolldown/binding-openharmony-arm64@1.1.3': + resolution: {integrity: sha512-hQ3/PYkDJICgevvyNcVrihVeqq7k1Pp3VZ9lY+dauAYUJKO+auqApvANhvR1An9BhmqYKvW2Mu1F9u4DXSMLxQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] - '@rolldown/binding-wasm32-wasi@1.0.3': - resolution: {integrity: sha512-JTtb8BWFynicNSoPrehsCzBtOKjZ6jhMiPFEmOiuXg1Fl8dn2KHQob+GuPSGR0dryQa1PQJbzjF3dqO/whhjLg==} + '@rolldown/binding-wasm32-wasi@1.1.3': + resolution: {integrity: sha512-Elcv/BtML9lXrV6JuKITc/grN2kYV9gjsQpW8Jfw4ioK0TOkjBjye0nnyqQNy9STNaI20lXNaQBRrD5gSgR0Yg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [wasm32] - '@rolldown/binding-win32-arm64-msvc@1.0.3': - resolution: {integrity: sha512-gEdFFEN70A/jxb2svrWsN3aDL7OUtmvlOy+6fa2jxG8K0wQ1ZbdeLGnidov6Yu5/733dI5ySfzFlQ/cb0bSz1g==} + '@rolldown/binding-win32-arm64-msvc@1.1.3': + resolution: {integrity: sha512-2DrEfhluH9yhiaFApmsjsjwrSYbNcY1oFTzYSP1a535jDbV98zCFanA/96TBUd0iDFcxGmw9QRExwGCXz3U+/g==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] - '@rolldown/binding-win32-x64-msvc@1.0.3': - resolution: {integrity: sha512-eXB7CHuaQdqmJcc3koCNtNPmT/bj2gc999kUFgBxG8Ac0NdgXc4rkCHhqrgrhN3zddvvvrgzj1e90SuSfmyIXA==} + '@rolldown/binding-win32-x64-msvc@1.1.3': + resolution: {integrity: sha512-OL4OMk7UPXOeVGGd3qo5zJyPIljf4AFgk5QAkPPS+OoLuOOozhuaQGC18MxVTnw/06q93gShAJzlwnSCY9YtqA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] @@ -6554,46 +6689,50 @@ packages: '@rvf/set-get@7.0.1': resolution: {integrity: sha512-GkTSn9K1GrTYoTUqlUs36k6nJnzjQaFBTTEIqUYmzBcsGsoJM8xG7EAx2WLHWAA4QzFjcwWUSHQ3vM3Fbw50Tg==} - '@scalar/agent-chat@0.12.11': - resolution: {integrity: sha512-JX1rFryU6kHHcMzzewr9o4Qe0st/27A8AFvka7v0OWsIoNJHcO9aA5gitedci6eZJNsNSBj6bXpH5VrCU1jAfA==} + '@scalar/agent-chat@0.12.13': + resolution: {integrity: sha512-JPjwnOqIRb/TF+0bVNL7rg9SSjce3p0G++GF8uNvoGthixWuQxzo2pems4npZwe6Aw57icRTsZ3GBFgs9VmJvw==} engines: {node: '>=22'} - '@scalar/api-client@3.10.4': - resolution: {integrity: sha512-TwmbM6hnzPs1vZUzucczNkPTzxTOtV+gu+TIq8/yEZBYF7GC6+6YerxPrGYjTgifp2UbIYf9GlSvmZv8RzSkIQ==} + '@scalar/api-client@3.12.0': + resolution: {integrity: sha512-NBZTMuqG9Qu+C0bUtMJPQEbGBLC5skXj55A/oDSzPwvTmWKV6I9Xc4wcrqVZMMidx315uC/WWyrB9WCUMgNi3Q==} engines: {node: '>=22'} - '@scalar/api-reference-react@0.9.47': - resolution: {integrity: sha512-2vT7WCgU2AY2wrB+Xit09b76h5uQsQ/zP68LyTKSPYG8FIYeVsuGkrpIJGbtY/f57d3rQCqrbrZjLb30VzuWKw==} + '@scalar/api-reference-react@0.9.49': + resolution: {integrity: sha512-dtOrWZ++rFfsihNZKZ2pJ722bSkI3qp5CZKdiOY98PoJUWwPoSxT7C3NAn4NX59jQ0kuLhWDEELDpAcA779cvA==} engines: {node: '>=22'} peerDependencies: react: ^18.0.0 || ^19.0.0 - '@scalar/api-reference@1.60.0': - resolution: {integrity: sha512-godMRo3E/m6uZtm0zVfDa5xCg1IGD+eEt4uVWzR0v62mbm2O70ZWY9l+ondg8CrPQFsCdtWpWW+nYdzvp1tWTw==} + '@scalar/api-reference@1.62.0': + resolution: {integrity: sha512-QYPqMPfy4vyVLysHeSFZsD+717ua/gHIhYYu7UjqPzJUOVh2R+/9hINKKzvoP13lQWCQTmuzs2VEl8rQOIfDjw==} engines: {node: '>=22'} - '@scalar/code-highlight@0.3.6': - resolution: {integrity: sha512-G/EtLF6a3IFGfIv5b7Uj5v/yXFY9+ktViVkfAkSoWJdHNBNk0S/Rb2OgRq24kp0D2TDXhLYF3E9OSHzquS7WqQ==} + '@scalar/blocks@0.1.0': + resolution: {integrity: sha512-oFnIa6dAkCt0N5yw5aqCABbP7r7YsI7ERNrGtlTDdrCteDRatMJg9y03LT+9Vhf2/K3VHWrdCZMruUdfU1uPzQ==} engines: {node: '>=22'} - '@scalar/components@0.27.2': - resolution: {integrity: sha512-cwIf042ehbD4dZWUIIlXZp0VWvaH7jznj/gpuLEqtuEDla5MrhTWYWj20aufpTpCeGexf1mqgouaWHUZ7lYByQ==} + '@scalar/code-highlight@0.4.0': + resolution: {integrity: sha512-ajUQ9oq5MwVHrXGze0SZVatgSXP/WbN4qgE0usYDFt1XuJ+O56TaBsCWb043r3Y9ZnHSnK9GHreVaLOxXsbf1A==} engines: {node: '>=22'} - '@scalar/helpers@0.8.2': - resolution: {integrity: sha512-qNbqUjSB3S4Gr4A0oANcm5G1Ip+EqBxICYKhe9YzmnaBpbmW6shxqpiivApTvvuDf+uIhR3uMwWyVQbYcGLsxA==} + '@scalar/components@0.27.4': + resolution: {integrity: sha512-1sbsMYGJcWiQ+5GouI1hHCn1VrytHPhzhHhCAZTXxi1Tw4SzsPSQuACARehXca+w2p7RK9ayGRYQ+K5bX18SaQ==} + engines: {node: '>=22'} + + '@scalar/helpers@0.9.0': + resolution: {integrity: sha512-M34CLRCttqC1bXthI/QSzQj0s5C6nrU2PFWf/vOT3RpycbiGDGQbqR+5RfFzpOIQvRqbHfNdcRbeiZBw+vCbkQ==} engines: {node: '>=22'} '@scalar/icons@0.7.3': resolution: {integrity: sha512-5uSUvumj6yJEAZT7/MKpgrkNl76waDXUpu0kUBBFJ83GhZirBIK+Z9SktShEvJT0+rk/j9Zaer3BHoEhYwAEBQ==} engines: {node: '>=22'} - '@scalar/json-magic@0.12.16': - resolution: {integrity: sha512-w8cDbZhHCzmIblWx92IVWoAXsbI4Fz3m++jiBANTSO1hgphF6UqEPQiCt3wnMPaxaanjMQxjS/iBk1UGXR2EGA==} + '@scalar/json-magic@0.12.17': + resolution: {integrity: sha512-Vw2nrUDIjhvMP6vxFtkiiFlabJ6SyTtfn1BsOxgnr1hIB+/rkngMguiDzl5em21VjyfFGIoADia+QWKM2hdcdA==} engines: {node: '>=22'} - '@scalar/oas-utils@0.18.5': - resolution: {integrity: sha512-GLL0DPEaKkIrDmkwY83RDnCKQRuzw1u6MjGlEX5iVs/LDjQRvWnkAz9A5iUrH/YHr+ike5NIl3/WzaEWiz6x6A==} + '@scalar/oas-utils@0.19.1': + resolution: {integrity: sha512-FflVWiFDm0s4a+FqKJ0uYsVhDzbAtVyXSyUWsGe9BGPPQSXYYvVEVp9H2ir4xueaw4TZb1cDEZbteHLx7h7vqg==} engines: {node: '>=22'} '@scalar/openapi-types@0.9.1': @@ -6604,27 +6743,27 @@ packages: resolution: {integrity: sha512-D5b0rGLLZgmkO9mdW2j/ND1KBlH1u3RCpr87HPxv9P9ZSr6PtM5iLqFOJq0ACiaHjY2mikCrxgDmnUEhTzRpHQ==} engines: {node: '>=22'} - '@scalar/schemas@0.5.0': - resolution: {integrity: sha512-aV0PWqFpQft9a6d4Z6HOTHKDaA+K4AquxSPfBDI1c5f6lMe5l3zViww/iBQA/Yw+V1+Y2iwSVzhuxaktC1UiMg==} + '@scalar/schemas@0.7.0': + resolution: {integrity: sha512-Roj0e7S29yTbGojwdx/b8C1H5arVN4h/VSPrtQ5vb9NT2ZAbrfPQVmpQYrF845rKOWX7kSzIvyBwrKNEMIAkxA==} engines: {node: '>=22'} - '@scalar/sidebar@0.9.23': - resolution: {integrity: sha512-2qC5vh1BGhnARF9/psKr5jPbwWyg4RIfflNmh7q/jHxNyeCrHGr1yyUiV8TsWmURR5ZNMiyUPzHaio4hdAcZNw==} + '@scalar/sidebar@0.9.25': + resolution: {integrity: sha512-RoMKTvCo801kj5J59Myqhc6YYtvjlXGNvP7qbj/TY2hfQ4XS5dH5S7t8JKJje+78MLC2Xg9OsEmCjv+gb8ta4w==} engines: {node: '>=22'} - '@scalar/snippetz@0.9.17': - resolution: {integrity: sha512-BC8ai3E9O5TbJwdGTounh9T6BVSTuYJ/BObZ6wfmXxMK/AL7bDfYED5xBqqzucAGpjVUaN0u796w5EHB82ryrQ==} + '@scalar/snippetz@0.9.19': + resolution: {integrity: sha512-RVTZQqUxOiBhAuPywDmser1xOatVj7HCNmg+qpW8172MsMpx00lRGZ940WVAhjHgKglTkwHPaTdVX7gQeDrFUQ==} engines: {node: '>=22'} - '@scalar/themes@0.16.0': - resolution: {integrity: sha512-xcd5cdouNKGGI+D3v4SVm5WVLjiLDPT0jBiZrNA6m/h2yDaIeFoXMDrH61aj962Y+P7KR33/2b0xLHgws5Os8Q==} + '@scalar/themes@0.16.2': + resolution: {integrity: sha512-4mAn7z2W5/ASi1OF06dqf04xjxTHnMzESC2E+qVMukJsEsV4kDlYSIfm/g5hwWxhqWsBMjorF9Dtlqd0ycJ92g==} engines: {node: '>=22'} '@scalar/typebox@0.1.3': resolution: {integrity: sha512-lU055AUccECZMIfGA0z/C1StYmboAYIPJLDFBzOO81yXBi35Pxdq+I4fWX6iUZ8qcoHneiLGk9jAUM1rA93iEg==} - '@scalar/types@0.14.0': - resolution: {integrity: sha512-aS1FvHbKhBC51mWqXmhkPe6lmhCd9+u//DQ1YTiCWOz4/fXAdWfwHvW9UZ7AzzcGwGiCo9diE81xV+UvSI1qIg==} + '@scalar/types@0.16.0': + resolution: {integrity: sha512-26OSrvZjKWZ7F236wWmJajBGDVFJuvXFJqKPFqbt/PxlgZKXQXfJsZorASRQmdNogT576nxYalQ7oaYWEnQwfw==} engines: {node: '>=22'} '@scalar/use-codemirror@0.14.12': @@ -6643,8 +6782,8 @@ packages: resolution: {integrity: sha512-tpmmG+/xRE2Kn9RpflU3AIyZv08v10+E1ZrJCx7z6+/91zHVxy0M73kC1LT4/8PbYNt85ywyC8+n+D99JdMcGA==} engines: {node: '>=20'} - '@scalar/workspace-store@0.54.4': - resolution: {integrity: sha512-82ffXzHc/TDR6mr9BSzzRkxOaVG8mHQlWLKnPiv8GiFNETgBTQKv9nELEZQpLcG02cJVgVytl+X/TjgZHRDKAw==} + '@scalar/workspace-store@0.55.0': + resolution: {integrity: sha512-4EkaLfONW8qu2vigJopbjg9ahE/Z9Q+IInnVoUTZfxLp4rb2Z3ztx8LmQOym8DPpv62BvuMtJcNdQOnQ6tVLHA==} engines: {node: '>=22'} '@schummar/icu-type-parser@1.21.5': @@ -7129,6 +7268,9 @@ packages: resolution: {integrity: sha512-Wo1iKt2b9OT7d+YGhvEPD3DXvPv2etTusIMhMUoG7fbhmxcXCtIjJDEygy91Y2JFlwGyjqiBPRozme7UD8hoqg==} engines: {node: '>=12'} + '@tanstack/query-async-storage-persister@5.101.0': + resolution: {integrity: sha512-8Y8pwMVPh4kqU9m+MhbDYBuub+i5T2M9P/0+Ae3KccX1BwMIp6AdFAx3zDgLoZcEVI830mbJYf0sjAgsawCWVw==} + '@tanstack/query-core@5.101.0': resolution: {integrity: sha512-cQetA74EB+seWySv1TTKr828TnP0u39m6LykwDXIo84SNortpDkp30TMEjkqtYCNP9c40uT/iwl6MLiufEt0Ow==} @@ -7151,6 +7293,12 @@ packages: next: ^13 || ^14 || ^15 || ^16 react: ^18 || ^19 + '@tanstack/react-query-persist-client@5.101.0': + resolution: {integrity: sha512-AUcdBgz8V6sM9axzdqkVmWjYSOETkhr6yAZSBnEFyZT2jo6vkFq3UrpRuxGs6fmhKMWv8FA+ZJGcbaKPaoAElQ==} + peerDependencies: + '@tanstack/react-query': ^5.101.0 + react: ^18 || ^19 + '@tanstack/react-query@5.101.0': resolution: {integrity: sha512-rLlJXSpkqfizLWgkR5+eLeIk0MvTx/meEIR7LRjxic+qxiQP8zVjq7BqQkiCMNLQBlLfuOLqqr6KO5GtrDlmSg==} peerDependencies: @@ -7456,42 +7604,42 @@ packages: '@tsconfig/docusaurus@2.0.9': resolution: {integrity: sha512-0jVxZCgy2v7TnJrW9kVNikNwJHeiWb68Zhiiw2vHw4tzBFSP5vS2zn3O5EY2oPEVz5dXZRkK7MnWG3Ay5q0mIg==} - '@turbo/darwin-64@2.9.18': - resolution: {integrity: sha512-9f27peFu16ur8c0v9nUFUEyBnbKuuFsUTjHFWfmwGfzySBXbHwzU44QhZon6Mznz0cHsIr3984NQj/bVrnGSRw==} + '@turbo/darwin-64@2.10.0': + resolution: {integrity: sha512-EwvHThXzpY0KGd1/NAmuewI5D+aVa3Rl/OlxE36yfjUKb/+ySrfJrSlEFt8aD1OXwnnaHnQnPKHFndor0Zxlsg==} cpu: [x64] os: [darwin] - '@turbo/darwin-arm64@2.9.18': - resolution: {integrity: sha512-9A6TMRq/Ib+QnbhLlgkhOm+624wO4pzSQ/yQviQfWHOlFvaYxdnIAYmu2H6TS6y7kSVL0DvzNe04NbESTOzFVQ==} + '@turbo/darwin-arm64@2.10.0': + resolution: {integrity: sha512-9d2fTyyG0lf5Wq1bwJA9qUaeecViMkLcdctWaMMmCkxZ/JqypmqOwK3W6vmejeKVgkr06gSoiX8bD+xN5Jpxcg==} cpu: [arm64] os: [darwin] - '@turbo/gen@2.9.18': - resolution: {integrity: sha512-9Ry3V2eqFANYI7A5dyjehq2EOuLTY30XQSg4aDR7F3cJOuiP/Ad2KXwrxD3AnwNDkuSDVbJjlbES7yfJ0y7dhw==} + '@turbo/gen@2.10.0': + resolution: {integrity: sha512-QrnFiSKpKjijnQhde4VgEsg+WA8dQRc6EzO4iLy1+n7R8QZ3BCeVR7NePVOhhYcewoD8GZHnSPwrzu9cOvTdOA==} hasBin: true - '@turbo/linux-64@2.9.18': - resolution: {integrity: sha512-zCdIDtz69AnbYh913elJRRoF3QY5aa2HNnf+4rAkc7bQ+tWujiDkCNV7stazOUPggaDvhKIf2Z87qHftTeXSkw==} + '@turbo/linux-64@2.10.0': + resolution: {integrity: sha512-sZBtjMuufitanjzi6UssoUpJMnnPlLMcdcJj3m3ptNsSq31Xh7MnjhwA5nWvLDTfEFg8GPcbYFXMo8vSdKRfqQ==} cpu: [x64] os: [linux] - '@turbo/linux-arm64@2.9.18': - resolution: {integrity: sha512-Va1kXI04naMgYwqv/5Dfa36dTDx8015U7oaQAjrXa45ua9OoFjSV4OmvkML4EmXvUclQHCiBRbY8bvd0jV7eAg==} + '@turbo/linux-arm64@2.10.0': + resolution: {integrity: sha512-vkq/Z8R+1DQ+kifWFa810IjRy2NNBVvha3cg9sWA3nFh6nnGrHSMnnJKrzH7c/No9kq4Jb55Ru44YKsCSBgrKg==} cpu: [arm64] os: [linux] - '@turbo/windows-64@2.9.18': - resolution: {integrity: sha512-m0kDhZANxSNz9ck1ybogFscHabriAsp4eDFNrN/1H5WrgTF7b3VlcPZnhuO3v2+E2KnCbeAc+UUT10BZZHdDKw==} + '@turbo/windows-64@2.10.0': + resolution: {integrity: sha512-CRUEguLWxFQHptYZS7HjPhNhAFawfea07iR+xAQ5e4klgLrPCMdexBkXwSCwOxqTFknJ7RZFN3gOaADsw+Gttg==} cpu: [x64] os: [win32] - '@turbo/windows-arm64@2.9.18': - resolution: {integrity: sha512-nUdR8WqoomUys9iIQmG45TMiizJ+5BV8egSeLLZba/AWblyp3fVBcIH1kSE58OtK4g2YzbMJEth6Ttv9w5rqMA==} + '@turbo/windows-arm64@2.10.0': + resolution: {integrity: sha512-dVHGaf9F8twzgibcBqKoADT/LLqf9++jDb+hq/LPWWaOmRpp4M+/pVOm7vy4z9D++xg8eaxWLT0+wQxFwhYu9A==} cpu: [arm64] os: [win32] - '@tybys/wasm-util@0.10.2': - resolution: {integrity: sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==} + '@tybys/wasm-util@0.10.3': + resolution: {integrity: sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==} '@types/adm-zip@0.5.8': resolution: {integrity: sha512-RVVH7QvZYbN+ihqZ4kX/dMiowf6o+Jk1fNwiSdx0NahBJLU787zkULhGhJM8mf/obmLGmgdMM0bXsQTmyfbR7Q==} @@ -7884,8 +8032,8 @@ packages: '@videojs/xhr@2.7.0': resolution: {integrity: sha512-giab+EVRanChIupZK7gXjHy90y3nncA2phIOyG3Ne5fvpiMJzvqYwiTOnEVW2S4CoYcuKJkomat7bMXA/UoUZQ==} - '@vitejs/plugin-react@6.0.2': - resolution: {integrity: sha512-DlSMqo4WhThw4vB8Mpn0Woe9J+Jfq1geJ61AKW0QEgLzGMNwtIMdxbDUzLxcun8W7NbJO0e2Jg/Nxm3cCSVzzg==} + '@vitejs/plugin-react@6.0.3': + resolution: {integrity: sha512-vmFvco5/QuC2f9Oj+wTk0+9XeDFkHxSamwZKYc7MxYwKICfvUvlMhqKI0VuICPltGqh1neqBKDvO4kes1ya8vg==} engines: {node: ^20.19.0 || >=22.12.0} peerDependencies: '@rolldown/plugin-babel': ^0.1.7 || ^0.2.0 @@ -7981,7 +8129,7 @@ packages: resolution: {integrity: sha512-SDobKBbPIOe0cVL7QxMzGkuUGHvWTdihi9zOrrWaWUgFKe15cwEcwfWmgrcNzjT6kHnNmWuTajPHoIzUjYNYYQ==} peerDependencies: async-validator: ^4 - axios: '>=1.18.0' + axios: '>=1.18.1' change-case: ^5 drauu: ^0.4 focus-trap: ^7 @@ -9525,6 +9673,10 @@ packages: resolution: {integrity: sha512-C52mvJ+7lcyhWNfrzVfFsbTrBfy/ezE9FGEYLpu17FUeBcCkxERk9nN7uDl/478ynDiQ4U+5DbQC2vENHkVEtQ==} engines: {node: '>= 14.17'} + dockerode@5.0.1: + resolution: {integrity: sha512-avsq/xk4YPIrn0CgleX5bjT9Y8IT1p9PxrNQ++RBQ2WEyFfHCTDsT9kmyxz+H/axnjAwg8wJWEIuPGOUuNupiA==} + engines: {node: '>= 14.17'} + docusaurus-plugin-image-zoom@3.0.1: resolution: {integrity: sha512-mQrqA99VpoMQJNbi02qkWAMVNC4+kwc6zLLMNzraHAJlwn+HrlUmZSEDcTwgn+H4herYNxHKxveE2WsYy73eGw==} peerDependencies: @@ -9840,6 +9992,11 @@ packages: engines: {node: '>=18'} hasBin: true + esbuild@0.28.1: + resolution: {integrity: sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==} + engines: {node: '>=18'} + hasBin: true + escalade@3.2.0: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} @@ -11850,14 +12007,10 @@ packages: nanoid@3.3.12: resolution: {integrity: sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} - - nanoid@5.1.11: - resolution: {integrity: sha512-v+KEsUv2ps74PaSKv0gHTxTCgMXOIfBEbaqa6w6ISIGC7ZsvHN4N9oJ8d4cmf0n5oTzQz2SLmThbQWhjd/8eKg==} - engines: {node: ^18 || >=20} hasBin: true - nanoid@5.1.15: - resolution: {integrity: sha512-kBg3RpGtIe+RpTbyXwoI6pk5yD7KUiI3sygUqgeBMRst42KmhB4RZC7eiO9Wa1HIpaCCtpE2DJ6OI4Wi5ebwFw==} + nanoid@5.1.16: + resolution: {integrity: sha512-kVrnsrJqMR8+oLJnGEmSWw9BivK5mt7H3FZatVRjrc5wGqFYuBxX1yG7+A7Gi5AefkX6t/oCkizcQgpu0cY1dQ==} engines: {node: ^18 || >=20} hasBin: true @@ -12182,8 +12335,8 @@ packages: outvariant@1.4.3: resolution: {integrity: sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA==} - oxfmt@0.55.0: - resolution: {integrity: sha512-jSj2wCTakwgPMxkfiVZX0jf+nX+Nz6xlyAZjqNE0qXTFdCBPYlP6JAN+ODjmealw7DXBjOzYbdsqwBMAZnPZ6A==} + oxfmt@0.56.0: + resolution: {integrity: sha512-9Dv0wV3zKiyvhjD7bRKaInKmHQ1sPx3RGOjQkGFJbbdQ16576yf8qhMSO9Q9cvHcs+1NpBsRTkuDDYFFPTJ6gw==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: @@ -12195,8 +12348,8 @@ packages: vite-plus: optional: true - oxlint@1.70.0: - resolution: {integrity: sha512-D6JgHtzkhRwvEC+A0Nw5AEc5bk8x5i1pHzvZIEf/a0C4hOzmAACNGtkDGPyFaxxX3ZVGxCPeig3P3rMM8XU3/g==} + oxlint@1.71.0: + resolution: {integrity: sha512-U1m1X+C0vDj7DC1e13IoZULzEcPczE7UOMTs8VlZGHUEIUaSTZKo5qkPsQEfzpgnQ29Pea/w3Xntk62UCecxZw==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: @@ -12449,10 +12602,6 @@ packages: picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} - picomatch-browser@2.2.6: - resolution: {integrity: sha512-0ypsOQt9D4e3hziV8O4elD9uN0z/jtUEfxVRtNaAAtXIyUx9m/SzlO020i8YNL2aL/E6blOvvHQcin6HZlFy/w==} - engines: {node: '>=8.6'} - picomatch@2.3.1: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} engines: {node: '>=8.6'} @@ -12495,13 +12644,13 @@ packages: resolution: {integrity: sha512-emEcLuomt2j03vxD54giVB4SxTjnsqkU692xZOZXHDVoYyypEm+b3jpiTcc+Cf+myooc+/Ly0z01jqeNHVgJGw==} engines: {node: '>=16.0.0'} - playwright-core@1.61.0: - resolution: {integrity: sha512-caX7TrY3Ml6egyDX0WUcTHDxodl/b51y5wJOdCEA36QviK/s2g081hvmGs8eaE3DWb6NYZQ6BjO/QkNRPenoPA==} + playwright-core@1.61.1: + resolution: {integrity: sha512-h7Qlt6m4REp25qvIdvbDtVmD4LqVXfpRxhORv9L0jzETM05p4fuPJ3dKyuSXQxDSbXnmS79HAgi9589lGSpLkg==} engines: {node: '>=18'} hasBin: true - playwright@1.61.0: - resolution: {integrity: sha512-Z+7BeeqQPRRzklHsVFP4KTGIyMxKUmfeRA4WisM6G3/XW6nwGeX6fX9qYaDa+CiUqpOkb2f6X3nar05R3kSuJQ==} + playwright@1.61.1: + resolution: {integrity: sha512-DWnY5o3YbLWK4GovuAVwpqL+1VwGNdUGrRr++8j8PtQQzvAVZUIMjKQ90fY689sEJZJBbZVw1rXaOKSTitkzPQ==} engines: {node: '>=18'} hasBin: true @@ -12938,6 +13087,10 @@ packages: resolution: {integrity: sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==} engines: {node: ^10 || ^12 || >=14} + postcss@8.5.16: + resolution: {integrity: sha512-vuwillviilfKZsg0VGj5R/YwwcHx4SLsIOI/7K6mQkWx+l5cUHTjj5g0AasTBcyXsbfTgrwsUNmVUb5xVwyPwg==} + engines: {node: ^10 || ^12 || >=14} + postgres-array@2.0.0: resolution: {integrity: sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==} engines: {node: '>=4'} @@ -12958,8 +13111,8 @@ packages: resolution: {integrity: sha512-Ray65LYEJrMMqDtsBUBXunEVP/g4wtATvq/xz9rchUoLy/9mSkkFgUko/8DVtGxgiP3vivpFMgfb9HpCuDrBHg==} engines: {node: '>=10.15.1'} - posthog-js@1.391.2: - resolution: {integrity: sha512-q0DZN6ljchSnAFJIXf+sQFTPlsLjTlRa+TvrL+QRb6413BGtib/MNiQy1bnwLKt8KR+f6xJYvkqdLyty9s4Aww==} + posthog-js@1.395.0: + resolution: {integrity: sha512-5iTb00CGt2eQUUiBQysQiX89RAbCN6wK2sDNzvs9zv0alaY8mJ0ZySrUD3LQ+XyLhgM5pCpacBuUwChqiYDLDw==} posthog-node@4.18.0: resolution: {integrity: sha512-XROs1h+DNatgKh/AlIlCtDxWzwrKdYDb2mOs58n4yN8BkGN9ewqeQwG5ApS4/IzwCb7HPttUkOVulkYatd2PIw==} @@ -13157,9 +13310,6 @@ packages: resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} engines: {node: '>= 0.6'} - rate-limiter-flexible@8.1.0: - resolution: {integrity: sha512-J+4xBdVboibP1h0Imn4nFoCLT+UM9Os9vJaWaRWkLsQxS7jrhLJeLlmzP5hyCEsLwtgFIIY5KcWiJGyyVTMaKg==} - raw-body@2.5.3: resolution: {integrity: sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==} engines: {node: '>= 0.8'} @@ -13355,8 +13505,8 @@ packages: resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==} engines: {node: '>= 12.13.0'} - recharts@3.8.1: - resolution: {integrity: sha512-mwzmO1s9sFL0TduUpwndxCUNoXsBw3u3E/0+A+cLcrSfQitSG62L32N69GhqUrrT5qKcAE3pCGVINC6pqkBBQg==} + recharts@3.9.0: + resolution: {integrity: sha512-dCEcE9y20c8H2tkVeByrAXhhnBJk6/QLbxKmn+dJUptOfc5NMjwRh1jo0vZPRLD+5dMrHrP+hPEsfbGBMfnf5Q==} engines: {node: '>=18'} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 @@ -13507,6 +13657,9 @@ packages: reselect@5.1.1: resolution: {integrity: sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==} + reselect@5.2.0: + resolution: {integrity: sha512-AgZ3UOZm3YndfrJ4OYjgrT7bmCm/1iqkjvEfH/oYjzh6PD2qw4QuT3jjnXIrpdt4MTpMXclMT3lXbmRY+XRakw==} + reserved-identifiers@1.2.0: resolution: {integrity: sha512-yE7KUfFvaBFzGPs5H3Ops1RevfUEsDc5Iz65rOwWg4lE8HJSYtle77uul3+573457oHvBKuHYDl/xqUkKpEEdw==} engines: {node: '>=18'} @@ -13568,8 +13721,8 @@ packages: robust-predicates@3.0.3: resolution: {integrity: sha512-NS3levdsRIUOmiJ8FZWCP7LG3QpJyrs/TE0Zpf1yvZu8cAJJ6QMW92H1c7kWpdIHo8RvmLxN/o2JXTKHp74lUA==} - rolldown@1.0.3: - resolution: {integrity: sha512-i00lAJ2ks1BYr7rjNjKC7BcqAS7nVfiT3QX1SI5aY+AFHblCmaUf9OE9dbdzDvW6dJxbi2ZCZiy9v3CcwOiX3g==} + rolldown@1.1.3: + resolution: {integrity: sha512-1F1eEtUBtFvcGm1HQ9TiUIUHPQG7mSAODrhIzjxoUEFuo8OcbrGLiVLkevNgj84TE4lnHvnumwFjhJO5Eu135g==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true @@ -14150,6 +14303,9 @@ packages: tar-fs@3.1.2: resolution: {integrity: sha512-QGxxTxxyleAdyM3kpFs14ymbYmNFrfY+pHj7Z8FgtbZ7w2//VAgLMac7sT6nRpIHjppXO2AwwEOg0bPFVRcmXw==} + tar-fs@3.1.3: + resolution: {integrity: sha512-/hU4AXnIdZu+Gvl1pk0oI5f5HxWsCJRtY2aFaJdk9VvyL48DWU6iU5WAIPG+wIi1YvWA6eTJvIviP/tMAZZNwQ==} + tar-stream@2.2.0: resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} engines: {node: '>=6'} @@ -14425,8 +14581,8 @@ packages: resolution: {integrity: sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==} engines: {node: '>=0.6.11 <=0.7.0 || >=0.7.3'} - turbo@2.9.18: - resolution: {integrity: sha512-bwabv6PupzeavybzEoArBAkwq5fnzwf8OFnRtpHwnviFWuwJPFxtyH+aVp36TmIqK3aYYgtTJ3J0m2ysxxSzQg==} + turbo@2.10.0: + resolution: {integrity: sha512-o016H9PPtuH2deb3mh3Vci3Avfi9UYgM/RONQisY7HnloupP0IFSbFS3gFYJgFJP8nwBrByHWFQIDa8T2zIXPw==} hasBin: true tweetnacl@0.14.5: @@ -14448,6 +14604,10 @@ packages: resolution: {integrity: sha512-8ZiHFm91orbSAe2PSAiSVBVko18pbhbiB3U9GglSzF/zCGkR+rxpHx6sEMCUm4kxY4LjDIUGgCfUMtwfZfjfUA==} engines: {node: '>=20'} + type-fest@5.7.0: + resolution: {integrity: sha512-1URUxUqfHFM1c+zfSPsa3gnkO7Aq21qyH75SIduNYz4SzY964rn1X2vCMQaHSHhktiw+0kPa2iyb6PUpXqB6Vg==} + engines: {node: '>=20'} + type-is@1.6.18: resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} engines: {node: '>= 0.6'} @@ -14727,15 +14887,15 @@ packages: vite-tsconfig-paths@6.1.1: resolution: {integrity: sha512-2cihq7zliibCCZ8P9cKJrQBkfgdvcFkOOc3Y02o3GWUDLgqjWsZudaoiuOwO/gzTzy17cS5F7ZPo4bsnS4DGkg==} peerDependencies: - vite: '>=8.0.16' + vite: '>=8.1.0' - vite@8.0.16: - resolution: {integrity: sha512-h9bXPmJichP5fLmVQo3PyaGSDE2n3aPuomeAlVRm0JLmt4rY6zmPKd59HYI4LNW8oTK7tlTsuC7l/m7awx9Jcw==} + vite@8.1.0: + resolution: {integrity: sha512-BuJcQK/56NQTWDGn4ABea3q4SSBdNPWwNZKTkkUpcMPnLoquSYH8llRtSUIgoL1KSCpHt5eghLShn50mH36y7Q==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: '@types/node': ^20.19.0 || >=22.12.0 - '@vitejs/devtools': ^0.1.18 + '@vitejs/devtools': ^0.3.0 esbuild: ^0.27.0 || ^0.28.0 jiti: '>=1.21.0' less: ^4.0.0 @@ -15036,9 +15196,6 @@ packages: resolution: {integrity: sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==} engines: {node: '>=18'} - xcase@2.0.1: - resolution: {integrity: sha512-UmFXIPU+9Eg3E9m/728Bii0lAIuoc+6nbrNUKaRPJOFp91ih44qqGlWtxMB6kXFrRD6po+86ksHM5XHCfk6iPw==} - xdg-basedir@5.1.0: resolution: {integrity: sha512-GCPAHLvrIH13+c0SuacwvRYj2SxJXQ4kaVTT5xgL3kPrz56XxkF21IGhjSE1+W0aw7gpBWRGXLCPnPby6lSpmQ==} engines: {node: '>=12'} @@ -16399,272 +16556,272 @@ snapshots: '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - '@csstools/postcss-alpha-function@1.0.1(postcss@8.5.15)': + '@csstools/postcss-alpha-function@1.0.1(postcss@8.5.16)': dependencies: '@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.15) - '@csstools/utilities': 2.0.0(postcss@8.5.15) - postcss: 8.5.15 + '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.16) + '@csstools/utilities': 2.0.0(postcss@8.5.16) + postcss: 8.5.16 - '@csstools/postcss-cascade-layers@5.0.2(postcss@8.5.15)': + '@csstools/postcss-cascade-layers@5.0.2(postcss@8.5.16)': dependencies: '@csstools/selector-specificity': 5.0.0(postcss-selector-parser@7.1.0) - postcss: 8.5.15 + postcss: 8.5.16 postcss-selector-parser: 7.1.0 - '@csstools/postcss-color-function-display-p3-linear@1.0.1(postcss@8.5.15)': + '@csstools/postcss-color-function-display-p3-linear@1.0.1(postcss@8.5.16)': dependencies: '@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.15) - '@csstools/utilities': 2.0.0(postcss@8.5.15) - postcss: 8.5.15 + '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.16) + '@csstools/utilities': 2.0.0(postcss@8.5.16) + postcss: 8.5.16 - '@csstools/postcss-color-function@4.0.12(postcss@8.5.15)': + '@csstools/postcss-color-function@4.0.12(postcss@8.5.16)': dependencies: '@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.15) - '@csstools/utilities': 2.0.0(postcss@8.5.15) - postcss: 8.5.15 + '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.16) + '@csstools/utilities': 2.0.0(postcss@8.5.16) + postcss: 8.5.16 - '@csstools/postcss-color-mix-function@3.0.12(postcss@8.5.15)': + '@csstools/postcss-color-mix-function@3.0.12(postcss@8.5.16)': dependencies: '@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.15) - '@csstools/utilities': 2.0.0(postcss@8.5.15) - postcss: 8.5.15 + '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.16) + '@csstools/utilities': 2.0.0(postcss@8.5.16) + postcss: 8.5.16 - '@csstools/postcss-color-mix-variadic-function-arguments@1.0.2(postcss@8.5.15)': + '@csstools/postcss-color-mix-variadic-function-arguments@1.0.2(postcss@8.5.16)': dependencies: '@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.15) - '@csstools/utilities': 2.0.0(postcss@8.5.15) - postcss: 8.5.15 + '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.16) + '@csstools/utilities': 2.0.0(postcss@8.5.16) + postcss: 8.5.16 - '@csstools/postcss-content-alt-text@2.0.8(postcss@8.5.15)': + '@csstools/postcss-content-alt-text@2.0.8(postcss@8.5.16)': dependencies: '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.15) - '@csstools/utilities': 2.0.0(postcss@8.5.15) - postcss: 8.5.15 + '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.16) + '@csstools/utilities': 2.0.0(postcss@8.5.16) + postcss: 8.5.16 - '@csstools/postcss-contrast-color-function@2.0.12(postcss@8.5.15)': + '@csstools/postcss-contrast-color-function@2.0.12(postcss@8.5.16)': dependencies: '@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.15) - '@csstools/utilities': 2.0.0(postcss@8.5.15) - postcss: 8.5.15 + '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.16) + '@csstools/utilities': 2.0.0(postcss@8.5.16) + postcss: 8.5.16 - '@csstools/postcss-exponential-functions@2.0.9(postcss@8.5.15)': + '@csstools/postcss-exponential-functions@2.0.9(postcss@8.5.16)': dependencies: '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - postcss: 8.5.15 + postcss: 8.5.16 - '@csstools/postcss-font-format-keywords@4.0.0(postcss@8.5.15)': + '@csstools/postcss-font-format-keywords@4.0.0(postcss@8.5.16)': dependencies: - '@csstools/utilities': 2.0.0(postcss@8.5.15) - postcss: 8.5.15 + '@csstools/utilities': 2.0.0(postcss@8.5.16) + postcss: 8.5.16 postcss-value-parser: 4.2.0 - '@csstools/postcss-gamut-mapping@2.0.11(postcss@8.5.15)': + '@csstools/postcss-gamut-mapping@2.0.11(postcss@8.5.16)': dependencies: '@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - postcss: 8.5.15 + postcss: 8.5.16 - '@csstools/postcss-gradients-interpolation-method@5.0.12(postcss@8.5.15)': + '@csstools/postcss-gradients-interpolation-method@5.0.12(postcss@8.5.16)': dependencies: '@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.15) - '@csstools/utilities': 2.0.0(postcss@8.5.15) - postcss: 8.5.15 + '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.16) + '@csstools/utilities': 2.0.0(postcss@8.5.16) + postcss: 8.5.16 - '@csstools/postcss-hwb-function@4.0.12(postcss@8.5.15)': + '@csstools/postcss-hwb-function@4.0.12(postcss@8.5.16)': dependencies: '@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.15) - '@csstools/utilities': 2.0.0(postcss@8.5.15) - postcss: 8.5.15 + '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.16) + '@csstools/utilities': 2.0.0(postcss@8.5.16) + postcss: 8.5.16 - '@csstools/postcss-ic-unit@4.0.4(postcss@8.5.15)': + '@csstools/postcss-ic-unit@4.0.4(postcss@8.5.16)': dependencies: - '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.15) - '@csstools/utilities': 2.0.0(postcss@8.5.15) - postcss: 8.5.15 + '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.16) + '@csstools/utilities': 2.0.0(postcss@8.5.16) + postcss: 8.5.16 postcss-value-parser: 4.2.0 - '@csstools/postcss-initial@2.0.1(postcss@8.5.15)': + '@csstools/postcss-initial@2.0.1(postcss@8.5.16)': dependencies: - postcss: 8.5.15 + postcss: 8.5.16 - '@csstools/postcss-is-pseudo-class@5.0.3(postcss@8.5.15)': + '@csstools/postcss-is-pseudo-class@5.0.3(postcss@8.5.16)': dependencies: '@csstools/selector-specificity': 5.0.0(postcss-selector-parser@7.1.0) - postcss: 8.5.15 + postcss: 8.5.16 postcss-selector-parser: 7.1.0 - '@csstools/postcss-light-dark-function@2.0.11(postcss@8.5.15)': + '@csstools/postcss-light-dark-function@2.0.11(postcss@8.5.16)': dependencies: '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.15) - '@csstools/utilities': 2.0.0(postcss@8.5.15) - postcss: 8.5.15 + '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.16) + '@csstools/utilities': 2.0.0(postcss@8.5.16) + postcss: 8.5.16 - '@csstools/postcss-logical-float-and-clear@3.0.0(postcss@8.5.15)': + '@csstools/postcss-logical-float-and-clear@3.0.0(postcss@8.5.16)': dependencies: - postcss: 8.5.15 + postcss: 8.5.16 - '@csstools/postcss-logical-overflow@2.0.0(postcss@8.5.15)': + '@csstools/postcss-logical-overflow@2.0.0(postcss@8.5.16)': dependencies: - postcss: 8.5.15 + postcss: 8.5.16 - '@csstools/postcss-logical-overscroll-behavior@2.0.0(postcss@8.5.15)': + '@csstools/postcss-logical-overscroll-behavior@2.0.0(postcss@8.5.16)': dependencies: - postcss: 8.5.15 + postcss: 8.5.16 - '@csstools/postcss-logical-resize@3.0.0(postcss@8.5.15)': + '@csstools/postcss-logical-resize@3.0.0(postcss@8.5.16)': dependencies: - postcss: 8.5.15 + postcss: 8.5.16 postcss-value-parser: 4.2.0 - '@csstools/postcss-logical-viewport-units@3.0.4(postcss@8.5.15)': + '@csstools/postcss-logical-viewport-units@3.0.4(postcss@8.5.16)': dependencies: '@csstools/css-tokenizer': 3.0.4 - '@csstools/utilities': 2.0.0(postcss@8.5.15) - postcss: 8.5.15 + '@csstools/utilities': 2.0.0(postcss@8.5.16) + postcss: 8.5.16 - '@csstools/postcss-media-minmax@2.0.9(postcss@8.5.15)': + '@csstools/postcss-media-minmax@2.0.9(postcss@8.5.16)': dependencies: '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 '@csstools/media-query-list-parser': 4.0.3(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) - postcss: 8.5.15 + postcss: 8.5.16 - '@csstools/postcss-media-queries-aspect-ratio-number-values@3.0.5(postcss@8.5.15)': + '@csstools/postcss-media-queries-aspect-ratio-number-values@3.0.5(postcss@8.5.16)': dependencies: '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 '@csstools/media-query-list-parser': 4.0.3(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) - postcss: 8.5.15 + postcss: 8.5.16 - '@csstools/postcss-nested-calc@4.0.0(postcss@8.5.15)': + '@csstools/postcss-nested-calc@4.0.0(postcss@8.5.16)': dependencies: - '@csstools/utilities': 2.0.0(postcss@8.5.15) - postcss: 8.5.15 + '@csstools/utilities': 2.0.0(postcss@8.5.16) + postcss: 8.5.16 postcss-value-parser: 4.2.0 - '@csstools/postcss-normalize-display-values@4.0.1(postcss@8.5.15)': + '@csstools/postcss-normalize-display-values@4.0.1(postcss@8.5.16)': dependencies: - postcss: 8.5.15 + postcss: 8.5.16 postcss-value-parser: 4.2.0 - '@csstools/postcss-oklab-function@4.0.12(postcss@8.5.15)': + '@csstools/postcss-oklab-function@4.0.12(postcss@8.5.16)': dependencies: '@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.15) - '@csstools/utilities': 2.0.0(postcss@8.5.15) - postcss: 8.5.15 + '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.16) + '@csstools/utilities': 2.0.0(postcss@8.5.16) + postcss: 8.5.16 - '@csstools/postcss-position-area-property@1.0.0(postcss@8.5.15)': + '@csstools/postcss-position-area-property@1.0.0(postcss@8.5.16)': dependencies: - postcss: 8.5.15 + postcss: 8.5.16 - '@csstools/postcss-progressive-custom-properties@4.2.1(postcss@8.5.15)': + '@csstools/postcss-progressive-custom-properties@4.2.1(postcss@8.5.16)': dependencies: - postcss: 8.5.15 + postcss: 8.5.16 postcss-value-parser: 4.2.0 - '@csstools/postcss-property-rule-prelude-list@1.0.0(postcss@8.5.15)': + '@csstools/postcss-property-rule-prelude-list@1.0.0(postcss@8.5.16)': dependencies: '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - postcss: 8.5.15 + postcss: 8.5.16 - '@csstools/postcss-random-function@2.0.1(postcss@8.5.15)': + '@csstools/postcss-random-function@2.0.1(postcss@8.5.16)': dependencies: '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - postcss: 8.5.15 + postcss: 8.5.16 - '@csstools/postcss-relative-color-syntax@3.0.12(postcss@8.5.15)': + '@csstools/postcss-relative-color-syntax@3.0.12(postcss@8.5.16)': dependencies: '@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.15) - '@csstools/utilities': 2.0.0(postcss@8.5.15) - postcss: 8.5.15 + '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.16) + '@csstools/utilities': 2.0.0(postcss@8.5.16) + postcss: 8.5.16 - '@csstools/postcss-scope-pseudo-class@4.0.1(postcss@8.5.15)': + '@csstools/postcss-scope-pseudo-class@4.0.1(postcss@8.5.16)': dependencies: - postcss: 8.5.15 + postcss: 8.5.16 postcss-selector-parser: 7.1.0 - '@csstools/postcss-sign-functions@1.1.4(postcss@8.5.15)': + '@csstools/postcss-sign-functions@1.1.4(postcss@8.5.16)': dependencies: '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - postcss: 8.5.15 + postcss: 8.5.16 - '@csstools/postcss-stepped-value-functions@4.0.9(postcss@8.5.15)': + '@csstools/postcss-stepped-value-functions@4.0.9(postcss@8.5.16)': dependencies: '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - postcss: 8.5.15 + postcss: 8.5.16 - '@csstools/postcss-syntax-descriptor-syntax-production@1.0.1(postcss@8.5.15)': + '@csstools/postcss-syntax-descriptor-syntax-production@1.0.1(postcss@8.5.16)': dependencies: '@csstools/css-tokenizer': 3.0.4 - postcss: 8.5.15 + postcss: 8.5.16 - '@csstools/postcss-system-ui-font-family@1.0.0(postcss@8.5.15)': + '@csstools/postcss-system-ui-font-family@1.0.0(postcss@8.5.16)': dependencies: '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - postcss: 8.5.15 + postcss: 8.5.16 - '@csstools/postcss-text-decoration-shorthand@4.0.3(postcss@8.5.15)': + '@csstools/postcss-text-decoration-shorthand@4.0.3(postcss@8.5.16)': dependencies: '@csstools/color-helpers': 5.1.0 - postcss: 8.5.15 + postcss: 8.5.16 postcss-value-parser: 4.2.0 - '@csstools/postcss-trigonometric-functions@4.0.9(postcss@8.5.15)': + '@csstools/postcss-trigonometric-functions@4.0.9(postcss@8.5.16)': dependencies: '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - postcss: 8.5.15 + postcss: 8.5.16 - '@csstools/postcss-unset-value@4.0.0(postcss@8.5.15)': + '@csstools/postcss-unset-value@4.0.0(postcss@8.5.16)': dependencies: - postcss: 8.5.15 + postcss: 8.5.16 '@csstools/selector-resolve-nested@3.1.0(postcss-selector-parser@7.1.0)': dependencies: @@ -16674,9 +16831,9 @@ snapshots: dependencies: postcss-selector-parser: 7.1.0 - '@csstools/utilities@2.0.0(postcss@8.5.15)': + '@csstools/utilities@2.0.0(postcss@8.5.16)': dependencies: - postcss: 8.5.15 + postcss: 8.5.16 '@ctrl/deluge@8.0.0': dependencies: @@ -16783,7 +16940,7 @@ snapshots: - '@algolia/client-search' - algoliasearch - '@docusaurus/babel@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.15))(esbuild@0.28.0)(html-minifier-terser@7.2.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + '@docusaurus/babel@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.16))(esbuild@0.28.1)(html-minifier-terser@7.2.0)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': dependencies: '@babel/core': 7.29.0 '@babel/generator': 7.29.0 @@ -16795,7 +16952,7 @@ snapshots: '@babel/runtime': 7.29.7 '@babel/traverse': 7.29.0 '@docusaurus/logger': 3.10.1 - '@docusaurus/utils': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.15))(esbuild@0.28.0)(html-minifier-terser@7.2.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@docusaurus/utils': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.16))(esbuild@0.28.1)(html-minifier-terser@7.2.0)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) babel-plugin-dynamic-import-node: 2.3.3 fs-extra: 11.2.0 tslib: 2.8.1 @@ -16817,34 +16974,68 @@ snapshots: - uglify-js - webpack-cli - '@docusaurus/bundler@3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.0)(postcss@8.5.15))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3)': + '@docusaurus/babel@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': dependencies: '@babel/core': 7.29.0 - '@docusaurus/babel': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.15))(esbuild@0.28.0)(html-minifier-terser@7.2.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@babel/generator': 7.29.0 + '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.29.0) + '@babel/plugin-transform-runtime': 7.29.7(@babel/core@7.29.0) + '@babel/preset-env': 7.29.7(@babel/core@7.29.0) + '@babel/preset-react': 7.29.7(@babel/core@7.29.0) + '@babel/preset-typescript': 7.29.7(@babel/core@7.29.0) + '@babel/runtime': 7.29.7 + '@babel/traverse': 7.29.0 + '@docusaurus/logger': 3.10.1 + '@docusaurus/utils': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + babel-plugin-dynamic-import-node: 2.3.3 + fs-extra: 11.2.0 + tslib: 2.8.1 + transitivePeerDependencies: + - '@minify-html/node' + - '@swc/core' + - '@swc/css' + - '@swc/html' + - clean-css + - cssnano + - csso + - esbuild + - html-minifier-terser + - lightningcss + - postcss + - react + - react-dom + - supports-color + - uglify-js + - webpack-cli + + '@docusaurus/bundler@3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.1)(postcss@8.5.16))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3)': + dependencies: + '@babel/core': 7.29.0 + '@docusaurus/babel': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.16))(esbuild@0.28.1)(html-minifier-terser@7.2.0)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@docusaurus/cssnano-preset': 3.10.1 '@docusaurus/logger': 3.10.1 - '@docusaurus/types': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.15))(esbuild@0.28.0)(html-minifier-terser@7.2.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@docusaurus/utils': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.15))(esbuild@0.28.0)(html-minifier-terser@7.2.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - babel-loader: 9.2.1(@babel/core@7.29.0)(webpack@5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)) + '@docusaurus/types': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.16))(esbuild@0.28.1)(html-minifier-terser@7.2.0)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@docusaurus/utils': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.16))(esbuild@0.28.1)(html-minifier-terser@7.2.0)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + babel-loader: 9.2.1(@babel/core@7.29.0)(webpack@5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)) clean-css: 5.3.3 - copy-webpack-plugin: 11.0.0(webpack@5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)) - css-loader: 6.11.0(@rspack/core@1.7.11(@swc/helpers@0.5.15))(webpack@5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)) - css-minimizer-webpack-plugin: 5.0.1(clean-css@5.3.3)(esbuild@0.28.0)(webpack@5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)) - cssnano: 6.1.2(postcss@8.5.15) - file-loader: 6.2.0(webpack@5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)) + copy-webpack-plugin: 11.0.0(webpack@5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)) + css-loader: 6.11.0(@rspack/core@1.7.11(@swc/helpers@0.5.15))(webpack@5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)) + css-minimizer-webpack-plugin: 5.0.1(clean-css@5.3.3)(esbuild@0.28.0)(webpack@5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)) + cssnano: 6.1.2(postcss@8.5.16) + file-loader: 6.2.0(webpack@5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)) html-minifier-terser: 7.2.0 - mini-css-extract-plugin: 2.10.2(webpack@5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)) - null-loader: 4.0.1(webpack@5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)) - postcss: 8.5.15 - postcss-loader: 7.3.4(postcss@8.5.15)(typescript@6.0.3)(webpack@5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)) - postcss-preset-env: 10.6.1(postcss@8.5.15) - terser-webpack-plugin: 5.6.0(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.15))(esbuild@0.28.0)(html-minifier-terser@7.2.0)(postcss@8.5.15)(webpack@5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)) + mini-css-extract-plugin: 2.10.2(webpack@5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)) + null-loader: 4.0.1(webpack@5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)) + postcss: 8.5.16 + postcss-loader: 7.3.4(postcss@8.5.16)(typescript@6.0.3)(webpack@5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)) + postcss-preset-env: 10.6.1(postcss@8.5.16) + terser-webpack-plugin: 5.6.0(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.16))(esbuild@0.28.0)(html-minifier-terser@7.2.0)(postcss@8.5.16)(webpack@5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)) tslib: 2.8.1 - url-loader: 4.1.1(file-loader@6.2.0(webpack@5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)))(webpack@5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)) - webpack: 5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.15))(esbuild@0.28.0)(html-minifier-terser@7.2.0)(postcss@8.5.15) - webpackbar: 7.0.0(@rspack/core@1.7.11(@swc/helpers@0.5.15))(webpack@5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)) + url-loader: 4.1.1(file-loader@6.2.0(webpack@5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)))(webpack@5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)) + webpack: 5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.16))(esbuild@0.28.1)(html-minifier-terser@7.2.0)(postcss@8.5.16) + webpackbar: 7.0.0(@rspack/core@1.7.11(@swc/helpers@0.5.15))(webpack@5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)) optionalDependencies: - '@docusaurus/faster': 3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.0)(postcss@8.5.15) + '@docusaurus/faster': 3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.1)(postcss@8.5.16) transitivePeerDependencies: - '@minify-html/node' - '@parcel/css' @@ -16862,15 +17053,60 @@ snapshots: - uglify-js - webpack-cli - '@docusaurus/core@3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.0)(postcss@8.5.15))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3)': + '@docusaurus/bundler@3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.1)(postcss@8.5.16))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3)': dependencies: - '@docusaurus/babel': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.15))(esbuild@0.28.0)(html-minifier-terser@7.2.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@docusaurus/bundler': 3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.0)(postcss@8.5.15))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3) + '@babel/core': 7.29.0 + '@docusaurus/babel': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.16))(esbuild@0.28.1)(html-minifier-terser@7.2.0)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@docusaurus/cssnano-preset': 3.10.1 '@docusaurus/logger': 3.10.1 - '@docusaurus/mdx-loader': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@docusaurus/utils': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.15))(esbuild@0.28.0)(html-minifier-terser@7.2.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@docusaurus/utils-common': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.15))(esbuild@0.28.0)(html-minifier-terser@7.2.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@docusaurus/utils-validation': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@docusaurus/types': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.16))(esbuild@0.28.1)(html-minifier-terser@7.2.0)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@docusaurus/utils': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.16))(esbuild@0.28.1)(html-minifier-terser@7.2.0)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + babel-loader: 9.2.1(@babel/core@7.29.0)(webpack@5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)) + clean-css: 5.3.3 + copy-webpack-plugin: 11.0.0(webpack@5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)) + css-loader: 6.11.0(@rspack/core@1.7.11(@swc/helpers@0.5.15))(webpack@5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)) + css-minimizer-webpack-plugin: 5.0.1(clean-css@5.3.3)(esbuild@0.28.1)(webpack@5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)) + cssnano: 6.1.2(postcss@8.5.16) + file-loader: 6.2.0(webpack@5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)) + html-minifier-terser: 7.2.0 + mini-css-extract-plugin: 2.10.2(webpack@5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)) + null-loader: 4.0.1(webpack@5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)) + postcss: 8.5.16 + postcss-loader: 7.3.4(postcss@8.5.16)(typescript@6.0.3)(webpack@5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)) + postcss-preset-env: 10.6.1(postcss@8.5.16) + terser-webpack-plugin: 5.6.0(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.16))(esbuild@0.28.1)(html-minifier-terser@7.2.0)(postcss@8.5.16)(webpack@5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)) + tslib: 2.8.1 + url-loader: 4.1.1(file-loader@6.2.0(webpack@5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)))(webpack@5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)) + webpack: 5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.16))(esbuild@0.28.1)(html-minifier-terser@7.2.0)(postcss@8.5.16) + webpackbar: 7.0.0(@rspack/core@1.7.11(@swc/helpers@0.5.15))(webpack@5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)) + optionalDependencies: + '@docusaurus/faster': 3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.1)(postcss@8.5.16) + transitivePeerDependencies: + - '@minify-html/node' + - '@parcel/css' + - '@rspack/core' + - '@swc/core' + - '@swc/css' + - '@swc/html' + - csso + - esbuild + - lightningcss + - react + - react-dom + - supports-color + - typescript + - uglify-js + - webpack-cli + + '@docusaurus/core@3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.1)(postcss@8.5.16))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3)': + dependencies: + '@docusaurus/babel': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@docusaurus/bundler': 3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.1)(postcss@8.5.16))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3) + '@docusaurus/logger': 3.10.1 + '@docusaurus/mdx-loader': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@docusaurus/utils': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@docusaurus/utils-common': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@docusaurus/utils-validation': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@mdx-js/react': 3.1.1(@types/react@19.2.17)(react@19.2.7) boxen: 6.2.1 chalk: 4.1.2 @@ -16886,7 +17122,7 @@ snapshots: execa: 5.1.1 fs-extra: 11.2.0 html-tags: 3.3.1 - html-webpack-plugin: 5.6.7(@rspack/core@1.7.11(@swc/helpers@0.5.15))(webpack@5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)) + html-webpack-plugin: 5.6.7(@rspack/core@1.7.11(@swc/helpers@0.5.15))(webpack@5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)) leven: 3.1.0 lodash: 4.18.1 open: 8.4.2 @@ -16896,7 +17132,7 @@ snapshots: react-dom: 19.2.7(react@19.2.7) react-helmet-async: '@slorber/react-helmet-async@1.3.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7)' react-loadable: '@docusaurus/react-loadable@6.0.0(react@19.2.7)' - react-loadable-ssr-addon-v5-slorber: 1.0.3(@docusaurus/react-loadable@6.0.0(react@19.2.7))(webpack@5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)) + react-loadable-ssr-addon-v5-slorber: 1.0.3(@docusaurus/react-loadable@6.0.0(react@19.2.7))(webpack@5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)) react-router: 5.3.4(react@19.2.7) react-router-config: 5.1.1(react-router@5.3.4(react@19.2.7))(react@19.2.7) react-router-dom: 5.3.4(react@19.2.7) @@ -16905,12 +17141,83 @@ snapshots: tinypool: 1.1.1 tslib: 2.8.1 update-notifier: 6.0.2 - webpack: 5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.15))(esbuild@0.28.0)(html-minifier-terser@7.2.0)(postcss@8.5.15) + webpack: 5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.15) webpack-bundle-analyzer: 4.10.2 - webpack-dev-server: 5.2.4(tslib@2.8.1)(webpack@5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)) + webpack-dev-server: 5.2.4(tslib@2.8.1)(webpack@5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)) webpack-merge: 6.0.1 optionalDependencies: - '@docusaurus/faster': 3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.0)(postcss@8.5.15) + '@docusaurus/faster': 3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.1)(postcss@8.5.16) + transitivePeerDependencies: + - '@minify-html/node' + - '@parcel/css' + - '@rspack/core' + - '@swc/core' + - '@swc/css' + - '@swc/html' + - bufferutil + - clean-css + - cssnano + - csso + - debug + - esbuild + - html-minifier-terser + - lightningcss + - postcss + - supports-color + - typescript + - uglify-js + - utf-8-validate + - webpack-cli + + '@docusaurus/core@3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.1)(postcss@8.5.16))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3)': + dependencies: + '@docusaurus/babel': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.16))(esbuild@0.28.1)(html-minifier-terser@7.2.0)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@docusaurus/bundler': 3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.1)(postcss@8.5.16))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3) + '@docusaurus/logger': 3.10.1 + '@docusaurus/mdx-loader': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@docusaurus/utils': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.16))(esbuild@0.28.1)(html-minifier-terser@7.2.0)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@docusaurus/utils-common': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.16))(esbuild@0.28.1)(html-minifier-terser@7.2.0)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@docusaurus/utils-validation': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@mdx-js/react': 3.1.1(@types/react@19.2.17)(react@19.2.7) + boxen: 6.2.1 + chalk: 4.1.2 + chokidar: 3.6.0 + cli-table3: 0.6.5 + combine-promises: 1.2.0 + commander: 5.1.0 + core-js: 3.49.0 + detect-port: 1.6.1 + escape-html: 1.0.3 + eta: 2.2.0 + eval: 0.1.8 + execa: 5.1.1 + fs-extra: 11.2.0 + html-tags: 3.3.1 + html-webpack-plugin: 5.6.7(@rspack/core@1.7.11(@swc/helpers@0.5.15))(webpack@5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)) + leven: 3.1.0 + lodash: 4.18.1 + open: 8.4.2 + p-map: 4.0.0 + prompts: 2.4.2 + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) + react-helmet-async: '@slorber/react-helmet-async@1.3.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7)' + react-loadable: '@docusaurus/react-loadable@6.0.0(react@19.2.7)' + react-loadable-ssr-addon-v5-slorber: 1.0.3(@docusaurus/react-loadable@6.0.0(react@19.2.7))(webpack@5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)) + react-router: 5.3.4(react@19.2.7) + react-router-config: 5.1.1(react-router@5.3.4(react@19.2.7))(react@19.2.7) + react-router-dom: 5.3.4(react@19.2.7) + semver: 7.7.4 + serve-handler: 6.1.7 + tinypool: 1.1.1 + tslib: 2.8.1 + update-notifier: 6.0.2 + webpack: 5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.16))(esbuild@0.28.1)(html-minifier-terser@7.2.0)(postcss@8.5.16) + webpack-bundle-analyzer: 4.10.2 + webpack-dev-server: 5.2.4(tslib@2.8.1)(webpack@5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)) + webpack-merge: 6.0.1 + optionalDependencies: + '@docusaurus/faster': 3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.1)(postcss@8.5.16) transitivePeerDependencies: - '@minify-html/node' - '@parcel/css' @@ -16935,23 +17242,23 @@ snapshots: '@docusaurus/cssnano-preset@3.10.1': dependencies: - cssnano-preset-advanced: 6.1.2(postcss@8.5.15) - postcss: 8.5.15 - postcss-sort-media-queries: 5.2.0(postcss@8.5.15) + cssnano-preset-advanced: 6.1.2(postcss@8.5.16) + postcss: 8.5.16 + postcss-sort-media-queries: 5.2.0(postcss@8.5.16) tslib: 2.8.1 - '@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.0)(postcss@8.5.15)': + '@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.1)(postcss@8.5.16)': dependencies: - '@docusaurus/types': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.15))(esbuild@0.28.0)(html-minifier-terser@7.2.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@docusaurus/types': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.16))(esbuild@0.28.1)(html-minifier-terser@7.2.0)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@rspack/core': 1.7.11(@swc/helpers@0.5.15) '@swc/core': 1.15.3(@swc/helpers@0.5.15) '@swc/html': 1.15.40 browserslist: 4.28.2 lightningcss: 1.32.0 semver: 7.7.4 - swc-loader: 0.2.7(@swc/core@1.15.3(@swc/helpers@0.5.15))(webpack@5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)) + swc-loader: 0.2.7(@swc/core@1.15.3(@swc/helpers@0.5.15))(webpack@5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)) tslib: 2.8.1 - webpack: 5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(@swc/html@1.15.40)(esbuild@0.28.0)(lightningcss@1.32.0)(postcss@8.5.15) + webpack: 5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(@swc/html@1.15.40)(esbuild@0.28.1)(lightningcss@1.32.0)(postcss@8.5.16) transitivePeerDependencies: - '@minify-html/node' - '@swc/css' @@ -16970,16 +17277,16 @@ snapshots: chalk: 4.1.2 tslib: 2.8.1 - '@docusaurus/mdx-loader@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + '@docusaurus/mdx-loader@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': dependencies: '@docusaurus/logger': 3.10.1 - '@docusaurus/utils': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.15))(esbuild@0.28.0)(html-minifier-terser@7.2.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@docusaurus/utils-validation': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@docusaurus/utils': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@docusaurus/utils-validation': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@mdx-js/mdx': 3.1.1 '@slorber/remark-comment': 1.0.0 escape-html: 1.0.3 estree-util-value-to-estree: 3.5.0 - file-loader: 6.2.0(webpack@5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)) + file-loader: 6.2.0(webpack@5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)) fs-extra: 11.2.0 image-size: 2.0.2 mdast-util-mdx: 3.0.0 @@ -16995,9 +17302,9 @@ snapshots: tslib: 2.8.1 unified: 11.0.5 unist-util-visit: 5.1.0 - url-loader: 4.1.1(file-loader@6.2.0(webpack@5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)))(webpack@5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)) + url-loader: 4.1.1(file-loader@6.2.0(webpack@5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)))(webpack@5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)) vfile: 6.0.3 - webpack: 5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.15))(esbuild@0.28.0)(html-minifier-terser@7.2.0)(postcss@8.5.15) + webpack: 5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.15) transitivePeerDependencies: - '@minify-html/node' - '@swc/core' @@ -17014,9 +17321,53 @@ snapshots: - uglify-js - webpack-cli - '@docusaurus/module-type-aliases@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + '@docusaurus/mdx-loader@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': dependencies: - '@docusaurus/types': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.15))(esbuild@0.28.0)(html-minifier-terser@7.2.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@docusaurus/logger': 3.10.1 + '@docusaurus/utils': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.16))(esbuild@0.28.1)(html-minifier-terser@7.2.0)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@docusaurus/utils-validation': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@mdx-js/mdx': 3.1.1 + '@slorber/remark-comment': 1.0.0 + escape-html: 1.0.3 + estree-util-value-to-estree: 3.5.0 + file-loader: 6.2.0(webpack@5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)) + fs-extra: 11.2.0 + image-size: 2.0.2 + mdast-util-mdx: 3.0.0 + mdast-util-to-string: 4.0.0 + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) + rehype-raw: 7.0.0 + remark-directive: 3.0.1 + remark-emoji: 4.0.1 + remark-frontmatter: 5.0.0 + remark-gfm: 4.0.1 + stringify-object: 3.3.0 + tslib: 2.8.1 + unified: 11.0.5 + unist-util-visit: 5.1.0 + url-loader: 4.1.1(file-loader@6.2.0(webpack@5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)))(webpack@5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)) + vfile: 6.0.3 + webpack: 5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.16))(esbuild@0.28.1)(html-minifier-terser@7.2.0)(postcss@8.5.16) + transitivePeerDependencies: + - '@minify-html/node' + - '@swc/core' + - '@swc/css' + - '@swc/html' + - clean-css + - cssnano + - csso + - esbuild + - html-minifier-terser + - lightningcss + - postcss + - supports-color + - uglify-js + - webpack-cli + + '@docusaurus/module-type-aliases@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + dependencies: + '@docusaurus/types': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@types/history': 4.7.11 '@types/react': 19.2.17 '@types/react-router-config': 5.0.11 @@ -17041,17 +17392,44 @@ snapshots: - uglify-js - webpack-cli - '@docusaurus/plugin-content-blog@3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.0)(postcss@8.5.15))(@docusaurus/plugin-content-docs@3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.0)(postcss@8.5.15))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3)': + '@docusaurus/module-type-aliases@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': dependencies: - '@docusaurus/core': 3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.0)(postcss@8.5.15))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3) + '@docusaurus/types': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.16))(esbuild@0.28.1)(html-minifier-terser@7.2.0)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@types/history': 4.7.11 + '@types/react': 19.2.17 + '@types/react-router-config': 5.0.11 + '@types/react-router-dom': 5.3.3 + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) + react-helmet-async: '@slorber/react-helmet-async@1.3.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7)' + react-loadable: '@docusaurus/react-loadable@6.0.0(react@19.2.7)' + transitivePeerDependencies: + - '@minify-html/node' + - '@swc/core' + - '@swc/css' + - '@swc/html' + - clean-css + - cssnano + - csso + - esbuild + - html-minifier-terser + - lightningcss + - postcss + - supports-color + - uglify-js + - webpack-cli + + '@docusaurus/plugin-content-blog@3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.1)(postcss@8.5.16))(@docusaurus/plugin-content-docs@3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.1)(postcss@8.5.16))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3)': + dependencies: + '@docusaurus/core': 3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.1)(postcss@8.5.16))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3) '@docusaurus/logger': 3.10.1 - '@docusaurus/mdx-loader': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@docusaurus/plugin-content-docs': 3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.0)(postcss@8.5.15))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3) - '@docusaurus/theme-common': 3.10.1(@docusaurus/plugin-content-docs@3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.0)(postcss@8.5.15))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@docusaurus/types': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.15))(esbuild@0.28.0)(html-minifier-terser@7.2.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@docusaurus/utils': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.15))(esbuild@0.28.0)(html-minifier-terser@7.2.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@docusaurus/utils-common': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.15))(esbuild@0.28.0)(html-minifier-terser@7.2.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@docusaurus/utils-validation': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@docusaurus/mdx-loader': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@docusaurus/plugin-content-docs': 3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.1)(postcss@8.5.16))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3) + '@docusaurus/theme-common': 3.10.1(@docusaurus/plugin-content-docs@3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.1)(postcss@8.5.16))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@docusaurus/types': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@docusaurus/utils': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@docusaurus/utils-common': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@docusaurus/utils-validation': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) cheerio: 1.0.0-rc.12 combine-promises: 1.2.0 feed: 4.2.2 @@ -17064,7 +17442,7 @@ snapshots: tslib: 2.8.1 unist-util-visit: 5.1.0 utility-types: 3.11.0 - webpack: 5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.15))(esbuild@0.28.0)(html-minifier-terser@7.2.0)(postcss@8.5.15) + webpack: 5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.15) transitivePeerDependencies: - '@docusaurus/faster' - '@mdx-js/react' @@ -17089,17 +17467,65 @@ snapshots: - utf-8-validate - webpack-cli - '@docusaurus/plugin-content-docs@3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.0)(postcss@8.5.15))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3)': + '@docusaurus/plugin-content-blog@3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.1)(postcss@8.5.16))(@docusaurus/plugin-content-docs@3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.1)(postcss@8.5.16))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3)': dependencies: - '@docusaurus/core': 3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.0)(postcss@8.5.15))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3) + '@docusaurus/core': 3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.1)(postcss@8.5.16))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3) '@docusaurus/logger': 3.10.1 - '@docusaurus/mdx-loader': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@docusaurus/module-type-aliases': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@docusaurus/theme-common': 3.10.1(@docusaurus/plugin-content-docs@3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.0)(postcss@8.5.15))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@docusaurus/types': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.15))(esbuild@0.28.0)(html-minifier-terser@7.2.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@docusaurus/utils': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.15))(esbuild@0.28.0)(html-minifier-terser@7.2.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@docusaurus/utils-common': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.15))(esbuild@0.28.0)(html-minifier-terser@7.2.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@docusaurus/utils-validation': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@docusaurus/mdx-loader': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@docusaurus/plugin-content-docs': 3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.1)(postcss@8.5.16))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3) + '@docusaurus/theme-common': 3.10.1(@docusaurus/plugin-content-docs@3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.1)(postcss@8.5.16))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@docusaurus/types': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.16))(esbuild@0.28.1)(html-minifier-terser@7.2.0)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@docusaurus/utils': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.16))(esbuild@0.28.1)(html-minifier-terser@7.2.0)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@docusaurus/utils-common': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.16))(esbuild@0.28.1)(html-minifier-terser@7.2.0)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@docusaurus/utils-validation': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + cheerio: 1.0.0-rc.12 + combine-promises: 1.2.0 + feed: 4.2.2 + fs-extra: 11.2.0 + lodash: 4.18.1 + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) + schema-dts: 1.1.5 + srcset: 4.0.0 + tslib: 2.8.1 + unist-util-visit: 5.1.0 + utility-types: 3.11.0 + webpack: 5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.16))(esbuild@0.28.1)(html-minifier-terser@7.2.0)(postcss@8.5.16) + transitivePeerDependencies: + - '@docusaurus/faster' + - '@mdx-js/react' + - '@minify-html/node' + - '@parcel/css' + - '@rspack/core' + - '@swc/core' + - '@swc/css' + - '@swc/html' + - bufferutil + - clean-css + - cssnano + - csso + - debug + - esbuild + - html-minifier-terser + - lightningcss + - postcss + - supports-color + - typescript + - uglify-js + - utf-8-validate + - webpack-cli + + '@docusaurus/plugin-content-docs@3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.1)(postcss@8.5.16))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3)': + dependencies: + '@docusaurus/core': 3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.1)(postcss@8.5.16))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3) + '@docusaurus/logger': 3.10.1 + '@docusaurus/mdx-loader': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@docusaurus/module-type-aliases': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@docusaurus/theme-common': 3.10.1(@docusaurus/plugin-content-docs@3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.1)(postcss@8.5.16))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@docusaurus/types': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@docusaurus/utils': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@docusaurus/utils-common': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@docusaurus/utils-validation': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@types/react-router-config': 5.0.11 combine-promises: 1.2.0 fs-extra: 11.2.0 @@ -17110,7 +17536,7 @@ snapshots: schema-dts: 1.1.5 tslib: 2.8.1 utility-types: 3.11.0 - webpack: 5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.15))(esbuild@0.28.0)(html-minifier-terser@7.2.0)(postcss@8.5.15) + webpack: 5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.15) transitivePeerDependencies: - '@docusaurus/faster' - '@mdx-js/react' @@ -17135,18 +17561,64 @@ snapshots: - utf-8-validate - webpack-cli - '@docusaurus/plugin-content-pages@3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.0)(postcss@8.5.15))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3)': + '@docusaurus/plugin-content-docs@3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.1)(postcss@8.5.16))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3)': dependencies: - '@docusaurus/core': 3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.0)(postcss@8.5.15))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3) - '@docusaurus/mdx-loader': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@docusaurus/types': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.15))(esbuild@0.28.0)(html-minifier-terser@7.2.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@docusaurus/utils': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.15))(esbuild@0.28.0)(html-minifier-terser@7.2.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@docusaurus/utils-validation': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@docusaurus/core': 3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.1)(postcss@8.5.16))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3) + '@docusaurus/logger': 3.10.1 + '@docusaurus/mdx-loader': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@docusaurus/module-type-aliases': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@docusaurus/theme-common': 3.10.1(@docusaurus/plugin-content-docs@3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.1)(postcss@8.5.16))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@docusaurus/types': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.16))(esbuild@0.28.1)(html-minifier-terser@7.2.0)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@docusaurus/utils': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.16))(esbuild@0.28.1)(html-minifier-terser@7.2.0)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@docusaurus/utils-common': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.16))(esbuild@0.28.1)(html-minifier-terser@7.2.0)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@docusaurus/utils-validation': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@types/react-router-config': 5.0.11 + combine-promises: 1.2.0 + fs-extra: 11.2.0 + js-yaml: 4.1.1 + lodash: 4.18.1 + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) + schema-dts: 1.1.5 + tslib: 2.8.1 + utility-types: 3.11.0 + webpack: 5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.16))(esbuild@0.28.1)(html-minifier-terser@7.2.0)(postcss@8.5.16) + transitivePeerDependencies: + - '@docusaurus/faster' + - '@mdx-js/react' + - '@minify-html/node' + - '@parcel/css' + - '@rspack/core' + - '@swc/core' + - '@swc/css' + - '@swc/html' + - bufferutil + - clean-css + - cssnano + - csso + - debug + - esbuild + - html-minifier-terser + - lightningcss + - postcss + - supports-color + - typescript + - uglify-js + - utf-8-validate + - webpack-cli + + '@docusaurus/plugin-content-pages@3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.1)(postcss@8.5.16))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3)': + dependencies: + '@docusaurus/core': 3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.1)(postcss@8.5.16))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3) + '@docusaurus/mdx-loader': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@docusaurus/types': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@docusaurus/utils': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@docusaurus/utils-validation': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) fs-extra: 11.2.0 react: 19.2.7 react-dom: 19.2.7(react@19.2.7) tslib: 2.8.1 - webpack: 5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.15))(esbuild@0.28.0)(html-minifier-terser@7.2.0)(postcss@8.5.15) + webpack: 5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.15) transitivePeerDependencies: - '@docusaurus/faster' - '@mdx-js/react' @@ -17171,12 +17643,48 @@ snapshots: - utf-8-validate - webpack-cli - '@docusaurus/plugin-css-cascade-layers@3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.0)(postcss@8.5.15))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3)': + '@docusaurus/plugin-content-pages@3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.1)(postcss@8.5.16))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3)': dependencies: - '@docusaurus/core': 3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.0)(postcss@8.5.15))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3) - '@docusaurus/types': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.15))(esbuild@0.28.0)(html-minifier-terser@7.2.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@docusaurus/utils': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.15))(esbuild@0.28.0)(html-minifier-terser@7.2.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@docusaurus/utils-validation': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@docusaurus/core': 3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.1)(postcss@8.5.16))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3) + '@docusaurus/mdx-loader': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@docusaurus/types': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.16))(esbuild@0.28.1)(html-minifier-terser@7.2.0)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@docusaurus/utils': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.16))(esbuild@0.28.1)(html-minifier-terser@7.2.0)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@docusaurus/utils-validation': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + fs-extra: 11.2.0 + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) + tslib: 2.8.1 + webpack: 5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.16))(esbuild@0.28.1)(html-minifier-terser@7.2.0)(postcss@8.5.16) + transitivePeerDependencies: + - '@docusaurus/faster' + - '@mdx-js/react' + - '@minify-html/node' + - '@parcel/css' + - '@rspack/core' + - '@swc/core' + - '@swc/css' + - '@swc/html' + - bufferutil + - clean-css + - cssnano + - csso + - debug + - esbuild + - html-minifier-terser + - lightningcss + - postcss + - supports-color + - typescript + - uglify-js + - utf-8-validate + - webpack-cli + + '@docusaurus/plugin-css-cascade-layers@3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.1)(postcss@8.5.16))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3)': + dependencies: + '@docusaurus/core': 3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.1)(postcss@8.5.16))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3) + '@docusaurus/types': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.16))(esbuild@0.28.1)(html-minifier-terser@7.2.0)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@docusaurus/utils': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.16))(esbuild@0.28.1)(html-minifier-terser@7.2.0)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@docusaurus/utils-validation': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) tslib: 2.8.1 transitivePeerDependencies: - '@docusaurus/faster' @@ -17204,11 +17712,11 @@ snapshots: - utf-8-validate - webpack-cli - '@docusaurus/plugin-debug@3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.0)(postcss@8.5.15))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3)': + '@docusaurus/plugin-debug@3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.1)(postcss@8.5.16))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3)': dependencies: - '@docusaurus/core': 3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.0)(postcss@8.5.15))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3) - '@docusaurus/types': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.15))(esbuild@0.28.0)(html-minifier-terser@7.2.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@docusaurus/utils': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.15))(esbuild@0.28.0)(html-minifier-terser@7.2.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@docusaurus/core': 3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.1)(postcss@8.5.16))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3) + '@docusaurus/types': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.16))(esbuild@0.28.1)(html-minifier-terser@7.2.0)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@docusaurus/utils': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.16))(esbuild@0.28.1)(html-minifier-terser@7.2.0)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) fs-extra: 11.2.0 react: 19.2.7 react-dom: 19.2.7(react@19.2.7) @@ -17238,11 +17746,11 @@ snapshots: - utf-8-validate - webpack-cli - '@docusaurus/plugin-google-analytics@3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.0)(postcss@8.5.15))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3)': + '@docusaurus/plugin-google-analytics@3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.1)(postcss@8.5.16))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3)': dependencies: - '@docusaurus/core': 3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.0)(postcss@8.5.15))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3) - '@docusaurus/types': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.15))(esbuild@0.28.0)(html-minifier-terser@7.2.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@docusaurus/utils-validation': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@docusaurus/core': 3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.1)(postcss@8.5.16))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3) + '@docusaurus/types': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.16))(esbuild@0.28.1)(html-minifier-terser@7.2.0)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@docusaurus/utils-validation': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) react: 19.2.7 react-dom: 19.2.7(react@19.2.7) tslib: 2.8.1 @@ -17270,11 +17778,11 @@ snapshots: - utf-8-validate - webpack-cli - '@docusaurus/plugin-google-gtag@3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.0)(postcss@8.5.15))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3)': + '@docusaurus/plugin-google-gtag@3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.1)(postcss@8.5.16))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3)': dependencies: - '@docusaurus/core': 3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.0)(postcss@8.5.15))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3) - '@docusaurus/types': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.15))(esbuild@0.28.0)(html-minifier-terser@7.2.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@docusaurus/utils-validation': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@docusaurus/core': 3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.1)(postcss@8.5.16))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3) + '@docusaurus/types': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.16))(esbuild@0.28.1)(html-minifier-terser@7.2.0)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@docusaurus/utils-validation': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@types/gtag.js': 0.0.20 react: 19.2.7 react-dom: 19.2.7(react@19.2.7) @@ -17303,11 +17811,11 @@ snapshots: - utf-8-validate - webpack-cli - '@docusaurus/plugin-google-tag-manager@3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.0)(postcss@8.5.15))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3)': + '@docusaurus/plugin-google-tag-manager@3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.1)(postcss@8.5.16))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3)': dependencies: - '@docusaurus/core': 3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.0)(postcss@8.5.15))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3) - '@docusaurus/types': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.15))(esbuild@0.28.0)(html-minifier-terser@7.2.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@docusaurus/utils-validation': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@docusaurus/core': 3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.1)(postcss@8.5.16))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3) + '@docusaurus/types': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.16))(esbuild@0.28.1)(html-minifier-terser@7.2.0)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@docusaurus/utils-validation': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) react: 19.2.7 react-dom: 19.2.7(react@19.2.7) tslib: 2.8.1 @@ -17335,14 +17843,14 @@ snapshots: - utf-8-validate - webpack-cli - '@docusaurus/plugin-sitemap@3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.0)(postcss@8.5.15))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3)': + '@docusaurus/plugin-sitemap@3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.1)(postcss@8.5.16))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3)': dependencies: - '@docusaurus/core': 3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.0)(postcss@8.5.15))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3) + '@docusaurus/core': 3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.1)(postcss@8.5.16))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3) '@docusaurus/logger': 3.10.1 - '@docusaurus/types': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.15))(esbuild@0.28.0)(html-minifier-terser@7.2.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@docusaurus/utils': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.15))(esbuild@0.28.0)(html-minifier-terser@7.2.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@docusaurus/utils-common': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.15))(esbuild@0.28.0)(html-minifier-terser@7.2.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@docusaurus/utils-validation': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@docusaurus/types': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.16))(esbuild@0.28.1)(html-minifier-terser@7.2.0)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@docusaurus/utils': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.16))(esbuild@0.28.1)(html-minifier-terser@7.2.0)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@docusaurus/utils-common': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.16))(esbuild@0.28.1)(html-minifier-terser@7.2.0)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@docusaurus/utils-validation': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) fs-extra: 11.2.0 react: 19.2.7 react-dom: 19.2.7(react@19.2.7) @@ -17372,18 +17880,18 @@ snapshots: - utf-8-validate - webpack-cli - '@docusaurus/plugin-svgr@3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.0)(postcss@8.5.15))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3)': + '@docusaurus/plugin-svgr@3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.1)(postcss@8.5.16))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3)': dependencies: - '@docusaurus/core': 3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.0)(postcss@8.5.15))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3) - '@docusaurus/types': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.15))(esbuild@0.28.0)(html-minifier-terser@7.2.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@docusaurus/utils': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.15))(esbuild@0.28.0)(html-minifier-terser@7.2.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@docusaurus/utils-validation': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@docusaurus/core': 3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.1)(postcss@8.5.16))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3) + '@docusaurus/types': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.16))(esbuild@0.28.1)(html-minifier-terser@7.2.0)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@docusaurus/utils': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.16))(esbuild@0.28.1)(html-minifier-terser@7.2.0)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@docusaurus/utils-validation': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@svgr/core': 8.1.0(typescript@6.0.3) '@svgr/webpack': 8.1.0(typescript@6.0.3) react: 19.2.7 react-dom: 19.2.7(react@19.2.7) tslib: 2.8.1 - webpack: 5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.15))(esbuild@0.28.0)(html-minifier-terser@7.2.0)(postcss@8.5.15) + webpack: 5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.16))(esbuild@0.28.1)(html-minifier-terser@7.2.0)(postcss@8.5.16) transitivePeerDependencies: - '@docusaurus/faster' - '@mdx-js/react' @@ -17408,23 +17916,23 @@ snapshots: - utf-8-validate - webpack-cli - '@docusaurus/preset-classic@3.10.1(@algolia/client-search@5.53.0)(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.0)(postcss@8.5.15))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(@types/react@19.2.17)(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(search-insights@2.17.3)(typescript@6.0.3)': + '@docusaurus/preset-classic@3.10.1(@algolia/client-search@5.53.0)(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.1)(postcss@8.5.16))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(@types/react@19.2.17)(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(search-insights@2.17.3)(typescript@6.0.3)': dependencies: - '@docusaurus/core': 3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.0)(postcss@8.5.15))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3) - '@docusaurus/plugin-content-blog': 3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.0)(postcss@8.5.15))(@docusaurus/plugin-content-docs@3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.0)(postcss@8.5.15))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3) - '@docusaurus/plugin-content-docs': 3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.0)(postcss@8.5.15))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3) - '@docusaurus/plugin-content-pages': 3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.0)(postcss@8.5.15))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3) - '@docusaurus/plugin-css-cascade-layers': 3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.0)(postcss@8.5.15))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3) - '@docusaurus/plugin-debug': 3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.0)(postcss@8.5.15))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3) - '@docusaurus/plugin-google-analytics': 3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.0)(postcss@8.5.15))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3) - '@docusaurus/plugin-google-gtag': 3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.0)(postcss@8.5.15))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3) - '@docusaurus/plugin-google-tag-manager': 3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.0)(postcss@8.5.15))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3) - '@docusaurus/plugin-sitemap': 3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.0)(postcss@8.5.15))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3) - '@docusaurus/plugin-svgr': 3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.0)(postcss@8.5.15))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3) - '@docusaurus/theme-classic': 3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.0)(postcss@8.5.15))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(@types/react@19.2.17)(esbuild@0.28.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3) - '@docusaurus/theme-common': 3.10.1(@docusaurus/plugin-content-docs@3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.0)(postcss@8.5.15))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@docusaurus/theme-search-algolia': 3.10.1(@algolia/client-search@5.53.0)(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.0)(postcss@8.5.15))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(@types/react@19.2.17)(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(search-insights@2.17.3)(typescript@6.0.3) - '@docusaurus/types': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.15))(esbuild@0.28.0)(html-minifier-terser@7.2.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@docusaurus/core': 3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.1)(postcss@8.5.16))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3) + '@docusaurus/plugin-content-blog': 3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.1)(postcss@8.5.16))(@docusaurus/plugin-content-docs@3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.1)(postcss@8.5.16))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3) + '@docusaurus/plugin-content-docs': 3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.1)(postcss@8.5.16))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3) + '@docusaurus/plugin-content-pages': 3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.1)(postcss@8.5.16))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3) + '@docusaurus/plugin-css-cascade-layers': 3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.1)(postcss@8.5.16))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3) + '@docusaurus/plugin-debug': 3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.1)(postcss@8.5.16))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3) + '@docusaurus/plugin-google-analytics': 3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.1)(postcss@8.5.16))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3) + '@docusaurus/plugin-google-gtag': 3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.1)(postcss@8.5.16))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3) + '@docusaurus/plugin-google-tag-manager': 3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.1)(postcss@8.5.16))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3) + '@docusaurus/plugin-sitemap': 3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.1)(postcss@8.5.16))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3) + '@docusaurus/plugin-svgr': 3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.1)(postcss@8.5.16))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3) + '@docusaurus/theme-classic': 3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.1)(postcss@8.5.16))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(@types/react@19.2.17)(esbuild@0.28.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3) + '@docusaurus/theme-common': 3.10.1(@docusaurus/plugin-content-docs@3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.1)(postcss@8.5.16))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@docusaurus/theme-search-algolia': 3.10.1(@algolia/client-search@5.53.0)(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.1)(postcss@8.5.16))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(@types/react@19.2.17)(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(search-insights@2.17.3)(typescript@6.0.3) + '@docusaurus/types': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.16))(esbuild@0.28.1)(html-minifier-terser@7.2.0)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) react: 19.2.7 react-dom: 19.2.7(react@19.2.7) transitivePeerDependencies: @@ -17459,21 +17967,21 @@ snapshots: '@types/react': 19.2.17 react: 19.2.7 - '@docusaurus/theme-classic@3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.0)(postcss@8.5.15))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(@types/react@19.2.17)(esbuild@0.28.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3)': + '@docusaurus/theme-classic@3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.1)(postcss@8.5.16))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(@types/react@19.2.17)(esbuild@0.28.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3)': dependencies: - '@docusaurus/core': 3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.0)(postcss@8.5.15))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3) + '@docusaurus/core': 3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.1)(postcss@8.5.16))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3) '@docusaurus/logger': 3.10.1 - '@docusaurus/mdx-loader': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@docusaurus/module-type-aliases': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@docusaurus/plugin-content-blog': 3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.0)(postcss@8.5.15))(@docusaurus/plugin-content-docs@3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.0)(postcss@8.5.15))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3) - '@docusaurus/plugin-content-docs': 3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.0)(postcss@8.5.15))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3) - '@docusaurus/plugin-content-pages': 3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.0)(postcss@8.5.15))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3) - '@docusaurus/theme-common': 3.10.1(@docusaurus/plugin-content-docs@3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.0)(postcss@8.5.15))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@docusaurus/mdx-loader': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@docusaurus/module-type-aliases': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@docusaurus/plugin-content-blog': 3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.1)(postcss@8.5.16))(@docusaurus/plugin-content-docs@3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.1)(postcss@8.5.16))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3) + '@docusaurus/plugin-content-docs': 3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.1)(postcss@8.5.16))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3) + '@docusaurus/plugin-content-pages': 3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.1)(postcss@8.5.16))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3) + '@docusaurus/theme-common': 3.10.1(@docusaurus/plugin-content-docs@3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.1)(postcss@8.5.16))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@docusaurus/theme-translations': 3.10.1 - '@docusaurus/types': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.15))(esbuild@0.28.0)(html-minifier-terser@7.2.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@docusaurus/utils': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.15))(esbuild@0.28.0)(html-minifier-terser@7.2.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@docusaurus/utils-common': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.15))(esbuild@0.28.0)(html-minifier-terser@7.2.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@docusaurus/utils-validation': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@docusaurus/types': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@docusaurus/utils': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@docusaurus/utils-common': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@docusaurus/utils-validation': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@mdx-js/react': 3.1.1(@types/react@19.2.17)(react@19.2.7) clsx: 2.1.1 copy-text-to-clipboard: 3.2.2 @@ -17512,13 +18020,13 @@ snapshots: - utf-8-validate - webpack-cli - '@docusaurus/theme-common@3.10.1(@docusaurus/plugin-content-docs@3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.0)(postcss@8.5.15))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + '@docusaurus/theme-common@3.10.1(@docusaurus/plugin-content-docs@3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.1)(postcss@8.5.16))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': dependencies: - '@docusaurus/mdx-loader': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@docusaurus/module-type-aliases': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@docusaurus/plugin-content-docs': 3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.0)(postcss@8.5.15))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3) - '@docusaurus/utils': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.15))(esbuild@0.28.0)(html-minifier-terser@7.2.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@docusaurus/utils-common': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.15))(esbuild@0.28.0)(html-minifier-terser@7.2.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@docusaurus/mdx-loader': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@docusaurus/module-type-aliases': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@docusaurus/plugin-content-docs': 3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.1)(postcss@8.5.16))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3) + '@docusaurus/utils': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@docusaurus/utils-common': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@types/history': 4.7.11 '@types/react': 19.2.17 '@types/react-router-config': 5.0.11 @@ -17545,13 +18053,46 @@ snapshots: - uglify-js - webpack-cli - '@docusaurus/theme-mermaid@3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.0)(postcss@8.5.15))(@docusaurus/plugin-content-docs@3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.0)(postcss@8.5.15))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3)': + '@docusaurus/theme-common@3.10.1(@docusaurus/plugin-content-docs@3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.1)(postcss@8.5.16))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': dependencies: - '@docusaurus/core': 3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.0)(postcss@8.5.15))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3) - '@docusaurus/module-type-aliases': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@docusaurus/theme-common': 3.10.1(@docusaurus/plugin-content-docs@3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.0)(postcss@8.5.15))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@docusaurus/types': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.15))(esbuild@0.28.0)(html-minifier-terser@7.2.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@docusaurus/utils-validation': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@docusaurus/mdx-loader': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@docusaurus/module-type-aliases': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@docusaurus/plugin-content-docs': 3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.1)(postcss@8.5.16))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3) + '@docusaurus/utils': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.16))(esbuild@0.28.1)(html-minifier-terser@7.2.0)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@docusaurus/utils-common': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.16))(esbuild@0.28.1)(html-minifier-terser@7.2.0)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@types/history': 4.7.11 + '@types/react': 19.2.17 + '@types/react-router-config': 5.0.11 + clsx: 2.1.1 + parse-numeric-range: 1.3.0 + prism-react-renderer: 2.4.1(react@19.2.7) + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) + tslib: 2.8.1 + utility-types: 3.11.0 + transitivePeerDependencies: + - '@minify-html/node' + - '@swc/core' + - '@swc/css' + - '@swc/html' + - clean-css + - cssnano + - csso + - esbuild + - html-minifier-terser + - lightningcss + - postcss + - supports-color + - uglify-js + - webpack-cli + + '@docusaurus/theme-mermaid@3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.1)(postcss@8.5.16))(@docusaurus/plugin-content-docs@3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.1)(postcss@8.5.16))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3)': + dependencies: + '@docusaurus/core': 3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.1)(postcss@8.5.16))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3) + '@docusaurus/module-type-aliases': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@docusaurus/theme-common': 3.10.1(@docusaurus/plugin-content-docs@3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.1)(postcss@8.5.16))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@docusaurus/types': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.16))(esbuild@0.28.1)(html-minifier-terser@7.2.0)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@docusaurus/utils-validation': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) mermaid: 11.15.0 react: 19.2.7 react-dom: 19.2.7(react@19.2.7) @@ -17581,17 +18122,17 @@ snapshots: - utf-8-validate - webpack-cli - '@docusaurus/theme-search-algolia@3.10.1(@algolia/client-search@5.53.0)(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.0)(postcss@8.5.15))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(@types/react@19.2.17)(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(search-insights@2.17.3)(typescript@6.0.3)': + '@docusaurus/theme-search-algolia@3.10.1(@algolia/client-search@5.53.0)(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.1)(postcss@8.5.16))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(@types/react@19.2.17)(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(search-insights@2.17.3)(typescript@6.0.3)': dependencies: '@algolia/autocomplete-core': 1.19.8(@algolia/client-search@5.53.0)(algoliasearch@5.53.0)(search-insights@2.17.3) '@docsearch/react': 4.6.3(@algolia/client-search@5.53.0)(@types/react@19.2.17)(algoliasearch@5.53.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(search-insights@2.17.3) - '@docusaurus/core': 3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.0)(postcss@8.5.15))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3) + '@docusaurus/core': 3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.1)(postcss@8.5.16))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3) '@docusaurus/logger': 3.10.1 - '@docusaurus/plugin-content-docs': 3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.0)(postcss@8.5.15))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3) - '@docusaurus/theme-common': 3.10.1(@docusaurus/plugin-content-docs@3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.0)(postcss@8.5.15))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@docusaurus/plugin-content-docs': 3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.1)(postcss@8.5.16))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3) + '@docusaurus/theme-common': 3.10.1(@docusaurus/plugin-content-docs@3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.1)(postcss@8.5.16))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@docusaurus/theme-translations': 3.10.1 - '@docusaurus/utils': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.15))(esbuild@0.28.0)(html-minifier-terser@7.2.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@docusaurus/utils-validation': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@docusaurus/utils': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.16))(esbuild@0.28.1)(html-minifier-terser@7.2.0)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@docusaurus/utils-validation': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) algoliasearch: 5.53.0 algoliasearch-helper: 3.29.1(algoliasearch@5.53.0) clsx: 2.1.1 @@ -17636,7 +18177,7 @@ snapshots: '@docusaurus/tsconfig@3.10.1': {} - '@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.15))(esbuild@0.28.0)(html-minifier-terser@7.2.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + '@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.16))(esbuild@0.28.1)(html-minifier-terser@7.2.0)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': dependencies: '@mdx-js/mdx': 3.1.1 '@types/history': 4.7.11 @@ -17648,7 +18189,7 @@ snapshots: react-dom: 19.2.7(react@19.2.7) react-helmet-async: '@slorber/react-helmet-async@1.3.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7)' utility-types: 3.11.0 - webpack: 5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.15))(esbuild@0.28.0)(html-minifier-terser@7.2.0)(postcss@8.5.15) + webpack: 5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.16))(esbuild@0.28.1)(html-minifier-terser@7.2.0)(postcss@8.5.16) webpack-merge: 5.10.0 transitivePeerDependencies: - '@minify-html/node' @@ -17666,9 +18207,39 @@ snapshots: - uglify-js - webpack-cli - '@docusaurus/utils-common@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.15))(esbuild@0.28.0)(html-minifier-terser@7.2.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + '@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': dependencies: - '@docusaurus/types': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.15))(esbuild@0.28.0)(html-minifier-terser@7.2.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@mdx-js/mdx': 3.1.1 + '@types/history': 4.7.11 + '@types/mdast': 4.0.4 + '@types/react': 19.2.17 + commander: 5.1.0 + joi: 17.13.3 + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) + react-helmet-async: '@slorber/react-helmet-async@1.3.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7)' + utility-types: 3.11.0 + webpack: 5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.15) + webpack-merge: 5.10.0 + transitivePeerDependencies: + - '@minify-html/node' + - '@swc/core' + - '@swc/css' + - '@swc/html' + - clean-css + - cssnano + - csso + - esbuild + - html-minifier-terser + - lightningcss + - postcss + - supports-color + - uglify-js + - webpack-cli + + '@docusaurus/utils-common@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.16))(esbuild@0.28.1)(html-minifier-terser@7.2.0)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + dependencies: + '@docusaurus/types': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.16))(esbuild@0.28.1)(html-minifier-terser@7.2.0)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) tslib: 2.8.1 transitivePeerDependencies: - '@minify-html/node' @@ -17688,11 +18259,33 @@ snapshots: - uglify-js - webpack-cli - '@docusaurus/utils-validation@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + '@docusaurus/utils-common@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + dependencies: + '@docusaurus/types': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + tslib: 2.8.1 + transitivePeerDependencies: + - '@minify-html/node' + - '@swc/core' + - '@swc/css' + - '@swc/html' + - clean-css + - cssnano + - csso + - esbuild + - html-minifier-terser + - lightningcss + - postcss + - react + - react-dom + - supports-color + - uglify-js + - webpack-cli + + '@docusaurus/utils-validation@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': dependencies: '@docusaurus/logger': 3.10.1 - '@docusaurus/utils': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.15))(esbuild@0.28.0)(html-minifier-terser@7.2.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@docusaurus/utils-common': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.15))(esbuild@0.28.0)(html-minifier-terser@7.2.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@docusaurus/utils': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@docusaurus/utils-common': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) fs-extra: 11.2.0 joi: 17.13.3 js-yaml: 4.1.1 @@ -17716,14 +18309,42 @@ snapshots: - uglify-js - webpack-cli - '@docusaurus/utils@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.15))(esbuild@0.28.0)(html-minifier-terser@7.2.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + '@docusaurus/utils-validation@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': dependencies: '@docusaurus/logger': 3.10.1 - '@docusaurus/types': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.15))(esbuild@0.28.0)(html-minifier-terser@7.2.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@docusaurus/utils-common': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.15))(esbuild@0.28.0)(html-minifier-terser@7.2.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@docusaurus/utils': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.16))(esbuild@0.28.1)(html-minifier-terser@7.2.0)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@docusaurus/utils-common': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.16))(esbuild@0.28.1)(html-minifier-terser@7.2.0)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + fs-extra: 11.2.0 + joi: 17.13.3 + js-yaml: 4.1.1 + lodash: 4.18.1 + tslib: 2.8.1 + transitivePeerDependencies: + - '@minify-html/node' + - '@swc/core' + - '@swc/css' + - '@swc/html' + - clean-css + - cssnano + - csso + - esbuild + - html-minifier-terser + - lightningcss + - postcss + - react + - react-dom + - supports-color + - uglify-js + - webpack-cli + + '@docusaurus/utils@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.16))(esbuild@0.28.1)(html-minifier-terser@7.2.0)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + dependencies: + '@docusaurus/logger': 3.10.1 + '@docusaurus/types': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.16))(esbuild@0.28.1)(html-minifier-terser@7.2.0)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@docusaurus/utils-common': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.16))(esbuild@0.28.1)(html-minifier-terser@7.2.0)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) escape-string-regexp: 4.0.0 execa: 5.1.1 - file-loader: 6.2.0(webpack@5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)) + file-loader: 6.2.0(webpack@5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)) fs-extra: 11.2.0 github-slugger: 1.5.0 globby: 11.1.0 @@ -17736,9 +18357,50 @@ snapshots: prompts: 2.4.2 resolve-pathname: 3.0.0 tslib: 2.8.1 - url-loader: 4.1.1(file-loader@6.2.0(webpack@5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)))(webpack@5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)) + url-loader: 4.1.1(file-loader@6.2.0(webpack@5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)))(webpack@5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)) utility-types: 3.11.0 - webpack: 5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.15))(esbuild@0.28.0)(html-minifier-terser@7.2.0)(postcss@8.5.15) + webpack: 5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.16))(esbuild@0.28.1)(html-minifier-terser@7.2.0)(postcss@8.5.16) + transitivePeerDependencies: + - '@minify-html/node' + - '@swc/core' + - '@swc/css' + - '@swc/html' + - clean-css + - cssnano + - csso + - esbuild + - html-minifier-terser + - lightningcss + - postcss + - react + - react-dom + - supports-color + - uglify-js + - webpack-cli + + '@docusaurus/utils@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + dependencies: + '@docusaurus/logger': 3.10.1 + '@docusaurus/types': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@docusaurus/utils-common': 3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + escape-string-regexp: 4.0.0 + execa: 5.1.1 + file-loader: 6.2.0(webpack@5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)) + fs-extra: 11.2.0 + github-slugger: 1.5.0 + globby: 11.1.0 + gray-matter: 4.0.3 + jiti: 1.21.7 + js-yaml: 4.1.1 + lodash: 4.18.1 + micromatch: 4.0.8 + p-queue: 6.6.2 + prompts: 2.4.2 + resolve-pathname: 3.0.0 + tslib: 2.8.1 + url-loader: 4.1.1(file-loader@6.2.0(webpack@5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)))(webpack@5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)) + utility-types: 3.11.0 + webpack: 5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.15) transitivePeerDependencies: - '@minify-html/node' - '@swc/core' @@ -17761,13 +18423,7 @@ snapshots: '@drizzle-team/brocli@0.12.0': {} - '@emnapi/core@1.10.0': - dependencies: - '@emnapi/wasi-threads': 1.2.1 - tslib: 2.8.1 - optional: true - - '@emnapi/core@1.11.0': + '@emnapi/core@1.11.1': dependencies: '@emnapi/wasi-threads': 1.2.2 tslib: 2.8.1 @@ -17778,7 +18434,7 @@ snapshots: tslib: 2.8.1 optional: true - '@emnapi/wasi-threads@1.2.1': + '@emnapi/runtime@1.11.1': dependencies: tslib: 2.8.1 optional: true @@ -17792,7 +18448,7 @@ snapshots: '@esbuild-kit/core-utils@3.3.2': dependencies: - esbuild: 0.28.0 + esbuild: 0.28.1 source-map-support: 0.5.21 '@esbuild-kit/esm-loader@2.6.5': @@ -17809,6 +18465,9 @@ snapshots: '@esbuild/aix-ppc64@0.28.0': optional: true + '@esbuild/aix-ppc64@0.28.1': + optional: true + '@esbuild/android-arm64@0.25.12': optional: true @@ -17818,6 +18477,9 @@ snapshots: '@esbuild/android-arm64@0.28.0': optional: true + '@esbuild/android-arm64@0.28.1': + optional: true + '@esbuild/android-arm@0.25.12': optional: true @@ -17827,6 +18489,9 @@ snapshots: '@esbuild/android-arm@0.28.0': optional: true + '@esbuild/android-arm@0.28.1': + optional: true + '@esbuild/android-x64@0.25.12': optional: true @@ -17836,6 +18501,9 @@ snapshots: '@esbuild/android-x64@0.28.0': optional: true + '@esbuild/android-x64@0.28.1': + optional: true + '@esbuild/darwin-arm64@0.25.12': optional: true @@ -17845,6 +18513,9 @@ snapshots: '@esbuild/darwin-arm64@0.28.0': optional: true + '@esbuild/darwin-arm64@0.28.1': + optional: true + '@esbuild/darwin-x64@0.25.12': optional: true @@ -17854,6 +18525,9 @@ snapshots: '@esbuild/darwin-x64@0.28.0': optional: true + '@esbuild/darwin-x64@0.28.1': + optional: true + '@esbuild/freebsd-arm64@0.25.12': optional: true @@ -17863,6 +18537,9 @@ snapshots: '@esbuild/freebsd-arm64@0.28.0': optional: true + '@esbuild/freebsd-arm64@0.28.1': + optional: true + '@esbuild/freebsd-x64@0.25.12': optional: true @@ -17872,6 +18549,9 @@ snapshots: '@esbuild/freebsd-x64@0.28.0': optional: true + '@esbuild/freebsd-x64@0.28.1': + optional: true + '@esbuild/linux-arm64@0.25.12': optional: true @@ -17881,6 +18561,9 @@ snapshots: '@esbuild/linux-arm64@0.28.0': optional: true + '@esbuild/linux-arm64@0.28.1': + optional: true + '@esbuild/linux-arm@0.25.12': optional: true @@ -17890,6 +18573,9 @@ snapshots: '@esbuild/linux-arm@0.28.0': optional: true + '@esbuild/linux-arm@0.28.1': + optional: true + '@esbuild/linux-ia32@0.25.12': optional: true @@ -17899,6 +18585,9 @@ snapshots: '@esbuild/linux-ia32@0.28.0': optional: true + '@esbuild/linux-ia32@0.28.1': + optional: true + '@esbuild/linux-loong64@0.25.12': optional: true @@ -17908,6 +18597,9 @@ snapshots: '@esbuild/linux-loong64@0.28.0': optional: true + '@esbuild/linux-loong64@0.28.1': + optional: true + '@esbuild/linux-mips64el@0.25.12': optional: true @@ -17917,6 +18609,9 @@ snapshots: '@esbuild/linux-mips64el@0.28.0': optional: true + '@esbuild/linux-mips64el@0.28.1': + optional: true + '@esbuild/linux-ppc64@0.25.12': optional: true @@ -17926,6 +18621,9 @@ snapshots: '@esbuild/linux-ppc64@0.28.0': optional: true + '@esbuild/linux-ppc64@0.28.1': + optional: true + '@esbuild/linux-riscv64@0.25.12': optional: true @@ -17935,6 +18633,9 @@ snapshots: '@esbuild/linux-riscv64@0.28.0': optional: true + '@esbuild/linux-riscv64@0.28.1': + optional: true + '@esbuild/linux-s390x@0.25.12': optional: true @@ -17944,6 +18645,9 @@ snapshots: '@esbuild/linux-s390x@0.28.0': optional: true + '@esbuild/linux-s390x@0.28.1': + optional: true + '@esbuild/linux-x64@0.25.12': optional: true @@ -17953,6 +18657,9 @@ snapshots: '@esbuild/linux-x64@0.28.0': optional: true + '@esbuild/linux-x64@0.28.1': + optional: true + '@esbuild/netbsd-arm64@0.25.12': optional: true @@ -17962,6 +18669,9 @@ snapshots: '@esbuild/netbsd-arm64@0.28.0': optional: true + '@esbuild/netbsd-arm64@0.28.1': + optional: true + '@esbuild/netbsd-x64@0.25.12': optional: true @@ -17971,6 +18681,9 @@ snapshots: '@esbuild/netbsd-x64@0.28.0': optional: true + '@esbuild/netbsd-x64@0.28.1': + optional: true + '@esbuild/openbsd-arm64@0.25.12': optional: true @@ -17980,6 +18693,9 @@ snapshots: '@esbuild/openbsd-arm64@0.28.0': optional: true + '@esbuild/openbsd-arm64@0.28.1': + optional: true + '@esbuild/openbsd-x64@0.25.12': optional: true @@ -17989,6 +18705,9 @@ snapshots: '@esbuild/openbsd-x64@0.28.0': optional: true + '@esbuild/openbsd-x64@0.28.1': + optional: true + '@esbuild/openharmony-arm64@0.25.12': optional: true @@ -17998,6 +18717,9 @@ snapshots: '@esbuild/openharmony-arm64@0.28.0': optional: true + '@esbuild/openharmony-arm64@0.28.1': + optional: true + '@esbuild/sunos-x64@0.25.12': optional: true @@ -18007,6 +18729,9 @@ snapshots: '@esbuild/sunos-x64@0.28.0': optional: true + '@esbuild/sunos-x64@0.28.1': + optional: true + '@esbuild/win32-arm64@0.25.12': optional: true @@ -18016,6 +18741,9 @@ snapshots: '@esbuild/win32-arm64@0.28.0': optional: true + '@esbuild/win32-arm64@0.28.1': + optional: true + '@esbuild/win32-ia32@0.25.12': optional: true @@ -18025,6 +18753,9 @@ snapshots: '@esbuild/win32-ia32@0.28.0': optional: true + '@esbuild/win32-ia32@0.28.1': + optional: true + '@esbuild/win32-x64@0.25.12': optional: true @@ -18034,6 +18765,9 @@ snapshots: '@esbuild/win32-x64@0.28.0': optional: true + '@esbuild/win32-x64@0.28.1': + optional: true + '@exodus/bytes@1.15.0(@noble/hashes@2.0.1)': optionalDependencies: '@noble/hashes': 2.0.1 @@ -18151,32 +18885,14 @@ snapshots: '@formatjs/fast-memoize': 3.1.0 tslib: 2.8.1 - '@gfazioli/mantine-onboarding-tour@4.0.5(@mantine/core@9.3.2(@mantine/hooks@9.3.2(react@19.2.7))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@mantine/hooks@9.3.2(react@19.2.7))(@tabler/icons-react@3.44.0(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + '@gfazioli/mantine-onboarding-tour@4.0.6(@mantine/core@9.4.1(@mantine/hooks@9.4.1(react@19.2.7))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@mantine/hooks@9.4.1(react@19.2.7))(@tabler/icons-react@3.44.0(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': dependencies: - '@mantine/core': 9.3.2(@mantine/hooks@9.3.2(react@19.2.7))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@mantine/hooks': 9.3.2(react@19.2.7) + '@mantine/core': 9.4.1(@mantine/hooks@9.4.1(react@19.2.7))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@mantine/hooks': 9.4.1(react@19.2.7) '@tabler/icons-react': 3.44.0(react@19.2.7) react: 19.2.7 react-dom: 19.2.7(react@19.2.7) - '@gitbeaker/core@43.8.0': - dependencies: - '@gitbeaker/requester-utils': 43.8.0 - qs: 6.15.2 - xcase: 2.0.1 - - '@gitbeaker/requester-utils@43.8.0': - dependencies: - picomatch-browser: 2.2.6 - qs: 6.15.2 - rate-limiter-flexible: 8.1.0 - xcase: 2.0.1 - - '@gitbeaker/rest@43.8.0': - dependencies: - '@gitbeaker/core': 43.8.0 - '@gitbeaker/requester-utils': 43.8.0 - '@grpc/grpc-js@1.12.5': dependencies: '@grpc/proto-loader': 0.7.13 @@ -18330,7 +19046,7 @@ snapshots: '@img/sharp-win32-x64@0.34.5': optional: true - '@immich/sdk@2.7.5': + '@immich/sdk@3.0.0': dependencies: '@oazapfts/runtime': 1.2.0 @@ -18780,91 +19496,91 @@ snapshots: js-base64: 3.7.8 optional: true - '@mantine/charts@9.3.2(@mantine/core@9.3.2(@mantine/hooks@9.3.2(react@19.2.7))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@mantine/hooks@9.3.2(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(recharts@3.8.1(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react-is@19.2.7)(react@19.2.7)(redux@5.0.1))': + '@mantine/charts@9.4.1(@mantine/core@9.4.1(@mantine/hooks@9.4.1(react@19.2.7))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@mantine/hooks@9.4.1(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(recharts@3.9.0(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react-is@19.2.7)(react@19.2.7)(redux@5.0.1))': dependencies: - '@mantine/core': 9.3.2(@mantine/hooks@9.3.2(react@19.2.7))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@mantine/hooks': 9.3.2(react@19.2.7) + '@mantine/core': 9.4.1(@mantine/hooks@9.4.1(react@19.2.7))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@mantine/hooks': 9.4.1(react@19.2.7) react: 19.2.7 react-dom: 19.2.7(react@19.2.7) - recharts: 3.8.1(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react-is@19.2.7)(react@19.2.7)(redux@5.0.1) + recharts: 3.9.0(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react-is@19.2.7)(react@19.2.7)(redux@5.0.1) - '@mantine/colors-generator@9.3.2(chroma-js@3.2.0)': + '@mantine/colors-generator@9.4.1(chroma-js@3.2.0)': dependencies: chroma-js: 3.2.0 - '@mantine/core@9.3.2(@mantine/hooks@9.3.2(react@19.2.7))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + '@mantine/core@9.4.1(@mantine/hooks@9.4.1(react@19.2.7))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': dependencies: '@floating-ui/react': 0.27.19(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@mantine/hooks': 9.3.2(react@19.2.7) + '@mantine/hooks': 9.4.1(react@19.2.7) clsx: 2.1.1 react: 19.2.7 react-dom: 19.2.7(react@19.2.7) react-number-format: 5.4.5(react-dom@19.2.7(react@19.2.7))(react@19.2.7) react-remove-scroll: 2.7.2(@types/react@19.2.17)(react@19.2.7) - type-fest: 5.6.0 + type-fest: 5.7.0 transitivePeerDependencies: - '@types/react' - '@mantine/dates@9.3.2(@mantine/core@9.3.2(@mantine/hooks@9.3.2(react@19.2.7))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@mantine/hooks@9.3.2(react@19.2.7))(dayjs@1.11.21)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + '@mantine/dates@9.4.1(@mantine/core@9.4.1(@mantine/hooks@9.4.1(react@19.2.7))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@mantine/hooks@9.4.1(react@19.2.7))(dayjs@1.11.21)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': dependencies: - '@mantine/core': 9.3.2(@mantine/hooks@9.3.2(react@19.2.7))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@mantine/hooks': 9.3.2(react@19.2.7) + '@mantine/core': 9.4.1(@mantine/hooks@9.4.1(react@19.2.7))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@mantine/hooks': 9.4.1(react@19.2.7) clsx: 2.1.1 dayjs: 1.11.21 react: 19.2.7 react-dom: 19.2.7(react@19.2.7) - '@mantine/dropzone@9.3.2(@mantine/core@9.3.2(@mantine/hooks@9.3.2(react@19.2.7))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@mantine/hooks@9.3.2(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + '@mantine/dropzone@9.4.1(@mantine/core@9.4.1(@mantine/hooks@9.4.1(react@19.2.7))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@mantine/hooks@9.4.1(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': dependencies: - '@mantine/core': 9.3.2(@mantine/hooks@9.3.2(react@19.2.7))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@mantine/hooks': 9.3.2(react@19.2.7) + '@mantine/core': 9.4.1(@mantine/hooks@9.4.1(react@19.2.7))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@mantine/hooks': 9.4.1(react@19.2.7) react: 19.2.7 react-dom: 19.2.7(react@19.2.7) react-dropzone: 15.0.0(react@19.2.7) - '@mantine/form@9.3.2(react@19.2.7)': + '@mantine/form@9.4.1(react@19.2.7)': dependencies: '@standard-schema/spec': 1.1.0 fast-deep-equal: 3.1.3 klona: 2.0.6 react: 19.2.7 - '@mantine/hooks@9.3.2(react@19.2.7)': + '@mantine/hooks@9.4.1(react@19.2.7)': dependencies: react: 19.2.7 - '@mantine/modals@9.3.2(@mantine/core@9.3.2(@mantine/hooks@9.3.2(react@19.2.7))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@mantine/hooks@9.3.2(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + '@mantine/modals@9.4.1(@mantine/core@9.4.1(@mantine/hooks@9.4.1(react@19.2.7))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@mantine/hooks@9.4.1(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': dependencies: - '@mantine/core': 9.3.2(@mantine/hooks@9.3.2(react@19.2.7))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@mantine/hooks': 9.3.2(react@19.2.7) + '@mantine/core': 9.4.1(@mantine/hooks@9.4.1(react@19.2.7))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@mantine/hooks': 9.4.1(react@19.2.7) react: 19.2.7 react-dom: 19.2.7(react@19.2.7) - '@mantine/notifications@9.3.2(@mantine/core@9.3.2(@mantine/hooks@9.3.2(react@19.2.7))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@mantine/hooks@9.3.2(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + '@mantine/notifications@9.4.1(@mantine/core@9.4.1(@mantine/hooks@9.4.1(react@19.2.7))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@mantine/hooks@9.4.1(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': dependencies: - '@mantine/core': 9.3.2(@mantine/hooks@9.3.2(react@19.2.7))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@mantine/hooks': 9.3.2(react@19.2.7) - '@mantine/store': 9.3.2(react@19.2.7) + '@mantine/core': 9.4.1(@mantine/hooks@9.4.1(react@19.2.7))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@mantine/hooks': 9.4.1(react@19.2.7) + '@mantine/store': 9.4.1(react@19.2.7) react: 19.2.7 react-dom: 19.2.7(react@19.2.7) react-transition-group: 4.4.5(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@mantine/spotlight@9.3.2(@mantine/core@9.3.2(@mantine/hooks@9.3.2(react@19.2.7))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@mantine/hooks@9.3.2(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + '@mantine/spotlight@9.4.1(@mantine/core@9.4.1(@mantine/hooks@9.4.1(react@19.2.7))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@mantine/hooks@9.4.1(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': dependencies: - '@mantine/core': 9.3.2(@mantine/hooks@9.3.2(react@19.2.7))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@mantine/hooks': 9.3.2(react@19.2.7) - '@mantine/store': 9.3.2(react@19.2.7) + '@mantine/core': 9.4.1(@mantine/hooks@9.4.1(react@19.2.7))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@mantine/hooks': 9.4.1(react@19.2.7) + '@mantine/store': 9.4.1(react@19.2.7) react: 19.2.7 react-dom: 19.2.7(react@19.2.7) - '@mantine/store@9.3.2(react@19.2.7)': + '@mantine/store@9.4.1(react@19.2.7)': dependencies: react: 19.2.7 - '@mantine/tiptap@9.3.2(@mantine/core@9.3.2(@mantine/hooks@9.3.2(react@19.2.7))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@mantine/hooks@9.3.2(react@19.2.7))(@tiptap/extension-link@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1))(@tiptap/react@3.27.1(@floating-ui/dom@1.7.6)(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1)(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + '@mantine/tiptap@9.4.1(@mantine/core@9.4.1(@mantine/hooks@9.4.1(react@19.2.7))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@mantine/hooks@9.4.1(react@19.2.7))(@tiptap/extension-link@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1))(@tiptap/react@3.27.1(@floating-ui/dom@1.7.6)(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1)(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': dependencies: - '@mantine/core': 9.3.2(@mantine/hooks@9.3.2(react@19.2.7))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@mantine/hooks': 9.3.2(react@19.2.7) + '@mantine/core': 9.4.1(@mantine/hooks@9.4.1(react@19.2.7))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@mantine/hooks': 9.4.1(react@19.2.7) '@tiptap/extension-link': 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1) '@tiptap/react': 3.27.1(@floating-ui/dom@1.7.6)(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1)(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) react: 19.2.7 @@ -18993,16 +19709,16 @@ snapshots: '@napi-rs/wasm-runtime@1.0.7': dependencies: - '@emnapi/core': 1.11.0 - '@emnapi/runtime': 1.10.0 - '@tybys/wasm-util': 0.10.2 + '@emnapi/core': 1.11.1 + '@emnapi/runtime': 1.11.1 + '@tybys/wasm-util': 0.10.3 optional: true - '@napi-rs/wasm-runtime@1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)': + '@napi-rs/wasm-runtime@1.1.6(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)': dependencies: - '@emnapi/core': 1.10.0 - '@emnapi/runtime': 1.10.0 - '@tybys/wasm-util': 0.10.2 + '@emnapi/core': 1.11.1 + '@emnapi/runtime': 1.11.1 + '@tybys/wasm-util': 0.10.3 optional: true '@next/env@16.2.9': {} @@ -19358,120 +20074,120 @@ snapshots: '@opentelemetry/semantic-conventions@1.41.1': {} - '@oxc-project/types@0.133.0': {} + '@oxc-project/types@0.137.0': {} - '@oxfmt/binding-android-arm-eabi@0.55.0': + '@oxfmt/binding-android-arm-eabi@0.56.0': optional: true - '@oxfmt/binding-android-arm64@0.55.0': + '@oxfmt/binding-android-arm64@0.56.0': optional: true - '@oxfmt/binding-darwin-arm64@0.55.0': + '@oxfmt/binding-darwin-arm64@0.56.0': optional: true - '@oxfmt/binding-darwin-x64@0.55.0': + '@oxfmt/binding-darwin-x64@0.56.0': optional: true - '@oxfmt/binding-freebsd-x64@0.55.0': + '@oxfmt/binding-freebsd-x64@0.56.0': optional: true - '@oxfmt/binding-linux-arm-gnueabihf@0.55.0': + '@oxfmt/binding-linux-arm-gnueabihf@0.56.0': optional: true - '@oxfmt/binding-linux-arm-musleabihf@0.55.0': + '@oxfmt/binding-linux-arm-musleabihf@0.56.0': optional: true - '@oxfmt/binding-linux-arm64-gnu@0.55.0': + '@oxfmt/binding-linux-arm64-gnu@0.56.0': optional: true - '@oxfmt/binding-linux-arm64-musl@0.55.0': + '@oxfmt/binding-linux-arm64-musl@0.56.0': optional: true - '@oxfmt/binding-linux-ppc64-gnu@0.55.0': + '@oxfmt/binding-linux-ppc64-gnu@0.56.0': optional: true - '@oxfmt/binding-linux-riscv64-gnu@0.55.0': + '@oxfmt/binding-linux-riscv64-gnu@0.56.0': optional: true - '@oxfmt/binding-linux-riscv64-musl@0.55.0': + '@oxfmt/binding-linux-riscv64-musl@0.56.0': optional: true - '@oxfmt/binding-linux-s390x-gnu@0.55.0': + '@oxfmt/binding-linux-s390x-gnu@0.56.0': optional: true - '@oxfmt/binding-linux-x64-gnu@0.55.0': + '@oxfmt/binding-linux-x64-gnu@0.56.0': optional: true - '@oxfmt/binding-linux-x64-musl@0.55.0': + '@oxfmt/binding-linux-x64-musl@0.56.0': optional: true - '@oxfmt/binding-openharmony-arm64@0.55.0': + '@oxfmt/binding-openharmony-arm64@0.56.0': optional: true - '@oxfmt/binding-win32-arm64-msvc@0.55.0': + '@oxfmt/binding-win32-arm64-msvc@0.56.0': optional: true - '@oxfmt/binding-win32-ia32-msvc@0.55.0': + '@oxfmt/binding-win32-ia32-msvc@0.56.0': optional: true - '@oxfmt/binding-win32-x64-msvc@0.55.0': + '@oxfmt/binding-win32-x64-msvc@0.56.0': optional: true - '@oxlint/binding-android-arm-eabi@1.70.0': + '@oxlint/binding-android-arm-eabi@1.71.0': optional: true - '@oxlint/binding-android-arm64@1.70.0': + '@oxlint/binding-android-arm64@1.71.0': optional: true - '@oxlint/binding-darwin-arm64@1.70.0': + '@oxlint/binding-darwin-arm64@1.71.0': optional: true - '@oxlint/binding-darwin-x64@1.70.0': + '@oxlint/binding-darwin-x64@1.71.0': optional: true - '@oxlint/binding-freebsd-x64@1.70.0': + '@oxlint/binding-freebsd-x64@1.71.0': optional: true - '@oxlint/binding-linux-arm-gnueabihf@1.70.0': + '@oxlint/binding-linux-arm-gnueabihf@1.71.0': optional: true - '@oxlint/binding-linux-arm-musleabihf@1.70.0': + '@oxlint/binding-linux-arm-musleabihf@1.71.0': optional: true - '@oxlint/binding-linux-arm64-gnu@1.70.0': + '@oxlint/binding-linux-arm64-gnu@1.71.0': optional: true - '@oxlint/binding-linux-arm64-musl@1.70.0': + '@oxlint/binding-linux-arm64-musl@1.71.0': optional: true - '@oxlint/binding-linux-ppc64-gnu@1.70.0': + '@oxlint/binding-linux-ppc64-gnu@1.71.0': optional: true - '@oxlint/binding-linux-riscv64-gnu@1.70.0': + '@oxlint/binding-linux-riscv64-gnu@1.71.0': optional: true - '@oxlint/binding-linux-riscv64-musl@1.70.0': + '@oxlint/binding-linux-riscv64-musl@1.71.0': optional: true - '@oxlint/binding-linux-s390x-gnu@1.70.0': + '@oxlint/binding-linux-s390x-gnu@1.71.0': optional: true - '@oxlint/binding-linux-x64-gnu@1.70.0': + '@oxlint/binding-linux-x64-gnu@1.71.0': optional: true - '@oxlint/binding-linux-x64-musl@1.70.0': + '@oxlint/binding-linux-x64-musl@1.71.0': optional: true - '@oxlint/binding-openharmony-arm64@1.70.0': + '@oxlint/binding-openharmony-arm64@1.71.0': optional: true - '@oxlint/binding-win32-arm64-msvc@1.70.0': + '@oxlint/binding-win32-arm64-msvc@1.71.0': optional: true - '@oxlint/binding-win32-ia32-msvc@1.70.0': + '@oxlint/binding-win32-ia32-msvc@1.71.0': optional: true - '@oxlint/binding-win32-x64-msvc@1.70.0': + '@oxlint/binding-win32-x64-msvc@1.71.0': optional: true '@panva/hkdf@1.2.1': {} @@ -19642,9 +20358,9 @@ snapshots: '@pkgjs/parseargs@0.11.0': optional: true - '@playwright/test@1.61.0': + '@playwright/test@1.61.1': dependencies: - playwright: 1.61.0 + playwright: 1.61.1 '@pnpm/config.env-replace@1.1.0': {} @@ -19660,11 +20376,11 @@ snapshots: '@polka/url@1.0.0-next.25': {} - '@posthog/core@1.35.3': + '@posthog/core@1.38.0': dependencies: - '@posthog/types': 1.390.2 + '@posthog/types': 1.391.1 - '@posthog/types@1.390.2': {} + '@posthog/types@1.391.1': {} '@protobufjs/aspromise@1.1.2': {} @@ -19768,53 +20484,53 @@ snapshots: '@codemirror/state': 6.6.0 '@codemirror/view': 6.43.0 - '@rolldown/binding-android-arm64@1.0.3': + '@rolldown/binding-android-arm64@1.1.3': optional: true - '@rolldown/binding-darwin-arm64@1.0.3': + '@rolldown/binding-darwin-arm64@1.1.3': optional: true - '@rolldown/binding-darwin-x64@1.0.3': + '@rolldown/binding-darwin-x64@1.1.3': optional: true - '@rolldown/binding-freebsd-x64@1.0.3': + '@rolldown/binding-freebsd-x64@1.1.3': optional: true - '@rolldown/binding-linux-arm-gnueabihf@1.0.3': + '@rolldown/binding-linux-arm-gnueabihf@1.1.3': optional: true - '@rolldown/binding-linux-arm64-gnu@1.0.3': + '@rolldown/binding-linux-arm64-gnu@1.1.3': optional: true - '@rolldown/binding-linux-arm64-musl@1.0.3': + '@rolldown/binding-linux-arm64-musl@1.1.3': optional: true - '@rolldown/binding-linux-ppc64-gnu@1.0.3': + '@rolldown/binding-linux-ppc64-gnu@1.1.3': optional: true - '@rolldown/binding-linux-s390x-gnu@1.0.3': + '@rolldown/binding-linux-s390x-gnu@1.1.3': optional: true - '@rolldown/binding-linux-x64-gnu@1.0.3': + '@rolldown/binding-linux-x64-gnu@1.1.3': optional: true - '@rolldown/binding-linux-x64-musl@1.0.3': + '@rolldown/binding-linux-x64-musl@1.1.3': optional: true - '@rolldown/binding-openharmony-arm64@1.0.3': + '@rolldown/binding-openharmony-arm64@1.1.3': optional: true - '@rolldown/binding-wasm32-wasi@1.0.3': + '@rolldown/binding-wasm32-wasi@1.1.3': dependencies: - '@emnapi/core': 1.10.0 - '@emnapi/runtime': 1.10.0 - '@napi-rs/wasm-runtime': 1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) + '@emnapi/core': 1.11.1 + '@emnapi/runtime': 1.11.1 + '@napi-rs/wasm-runtime': 1.1.6(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1) optional: true - '@rolldown/binding-win32-arm64-msvc@1.0.3': + '@rolldown/binding-win32-arm64-msvc@1.1.3': optional: true - '@rolldown/binding-win32-x64-msvc@1.0.3': + '@rolldown/binding-win32-x64-msvc@1.1.3': optional: true '@rolldown/pluginutils@1.0.1': {} @@ -19879,21 +20595,21 @@ snapshots: '@rvf/set-get@7.0.1': {} - '@scalar/agent-chat@0.12.11(nprogress@0.2.0)(tailwindcss@4.3.1)(typescript@6.0.3)(zod@4.4.3)': + '@scalar/agent-chat@0.12.13(nprogress@0.2.0)(tailwindcss@4.3.1)(typescript@6.0.3)(zod@4.4.3)': dependencies: '@ai-sdk/vue': 3.0.33(vue@3.5.35(typescript@6.0.3))(zod@4.4.3) - '@scalar/api-client': 3.10.4(nprogress@0.2.0)(tailwindcss@4.3.1)(typescript@6.0.3) - '@scalar/components': 0.27.2(tailwindcss@4.3.1)(typescript@6.0.3) - '@scalar/helpers': 0.8.2 + '@scalar/api-client': 3.12.0(nprogress@0.2.0)(tailwindcss@4.3.1)(typescript@6.0.3) + '@scalar/components': 0.27.4(tailwindcss@4.3.1)(typescript@6.0.3) + '@scalar/helpers': 0.9.0 '@scalar/icons': 0.7.3(typescript@6.0.3) - '@scalar/json-magic': 0.12.16 + '@scalar/json-magic': 0.12.17 '@scalar/openapi-types': 0.9.1 - '@scalar/schemas': 0.5.0 - '@scalar/themes': 0.16.0 - '@scalar/types': 0.14.0 + '@scalar/schemas': 0.7.0 + '@scalar/themes': 0.16.2 + '@scalar/types': 0.16.0 '@scalar/use-toasts': 0.10.2(typescript@6.0.3) '@scalar/validation': 0.6.0 - '@scalar/workspace-store': 0.54.4(typescript@6.0.3) + '@scalar/workspace-store': 0.55.0(typescript@6.0.3) '@vueuse/core': 13.9.0(vue@3.5.35(typescript@6.0.3)) ai: 6.0.33(zod@4.4.3) js-base64: 3.7.8 @@ -19917,33 +20633,32 @@ snapshots: - universal-cookie - zod - '@scalar/api-client@3.10.4(nprogress@0.2.0)(tailwindcss@4.3.1)(typescript@6.0.3)': + '@scalar/api-client@3.12.0(nprogress@0.2.0)(tailwindcss@4.3.1)(typescript@6.0.3)': dependencies: '@headlessui/tailwindcss': 0.2.2(tailwindcss@4.3.1) '@headlessui/vue': 1.7.23(vue@3.5.35(typescript@6.0.3)) - '@scalar/components': 0.27.2(tailwindcss@4.3.1)(typescript@6.0.3) - '@scalar/helpers': 0.8.2 + '@scalar/blocks': 0.1.0(tailwindcss@4.3.1)(typescript@6.0.3) + '@scalar/components': 0.27.4(tailwindcss@4.3.1)(typescript@6.0.3) + '@scalar/helpers': 0.9.0 '@scalar/icons': 0.7.3(typescript@6.0.3) - '@scalar/json-magic': 0.12.16 - '@scalar/oas-utils': 0.18.5(typescript@6.0.3) + '@scalar/oas-utils': 0.19.1(typescript@6.0.3) '@scalar/openapi-types': 0.9.1 - '@scalar/sidebar': 0.9.23(tailwindcss@4.3.1)(typescript@6.0.3) - '@scalar/snippetz': 0.9.17 - '@scalar/themes': 0.16.0 + '@scalar/sidebar': 0.9.25(tailwindcss@4.3.1)(typescript@6.0.3) + '@scalar/snippetz': 0.9.19 + '@scalar/themes': 0.16.2 '@scalar/typebox': 0.1.3 - '@scalar/types': 0.14.0 + '@scalar/types': 0.16.0 '@scalar/use-codemirror': 0.14.12(typescript@6.0.3) '@scalar/use-hooks': 0.4.7(typescript@6.0.3) '@scalar/use-toasts': 0.10.2(typescript@6.0.3) - '@scalar/workspace-store': 0.54.4(typescript@6.0.3) - '@types/har-format': 1.2.16 + '@scalar/workspace-store': 0.55.0(typescript@6.0.3) '@vueuse/core': 13.9.0(vue@3.5.35(typescript@6.0.3)) '@vueuse/integrations': 13.9.0(focus-trap@7.8.0)(fuse.js@7.4.2)(nprogress@0.2.0)(vue@3.5.35(typescript@6.0.3)) focus-trap: 7.8.0 fuse.js: 7.4.2 js-base64: 3.7.8 jsonc-parser: 3.3.1 - nanoid: 5.1.11 + nanoid: 5.1.16 pretty-ms: 9.3.0 radix-vue: 1.9.17(vue@3.5.35(typescript@6.0.3)) set-cookie-parser: 3.1.0 @@ -19966,10 +20681,10 @@ snapshots: - typescript - universal-cookie - '@scalar/api-reference-react@0.9.47(nprogress@0.2.0)(react@19.2.7)(tailwindcss@4.3.1)(typescript@6.0.3)(zod@4.4.3)': + '@scalar/api-reference-react@0.9.49(nprogress@0.2.0)(react@19.2.7)(tailwindcss@4.3.1)(typescript@6.0.3)(zod@4.4.3)': dependencies: - '@scalar/api-reference': 1.60.0(nprogress@0.2.0)(tailwindcss@4.3.1)(typescript@6.0.3)(zod@4.4.3) - '@scalar/types': 0.14.0 + '@scalar/api-reference': 1.62.0(nprogress@0.2.0)(tailwindcss@4.3.1)(typescript@6.0.3)(zod@4.4.3) + '@scalar/types': 0.16.0 react: 19.2.7 transitivePeerDependencies: - '@vue/composition-api' @@ -19988,30 +20703,31 @@ snapshots: - universal-cookie - zod - '@scalar/api-reference@1.60.0(nprogress@0.2.0)(tailwindcss@4.3.1)(typescript@6.0.3)(zod@4.4.3)': + '@scalar/api-reference@1.62.0(nprogress@0.2.0)(tailwindcss@4.3.1)(typescript@6.0.3)(zod@4.4.3)': dependencies: '@headlessui/vue': 1.7.23(vue@3.5.35(typescript@6.0.3)) - '@scalar/agent-chat': 0.12.11(nprogress@0.2.0)(tailwindcss@4.3.1)(typescript@6.0.3)(zod@4.4.3) - '@scalar/api-client': 3.10.4(nprogress@0.2.0)(tailwindcss@4.3.1)(typescript@6.0.3) - '@scalar/code-highlight': 0.3.6 - '@scalar/components': 0.27.2(tailwindcss@4.3.1)(typescript@6.0.3) - '@scalar/helpers': 0.8.2 + '@scalar/agent-chat': 0.12.13(nprogress@0.2.0)(tailwindcss@4.3.1)(typescript@6.0.3)(zod@4.4.3) + '@scalar/api-client': 3.12.0(nprogress@0.2.0)(tailwindcss@4.3.1)(typescript@6.0.3) + '@scalar/blocks': 0.1.0(tailwindcss@4.3.1)(typescript@6.0.3) + '@scalar/code-highlight': 0.4.0 + '@scalar/components': 0.27.4(tailwindcss@4.3.1)(typescript@6.0.3) + '@scalar/helpers': 0.9.0 '@scalar/icons': 0.7.3(typescript@6.0.3) - '@scalar/oas-utils': 0.18.5(typescript@6.0.3) - '@scalar/schemas': 0.5.0 - '@scalar/sidebar': 0.9.23(tailwindcss@4.3.1)(typescript@6.0.3) - '@scalar/snippetz': 0.9.17 - '@scalar/themes': 0.16.0 - '@scalar/types': 0.14.0 + '@scalar/oas-utils': 0.19.1(typescript@6.0.3) + '@scalar/schemas': 0.7.0 + '@scalar/sidebar': 0.9.25(tailwindcss@4.3.1)(typescript@6.0.3) + '@scalar/snippetz': 0.9.19 + '@scalar/themes': 0.16.2 + '@scalar/types': 0.16.0 '@scalar/use-hooks': 0.4.7(typescript@6.0.3) '@scalar/use-toasts': 0.10.2(typescript@6.0.3) '@scalar/validation': 0.6.0 - '@scalar/workspace-store': 0.54.4(typescript@6.0.3) + '@scalar/workspace-store': 0.55.0(typescript@6.0.3) '@unhead/vue': 2.1.15(vue@3.5.35(typescript@6.0.3)) '@vueuse/core': 13.9.0(vue@3.5.35(typescript@6.0.3)) fuse.js: 7.4.2 microdiff: 1.5.0 - nanoid: 5.1.11 + nanoid: 5.1.16 vue: 3.5.35(typescript@6.0.3) yaml: 2.9.0 transitivePeerDependencies: @@ -20031,7 +20747,25 @@ snapshots: - universal-cookie - zod - '@scalar/code-highlight@0.3.6': + '@scalar/blocks@0.1.0(tailwindcss@4.3.1)(typescript@6.0.3)': + dependencies: + '@scalar/components': 0.27.4(tailwindcss@4.3.1)(typescript@6.0.3) + '@scalar/helpers': 0.9.0 + '@scalar/icons': 0.7.3(typescript@6.0.3) + '@scalar/snippetz': 0.9.19 + '@scalar/themes': 0.16.2 + '@scalar/types': 0.16.0 + '@scalar/workspace-store': 0.55.0(typescript@6.0.3) + '@types/har-format': 1.2.16 + js-base64: 3.7.8 + vue: 3.5.35(typescript@6.0.3) + transitivePeerDependencies: + - '@vue/composition-api' + - supports-color + - tailwindcss + - typescript + + '@scalar/code-highlight@0.4.0': dependencies: hast-util-to-text: 4.0.2 highlight.js: 11.11.1 @@ -20051,16 +20785,16 @@ snapshots: transitivePeerDependencies: - supports-color - '@scalar/components@0.27.2(tailwindcss@4.3.1)(typescript@6.0.3)': + '@scalar/components@0.27.4(tailwindcss@4.3.1)(typescript@6.0.3)': dependencies: '@floating-ui/utils': 0.2.10 '@floating-ui/vue': 1.1.9(vue@3.5.35(typescript@6.0.3)) '@headlessui/tailwindcss': 0.2.2(tailwindcss@4.3.1) '@headlessui/vue': 1.7.23(vue@3.5.35(typescript@6.0.3)) - '@scalar/code-highlight': 0.3.6 - '@scalar/helpers': 0.8.2 + '@scalar/code-highlight': 0.4.0 + '@scalar/helpers': 0.9.0 '@scalar/icons': 0.7.3(typescript@6.0.3) - '@scalar/themes': 0.16.0 + '@scalar/themes': 0.16.2 '@scalar/use-hooks': 0.4.7(typescript@6.0.3) '@vueuse/core': 13.9.0(vue@3.5.35(typescript@6.0.3)) cva: 1.0.0-beta.4(typescript@6.0.3) @@ -20073,7 +20807,7 @@ snapshots: - tailwindcss - typescript - '@scalar/helpers@0.8.2': {} + '@scalar/helpers@0.9.0': {} '@scalar/icons@0.7.3(typescript@6.0.3)': dependencies: @@ -20084,18 +20818,18 @@ snapshots: transitivePeerDependencies: - typescript - '@scalar/json-magic@0.12.16': + '@scalar/json-magic@0.12.17': dependencies: - '@scalar/helpers': 0.8.2 + '@scalar/helpers': 0.9.0 pathe: 2.0.3 yaml: 2.9.0 - '@scalar/oas-utils@0.18.5(typescript@6.0.3)': + '@scalar/oas-utils@0.19.1(typescript@6.0.3)': dependencies: - '@scalar/helpers': 0.8.2 - '@scalar/themes': 0.16.0 - '@scalar/types': 0.14.0 - '@scalar/workspace-store': 0.54.4(typescript@6.0.3) + '@scalar/helpers': 0.9.0 + '@scalar/themes': 0.16.2 + '@scalar/types': 0.16.0 + '@scalar/workspace-store': 0.55.0(typescript@6.0.3) flatted: 3.4.2 vue: 3.5.35(typescript@6.0.3) yaml: 2.9.0 @@ -20108,19 +20842,19 @@ snapshots: dependencies: '@scalar/openapi-types': 0.9.1 - '@scalar/schemas@0.5.0': + '@scalar/schemas@0.7.0': dependencies: - '@scalar/helpers': 0.8.2 + '@scalar/helpers': 0.9.0 '@scalar/validation': 0.6.0 - '@scalar/sidebar@0.9.23(tailwindcss@4.3.1)(typescript@6.0.3)': + '@scalar/sidebar@0.9.25(tailwindcss@4.3.1)(typescript@6.0.3)': dependencies: - '@scalar/components': 0.27.2(tailwindcss@4.3.1)(typescript@6.0.3) - '@scalar/helpers': 0.8.2 + '@scalar/components': 0.27.4(tailwindcss@4.3.1)(typescript@6.0.3) + '@scalar/helpers': 0.9.0 '@scalar/icons': 0.7.3(typescript@6.0.3) - '@scalar/themes': 0.16.0 + '@scalar/themes': 0.16.2 '@scalar/use-hooks': 0.4.7(typescript@6.0.3) - '@scalar/workspace-store': 0.54.4(typescript@6.0.3) + '@scalar/workspace-store': 0.55.0(typescript@6.0.3) vue: 3.5.35(typescript@6.0.3) transitivePeerDependencies: - '@vue/composition-api' @@ -20128,23 +20862,23 @@ snapshots: - tailwindcss - typescript - '@scalar/snippetz@0.9.17': + '@scalar/snippetz@0.9.19': dependencies: - '@scalar/helpers': 0.8.2 - '@scalar/types': 0.14.0 + '@scalar/helpers': 0.9.0 + '@scalar/types': 0.16.0 js-base64: 3.7.8 stringify-object: 6.0.0 - '@scalar/themes@0.16.0': + '@scalar/themes@0.16.2': dependencies: - nanoid: 5.1.11 + nanoid: 5.1.16 '@scalar/typebox@0.1.3': {} - '@scalar/types@0.14.0': + '@scalar/types@0.16.0': dependencies: - '@scalar/helpers': 0.8.2 - nanoid: 5.1.11 + '@scalar/helpers': 0.9.0 + nanoid: 5.1.16 type-fest: 5.6.0 zod: 4.4.3 @@ -20188,15 +20922,15 @@ snapshots: '@scalar/validation@0.6.0': {} - '@scalar/workspace-store@0.54.4(typescript@6.0.3)': + '@scalar/workspace-store@0.55.0(typescript@6.0.3)': dependencies: - '@scalar/helpers': 0.8.2 - '@scalar/json-magic': 0.12.16 + '@scalar/helpers': 0.9.0 + '@scalar/json-magic': 0.12.17 '@scalar/openapi-upgrader': 0.2.9 - '@scalar/schemas': 0.5.0 - '@scalar/snippetz': 0.9.17 + '@scalar/schemas': 0.7.0 + '@scalar/snippetz': 0.9.19 '@scalar/typebox': 0.1.3 - '@scalar/types': 0.14.0 + '@scalar/types': 0.16.0 '@scalar/validation': 0.6.0 js-base64: 3.7.8 type-fest: 5.6.0 @@ -20313,9 +21047,9 @@ snapshots: '@sideway/pinpoint@2.0.0': {} - '@signalwire/docusaurus-plugin-llms-txt@1.2.2(@docusaurus/core@3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.0)(postcss@8.5.15))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3))': + '@signalwire/docusaurus-plugin-llms-txt@1.2.2(@docusaurus/core@3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.1)(postcss@8.5.16))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3))': dependencies: - '@docusaurus/core': 3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.0)(postcss@8.5.15))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3) + '@docusaurus/core': 3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.1)(postcss@8.5.16))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3) fs-extra: 11.2.0 hast-util-select: 6.0.4 hast-util-to-html: 9.0.5 @@ -20665,6 +21399,11 @@ snapshots: dependencies: remove-accents: 0.5.0 + '@tanstack/query-async-storage-persister@5.101.0': + dependencies: + '@tanstack/query-core': 5.101.0 + '@tanstack/query-persist-client-core': 5.101.0 + '@tanstack/query-core@5.101.0': {} '@tanstack/query-devtools@5.101.0': {} @@ -20679,10 +21418,16 @@ snapshots: '@tanstack/react-query': 5.101.0(react@19.2.7) react: 19.2.7 - '@tanstack/react-query-next-experimental@5.101.0(@tanstack/react-query@5.101.0(react@19.2.7))(next@16.2.9(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.61.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(sass@1.101.0))(react@19.2.7)': + '@tanstack/react-query-next-experimental@5.101.0(@tanstack/react-query@5.101.0(react@19.2.7))(next@16.2.9(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.61.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(sass@1.101.0))(react@19.2.7)': dependencies: '@tanstack/react-query': 5.101.0(react@19.2.7) - next: 16.2.9(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.61.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(sass@1.101.0) + next: 16.2.9(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.61.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(sass@1.101.0) + react: 19.2.7 + + '@tanstack/react-query-persist-client@5.101.0(@tanstack/react-query@5.101.0(react@19.2.7))(react@19.2.7)': + dependencies: + '@tanstack/query-persist-client-core': 5.101.0 + '@tanstack/react-query': 5.101.0(react@19.2.7) react: 19.2.7 '@tanstack/react-query@5.101.0(react@19.2.7)': @@ -20960,11 +21705,11 @@ snapshots: '@trpc/server': 11.17.0(typescript@6.0.3) typescript: 6.0.3 - '@trpc/next@11.17.0(@tanstack/react-query@5.101.0(react@19.2.7))(@trpc/client@11.17.0(@trpc/server@11.17.0(typescript@6.0.3))(typescript@6.0.3))(@trpc/react-query@11.17.0(@tanstack/react-query@5.101.0(react@19.2.7))(@trpc/client@11.17.0(@trpc/server@11.17.0(typescript@6.0.3))(typescript@6.0.3))(@trpc/server@11.17.0(typescript@6.0.3))(react@19.2.7)(typescript@6.0.3))(@trpc/server@11.17.0(typescript@6.0.3))(next@16.2.9(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.61.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(sass@1.101.0))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3)': + '@trpc/next@11.17.0(@tanstack/react-query@5.101.0(react@19.2.7))(@trpc/client@11.17.0(@trpc/server@11.17.0(typescript@6.0.3))(typescript@6.0.3))(@trpc/react-query@11.17.0(@tanstack/react-query@5.101.0(react@19.2.7))(@trpc/client@11.17.0(@trpc/server@11.17.0(typescript@6.0.3))(typescript@6.0.3))(@trpc/server@11.17.0(typescript@6.0.3))(react@19.2.7)(typescript@6.0.3))(@trpc/server@11.17.0(typescript@6.0.3))(next@16.2.9(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.61.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(sass@1.101.0))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3)': dependencies: '@trpc/client': 11.17.0(@trpc/server@11.17.0(typescript@6.0.3))(typescript@6.0.3) '@trpc/server': 11.17.0(typescript@6.0.3) - next: 16.2.9(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.61.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(sass@1.101.0) + next: 16.2.9(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.61.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(sass@1.101.0) react: 19.2.7 react-dom: 19.2.7(react@19.2.7) typescript: 6.0.3 @@ -20994,32 +21739,32 @@ snapshots: '@tsconfig/docusaurus@2.0.9': {} - '@turbo/darwin-64@2.9.18': + '@turbo/darwin-64@2.10.0': optional: true - '@turbo/darwin-arm64@2.9.18': + '@turbo/darwin-arm64@2.10.0': optional: true - '@turbo/gen@2.9.18(@types/node@24.13.2)': + '@turbo/gen@2.10.0(@types/node@24.13.2)': dependencies: '@inquirer/prompts': 7.10.1(@types/node@24.13.2) - esbuild: 0.25.12 + esbuild: 0.28.1 transitivePeerDependencies: - '@types/node' - '@turbo/linux-64@2.9.18': + '@turbo/linux-64@2.10.0': optional: true - '@turbo/linux-arm64@2.9.18': + '@turbo/linux-arm64@2.10.0': optional: true - '@turbo/windows-64@2.9.18': + '@turbo/windows-64@2.10.0': optional: true - '@turbo/windows-arm64@2.9.18': + '@turbo/windows-arm64@2.10.0': optional: true - '@tybys/wasm-util@0.10.2': + '@tybys/wasm-util@0.10.3': dependencies: tslib: 2.8.1 optional: true @@ -21480,10 +22225,10 @@ snapshots: global: 4.4.0 is-function: 1.0.2 - '@vitejs/plugin-react@6.0.2(babel-plugin-react-compiler@1.0.0)(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.101.0)(sugarss@5.0.0(postcss@8.5.15))(terser@5.46.1)(tsx@4.21.0)(yaml@2.9.0))': + '@vitejs/plugin-react@6.0.3(babel-plugin-react-compiler@1.0.0)(vite@8.1.0(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(sugarss@5.0.0(postcss@8.5.16))(terser@5.46.1)(tsx@4.21.0)(yaml@2.9.0))': dependencies: '@rolldown/pluginutils': 1.0.1 - vite: 8.0.16(@types/node@24.13.2)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.101.0)(sugarss@5.0.0(postcss@8.5.15))(terser@5.46.1)(tsx@4.21.0)(yaml@2.9.0) + vite: 8.1.0(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(sugarss@5.0.0(postcss@8.5.16))(terser@5.46.1)(tsx@4.21.0)(yaml@2.9.0) optionalDependencies: babel-plugin-react-compiler: 1.0.0 @@ -21499,7 +22244,7 @@ snapshots: obug: 2.1.1 std-env: 4.0.0 tinyrainbow: 3.1.0 - vitest: 4.1.9(@opentelemetry/api@1.9.0)(@types/node@24.13.2)(@vitest/coverage-v8@4.1.9)(@vitest/ui@4.1.9)(jsdom@29.1.1(@noble/hashes@2.0.1))(msw@2.14.6(@types/node@24.13.2)(typescript@6.0.3))(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.101.0)(sugarss@5.0.0(postcss@8.5.15))(terser@5.46.1)(tsx@4.21.0)(yaml@2.9.0)) + vitest: 4.1.9(@opentelemetry/api@1.9.0)(@types/node@24.13.2)(@vitest/coverage-v8@4.1.9)(@vitest/ui@4.1.9)(jsdom@29.1.1(@noble/hashes@2.0.1))(msw@2.14.6(@types/node@24.13.2)(typescript@6.0.3))(vite@8.1.0(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(sugarss@5.0.0(postcss@8.5.16))(terser@5.46.1)(tsx@4.21.0)(yaml@2.9.0)) '@vitest/expect@4.1.9': dependencies: @@ -21510,14 +22255,14 @@ snapshots: chai: 6.2.2 tinyrainbow: 3.1.0 - '@vitest/mocker@4.1.9(msw@2.14.6(@types/node@24.13.2)(typescript@6.0.3))(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.101.0)(sugarss@5.0.0(postcss@8.5.15))(terser@5.46.1)(tsx@4.21.0)(yaml@2.9.0))': + '@vitest/mocker@4.1.9(msw@2.14.6(@types/node@24.13.2)(typescript@6.0.3))(vite@8.1.0(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(sugarss@5.0.0(postcss@8.5.16))(terser@5.46.1)(tsx@4.21.0)(yaml@2.9.0))': dependencies: '@vitest/spy': 4.1.9 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: msw: 2.14.6(@types/node@24.13.2)(typescript@6.0.3) - vite: 8.0.16(@types/node@24.13.2)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.101.0)(sugarss@5.0.0(postcss@8.5.15))(terser@5.46.1)(tsx@4.21.0)(yaml@2.9.0) + vite: 8.1.0(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(sugarss@5.0.0(postcss@8.5.16))(terser@5.46.1)(tsx@4.21.0)(yaml@2.9.0) '@vitest/pretty-format@4.1.9': dependencies: @@ -21546,7 +22291,7 @@ snapshots: sirv: 3.0.2 tinyglobby: 0.2.17 tinyrainbow: 3.1.0 - vitest: 4.1.9(@opentelemetry/api@1.9.0)(@types/node@24.13.2)(@vitest/coverage-v8@4.1.9)(@vitest/ui@4.1.9)(jsdom@29.1.1(@noble/hashes@2.0.1))(msw@2.14.6(@types/node@24.13.2)(typescript@6.0.3))(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.101.0)(sugarss@5.0.0(postcss@8.5.15))(terser@5.46.1)(tsx@4.21.0)(yaml@2.9.0)) + vitest: 4.1.9(@opentelemetry/api@1.9.0)(@types/node@24.13.2)(@vitest/coverage-v8@4.1.9)(@vitest/ui@4.1.9)(jsdom@29.1.1(@noble/hashes@2.0.1))(msw@2.14.6(@types/node@24.13.2)(typescript@6.0.3))(vite@8.1.0(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(sugarss@5.0.0(postcss@8.5.16))(terser@5.46.1)(tsx@4.21.0)(yaml@2.9.0)) '@vitest/utils@4.1.9': dependencies: @@ -21965,13 +22710,13 @@ snapshots: attr-accept@2.2.5: {} - autoprefixer@10.5.0(postcss@8.5.15): + autoprefixer@10.5.0(postcss@8.5.16): dependencies: browserslist: 4.28.2 caniuse-lite: 1.0.30001793 fraction.js: 5.3.4 picocolors: 1.1.1 - postcss: 8.5.15 + postcss: 8.5.16 postcss-value-parser: 4.2.0 avvio@9.2.0: @@ -21999,12 +22744,12 @@ snapshots: b4a@1.6.6: {} - babel-loader@9.2.1(@babel/core@7.29.0)(webpack@5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)): + babel-loader@9.2.1(@babel/core@7.29.0)(webpack@5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)): dependencies: '@babel/core': 7.29.0 find-cache-dir: 4.0.0 schema-utils: 4.3.3 - webpack: 5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.15))(esbuild@0.28.0)(html-minifier-terser@7.2.0)(postcss@8.5.15) + webpack: 5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.16))(esbuild@0.28.1)(html-minifier-terser@7.2.0)(postcss@8.5.16) babel-plugin-dynamic-import-node@2.3.3: dependencies: @@ -22100,7 +22845,7 @@ snapshots: before-after-hook@4.0.0: {} - better-auth@1.6.15(@opentelemetry/api@1.9.0)(better-sqlite3@12.10.0)(drizzle-kit@0.31.10)(drizzle-orm@0.45.2(@libsql/client-wasm@0.14.0)(@opentelemetry/api@1.9.0)(@types/better-sqlite3@7.6.13)(@types/pg@8.20.0)(better-sqlite3@12.10.0)(gel@2.0.0)(kysely@0.29.2)(mysql2@3.22.5(@types/node@24.13.2))(pg@8.22.0)(sql.js@1.14.1))(mysql2@3.22.5(@types/node@24.13.2))(next@16.2.9(@opentelemetry/api@1.9.0)(@playwright/test@1.61.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(sass@1.101.0))(pg@8.22.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(vitest@4.1.9)(vue@3.5.35(typescript@6.0.3)): + better-auth@1.6.15(@opentelemetry/api@1.9.0)(better-sqlite3@12.10.0)(drizzle-kit@0.31.10)(drizzle-orm@0.45.2(@libsql/client-wasm@0.14.0)(@opentelemetry/api@1.9.0)(@types/better-sqlite3@7.6.13)(@types/pg@8.20.0)(better-sqlite3@12.10.0)(gel@2.0.0)(kysely@0.29.2)(mysql2@3.22.5(@types/node@24.13.2))(pg@8.22.0)(sql.js@1.14.1))(mysql2@3.22.5(@types/node@24.13.2))(next@16.2.9(@opentelemetry/api@1.9.0)(@playwright/test@1.61.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(sass@1.101.0))(pg@8.22.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(vitest@4.1.9)(vue@3.5.35(typescript@6.0.3)): dependencies: '@better-auth/core': 1.6.15(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.0)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.3.0) '@better-auth/drizzle-adapter': 1.6.15(@better-auth/core@1.6.15(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.0)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.3.0))(@better-auth/utils@0.4.1)(drizzle-orm@0.45.2(@libsql/client-wasm@0.14.0)(@opentelemetry/api@1.9.0)(@types/better-sqlite3@7.6.13)(@types/pg@8.20.0)(better-sqlite3@12.10.0)(gel@2.0.0)(kysely@0.29.2)(mysql2@3.22.5(@types/node@24.13.2))(pg@8.22.0)(sql.js@1.14.1)) @@ -22124,11 +22869,11 @@ snapshots: drizzle-kit: 0.31.10 drizzle-orm: 0.45.2(@libsql/client-wasm@0.14.0)(@opentelemetry/api@1.9.0)(@types/better-sqlite3@7.6.13)(@types/pg@8.20.0)(better-sqlite3@12.10.0)(gel@2.0.0)(kysely@0.29.2)(mysql2@3.22.5(@types/node@24.13.2))(pg@8.22.0)(sql.js@1.14.1) mysql2: 3.22.5(@types/node@24.13.2) - next: 16.2.9(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.61.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(sass@1.101.0) + next: 16.2.9(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.61.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(sass@1.101.0) pg: 8.22.0 react: 19.2.7 react-dom: 19.2.7(react@19.2.7) - vitest: 4.1.9(@opentelemetry/api@1.9.0)(@types/node@24.13.2)(@vitest/coverage-v8@4.1.9)(@vitest/ui@4.1.9)(jsdom@29.1.1(@noble/hashes@2.0.1))(msw@2.14.6(@types/node@24.13.2)(typescript@6.0.3))(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.101.0)(sugarss@5.0.0(postcss@8.5.15))(terser@5.46.1)(tsx@4.21.0)(yaml@2.9.0)) + vitest: 4.1.9(@opentelemetry/api@1.9.0)(@types/node@24.13.2)(@vitest/coverage-v8@4.1.9)(@vitest/ui@4.1.9)(jsdom@29.1.1(@noble/hashes@2.0.1))(msw@2.14.6(@types/node@24.13.2)(typescript@6.0.3))(vite@8.1.0(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(sugarss@5.0.0(postcss@8.5.16))(terser@5.46.1)(tsx@4.21.0)(yaml@2.9.0)) vue: 3.5.35(typescript@6.0.3) transitivePeerDependencies: - '@cloudflare/workers-types' @@ -22658,7 +23403,7 @@ snapshots: copy-text-to-clipboard@3.2.2: {} - copy-webpack-plugin@11.0.0(webpack@5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)): + copy-webpack-plugin@11.0.0(webpack@5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)): dependencies: fast-glob: 3.3.3 glob-parent: 6.0.2 @@ -22666,7 +23411,7 @@ snapshots: normalize-path: 3.0.0 schema-utils: 4.3.3 serialize-javascript: 6.0.2 - webpack: 5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.15))(esbuild@0.28.0)(html-minifier-terser@7.2.0)(postcss@8.5.15) + webpack: 5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.16))(esbuild@0.28.1)(html-minifier-terser@7.2.0)(postcss@8.5.16) core-js-compat@3.49.0: dependencies: @@ -22745,52 +23490,65 @@ snapshots: dependencies: type-fest: 1.4.0 - css-blank-pseudo@7.0.1(postcss@8.5.15): + css-blank-pseudo@7.0.1(postcss@8.5.16): dependencies: - postcss: 8.5.15 + postcss: 8.5.16 postcss-selector-parser: 7.1.0 - css-declaration-sorter@7.4.0(postcss@8.5.15): + css-declaration-sorter@7.4.0(postcss@8.5.16): dependencies: - postcss: 8.5.15 + postcss: 8.5.16 - css-has-pseudo@7.0.3(postcss@8.5.15): + css-has-pseudo@7.0.3(postcss@8.5.16): dependencies: '@csstools/selector-specificity': 5.0.0(postcss-selector-parser@7.1.0) - postcss: 8.5.15 + postcss: 8.5.16 postcss-selector-parser: 7.1.0 postcss-value-parser: 4.2.0 - css-loader@6.11.0(@rspack/core@1.7.11(@swc/helpers@0.5.15))(webpack@5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)): + css-loader@6.11.0(@rspack/core@1.7.11(@swc/helpers@0.5.15))(webpack@5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)): dependencies: - icss-utils: 5.1.0(postcss@8.5.15) - postcss: 8.5.15 - postcss-modules-extract-imports: 3.1.0(postcss@8.5.15) - postcss-modules-local-by-default: 4.2.0(postcss@8.5.15) - postcss-modules-scope: 3.2.1(postcss@8.5.15) - postcss-modules-values: 4.0.0(postcss@8.5.15) + icss-utils: 5.1.0(postcss@8.5.16) + postcss: 8.5.16 + postcss-modules-extract-imports: 3.1.0(postcss@8.5.16) + postcss-modules-local-by-default: 4.2.0(postcss@8.5.16) + postcss-modules-scope: 3.2.1(postcss@8.5.16) + postcss-modules-values: 4.0.0(postcss@8.5.16) postcss-value-parser: 4.2.0 semver: 7.7.4 optionalDependencies: '@rspack/core': 1.7.11(@swc/helpers@0.5.15) - webpack: 5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.15))(esbuild@0.28.0)(html-minifier-terser@7.2.0)(postcss@8.5.15) + webpack: 5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.16))(esbuild@0.28.1)(html-minifier-terser@7.2.0)(postcss@8.5.16) - css-minimizer-webpack-plugin@5.0.1(clean-css@5.3.3)(esbuild@0.28.0)(webpack@5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)): + css-minimizer-webpack-plugin@5.0.1(clean-css@5.3.3)(esbuild@0.28.0)(webpack@5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)): dependencies: '@jridgewell/trace-mapping': 0.3.31 - cssnano: 6.1.2(postcss@8.5.15) + cssnano: 6.1.2(postcss@8.5.16) jest-worker: 29.7.0 - postcss: 8.5.15 + postcss: 8.5.16 schema-utils: 4.3.3 serialize-javascript: 6.0.2 - webpack: 5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.15))(esbuild@0.28.0)(html-minifier-terser@7.2.0)(postcss@8.5.15) + webpack: 5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.16))(esbuild@0.28.1)(html-minifier-terser@7.2.0)(postcss@8.5.16) optionalDependencies: clean-css: 5.3.3 esbuild: 0.28.0 - css-prefers-color-scheme@10.0.0(postcss@8.5.15): + css-minimizer-webpack-plugin@5.0.1(clean-css@5.3.3)(esbuild@0.28.1)(webpack@5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)): dependencies: - postcss: 8.5.15 + '@jridgewell/trace-mapping': 0.3.31 + cssnano: 6.1.2(postcss@8.5.16) + jest-worker: 29.7.0 + postcss: 8.5.16 + schema-utils: 4.3.3 + serialize-javascript: 6.0.2 + webpack: 5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.16))(esbuild@0.28.1)(html-minifier-terser@7.2.0)(postcss@8.5.16) + optionalDependencies: + clean-css: 5.3.3 + esbuild: 0.28.1 + + css-prefers-color-scheme@10.0.0(postcss@8.5.16): + dependencies: + postcss: 8.5.16 css-select@4.3.0: dependencies: @@ -22831,60 +23589,60 @@ snapshots: cssesc@3.0.0: {} - cssnano-preset-advanced@6.1.2(postcss@8.5.15): + cssnano-preset-advanced@6.1.2(postcss@8.5.16): dependencies: - autoprefixer: 10.5.0(postcss@8.5.15) + autoprefixer: 10.5.0(postcss@8.5.16) browserslist: 4.28.2 - cssnano-preset-default: 6.1.2(postcss@8.5.15) - postcss: 8.5.15 - postcss-discard-unused: 6.0.5(postcss@8.5.15) - postcss-merge-idents: 6.0.3(postcss@8.5.15) - postcss-reduce-idents: 6.0.3(postcss@8.5.15) - postcss-zindex: 6.0.2(postcss@8.5.15) + cssnano-preset-default: 6.1.2(postcss@8.5.16) + postcss: 8.5.16 + postcss-discard-unused: 6.0.5(postcss@8.5.16) + postcss-merge-idents: 6.0.3(postcss@8.5.16) + postcss-reduce-idents: 6.0.3(postcss@8.5.16) + postcss-zindex: 6.0.2(postcss@8.5.16) - cssnano-preset-default@6.1.2(postcss@8.5.15): + cssnano-preset-default@6.1.2(postcss@8.5.16): dependencies: browserslist: 4.28.2 - css-declaration-sorter: 7.4.0(postcss@8.5.15) - cssnano-utils: 4.0.2(postcss@8.5.15) - postcss: 8.5.15 - postcss-calc: 9.0.1(postcss@8.5.15) - postcss-colormin: 6.1.0(postcss@8.5.15) - postcss-convert-values: 6.1.0(postcss@8.5.15) - postcss-discard-comments: 6.0.2(postcss@8.5.15) - postcss-discard-duplicates: 6.0.3(postcss@8.5.15) - postcss-discard-empty: 6.0.3(postcss@8.5.15) - postcss-discard-overridden: 6.0.2(postcss@8.5.15) - postcss-merge-longhand: 6.0.5(postcss@8.5.15) - postcss-merge-rules: 6.1.1(postcss@8.5.15) - postcss-minify-font-values: 6.1.0(postcss@8.5.15) - postcss-minify-gradients: 6.0.3(postcss@8.5.15) - postcss-minify-params: 6.1.0(postcss@8.5.15) - postcss-minify-selectors: 6.0.4(postcss@8.5.15) - postcss-normalize-charset: 6.0.2(postcss@8.5.15) - postcss-normalize-display-values: 6.0.2(postcss@8.5.15) - postcss-normalize-positions: 6.0.2(postcss@8.5.15) - postcss-normalize-repeat-style: 6.0.2(postcss@8.5.15) - postcss-normalize-string: 6.0.2(postcss@8.5.15) - postcss-normalize-timing-functions: 6.0.2(postcss@8.5.15) - postcss-normalize-unicode: 6.1.0(postcss@8.5.15) - postcss-normalize-url: 6.0.2(postcss@8.5.15) - postcss-normalize-whitespace: 6.0.2(postcss@8.5.15) - postcss-ordered-values: 6.0.2(postcss@8.5.15) - postcss-reduce-initial: 6.1.0(postcss@8.5.15) - postcss-reduce-transforms: 6.0.2(postcss@8.5.15) - postcss-svgo: 6.0.3(postcss@8.5.15) - postcss-unique-selectors: 6.0.4(postcss@8.5.15) + css-declaration-sorter: 7.4.0(postcss@8.5.16) + cssnano-utils: 4.0.2(postcss@8.5.16) + postcss: 8.5.16 + postcss-calc: 9.0.1(postcss@8.5.16) + postcss-colormin: 6.1.0(postcss@8.5.16) + postcss-convert-values: 6.1.0(postcss@8.5.16) + postcss-discard-comments: 6.0.2(postcss@8.5.16) + postcss-discard-duplicates: 6.0.3(postcss@8.5.16) + postcss-discard-empty: 6.0.3(postcss@8.5.16) + postcss-discard-overridden: 6.0.2(postcss@8.5.16) + postcss-merge-longhand: 6.0.5(postcss@8.5.16) + postcss-merge-rules: 6.1.1(postcss@8.5.16) + postcss-minify-font-values: 6.1.0(postcss@8.5.16) + postcss-minify-gradients: 6.0.3(postcss@8.5.16) + postcss-minify-params: 6.1.0(postcss@8.5.16) + postcss-minify-selectors: 6.0.4(postcss@8.5.16) + postcss-normalize-charset: 6.0.2(postcss@8.5.16) + postcss-normalize-display-values: 6.0.2(postcss@8.5.16) + postcss-normalize-positions: 6.0.2(postcss@8.5.16) + postcss-normalize-repeat-style: 6.0.2(postcss@8.5.16) + postcss-normalize-string: 6.0.2(postcss@8.5.16) + postcss-normalize-timing-functions: 6.0.2(postcss@8.5.16) + postcss-normalize-unicode: 6.1.0(postcss@8.5.16) + postcss-normalize-url: 6.0.2(postcss@8.5.16) + postcss-normalize-whitespace: 6.0.2(postcss@8.5.16) + postcss-ordered-values: 6.0.2(postcss@8.5.16) + postcss-reduce-initial: 6.1.0(postcss@8.5.16) + postcss-reduce-transforms: 6.0.2(postcss@8.5.16) + postcss-svgo: 6.0.3(postcss@8.5.16) + postcss-unique-selectors: 6.0.4(postcss@8.5.16) - cssnano-utils@4.0.2(postcss@8.5.15): + cssnano-utils@4.0.2(postcss@8.5.16): dependencies: - postcss: 8.5.15 + postcss: 8.5.16 - cssnano@6.1.2(postcss@8.5.15): + cssnano@6.1.2(postcss@8.5.16): dependencies: - cssnano-preset-default: 6.1.2(postcss@8.5.15) + cssnano-preset-default: 6.1.2(postcss@8.5.16) lilconfig: 3.1.3 - postcss: 8.5.15 + postcss: 8.5.16 csso@5.0.5: dependencies: @@ -23232,9 +23990,20 @@ snapshots: transitivePeerDependencies: - supports-color - docusaurus-plugin-image-zoom@3.0.1(@docusaurus/theme-classic@3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.0)(postcss@8.5.15))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(@types/react@19.2.17)(esbuild@0.28.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3)): + dockerode@5.0.1: dependencies: - '@docusaurus/theme-classic': 3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.0)(postcss@8.5.15))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(@types/react@19.2.17)(esbuild@0.28.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3) + '@balena/dockerignore': 1.0.2 + '@grpc/grpc-js': 1.12.5 + '@grpc/proto-loader': 0.7.13 + docker-modem: 5.0.7 + protobufjs: 7.4.0 + tar-fs: 2.1.4 + transitivePeerDependencies: + - supports-color + + docusaurus-plugin-image-zoom@3.0.1(@docusaurus/theme-classic@3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.1)(postcss@8.5.16))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(@types/react@19.2.17)(esbuild@0.28.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3)): + dependencies: + '@docusaurus/theme-classic': 3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.1)(postcss@8.5.16))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(@types/react@19.2.17)(esbuild@0.28.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3) medium-zoom: 1.1.0 validate-peer-dependencies: 2.2.0 @@ -23545,6 +24314,35 @@ snapshots: '@esbuild/win32-ia32': 0.28.0 '@esbuild/win32-x64': 0.28.0 + esbuild@0.28.1: + optionalDependencies: + '@esbuild/aix-ppc64': 0.28.1 + '@esbuild/android-arm': 0.28.1 + '@esbuild/android-arm64': 0.28.1 + '@esbuild/android-x64': 0.28.1 + '@esbuild/darwin-arm64': 0.28.1 + '@esbuild/darwin-x64': 0.28.1 + '@esbuild/freebsd-arm64': 0.28.1 + '@esbuild/freebsd-x64': 0.28.1 + '@esbuild/linux-arm': 0.28.1 + '@esbuild/linux-arm64': 0.28.1 + '@esbuild/linux-ia32': 0.28.1 + '@esbuild/linux-loong64': 0.28.1 + '@esbuild/linux-mips64el': 0.28.1 + '@esbuild/linux-ppc64': 0.28.1 + '@esbuild/linux-riscv64': 0.28.1 + '@esbuild/linux-s390x': 0.28.1 + '@esbuild/linux-x64': 0.28.1 + '@esbuild/netbsd-arm64': 0.28.1 + '@esbuild/netbsd-x64': 0.28.1 + '@esbuild/openbsd-arm64': 0.28.1 + '@esbuild/openbsd-x64': 0.28.1 + '@esbuild/openharmony-arm64': 0.28.1 + '@esbuild/sunos-x64': 0.28.1 + '@esbuild/win32-arm64': 0.28.1 + '@esbuild/win32-ia32': 0.28.1 + '@esbuild/win32-x64': 0.28.1 + escalade@3.2.0: {} escape-goat@4.0.0: {} @@ -23879,11 +24677,11 @@ snapshots: dependencies: is-unicode-supported: 2.1.0 - file-loader@6.2.0(webpack@5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)): + file-loader@6.2.0(webpack@5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)): dependencies: loader-utils: 2.0.4 schema-utils: 3.3.0 - webpack: 5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.15))(esbuild@0.28.0)(html-minifier-terser@7.2.0)(postcss@8.5.15) + webpack: 5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.16))(esbuild@0.28.1)(html-minifier-terser@7.2.0)(postcss@8.5.16) file-selector@2.1.2: dependencies: @@ -24516,7 +25314,7 @@ snapshots: html-void-elements@3.0.0: {} - html-webpack-plugin@5.6.7(@rspack/core@1.7.11(@swc/helpers@0.5.15))(webpack@5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)): + html-webpack-plugin@5.6.7(@rspack/core@1.7.11(@swc/helpers@0.5.15))(webpack@5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)): dependencies: '@types/html-minifier-terser': 6.1.0 html-minifier-terser: 6.1.0 @@ -24525,7 +25323,7 @@ snapshots: tapable: 2.3.3 optionalDependencies: '@rspack/core': 1.7.11(@swc/helpers@0.5.15) - webpack: 5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.15))(esbuild@0.28.0)(html-minifier-terser@7.2.0)(postcss@8.5.15) + webpack: 5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.16))(esbuild@0.28.1)(html-minifier-terser@7.2.0)(postcss@8.5.16) html-whitespace-sensitive-tag-names@3.0.1: {} @@ -24640,9 +25438,9 @@ snapshots: dependencies: safer-buffer: 2.1.2 - icss-utils@5.1.0(postcss@8.5.15): + icss-utils@5.1.0(postcss@8.5.16): dependencies: - postcss: 8.5.15 + postcss: 8.5.16 icu-minify@4.13.0: dependencies: @@ -25280,19 +26078,19 @@ snapshots: dependencies: semver: 7.7.4 - mantine-datatable@9.3.1(@mantine/core@9.3.2(@mantine/hooks@9.3.2(react@19.2.7))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@mantine/hooks@9.3.2(react@19.2.7))(clsx@2.1.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7): + mantine-datatable@9.3.1(@mantine/core@9.4.1(@mantine/hooks@9.4.1(react@19.2.7))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@mantine/hooks@9.4.1(react@19.2.7))(clsx@2.1.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7): dependencies: - '@mantine/core': 9.3.2(@mantine/hooks@9.3.2(react@19.2.7))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@mantine/hooks': 9.3.2(react@19.2.7) + '@mantine/core': 9.4.1(@mantine/hooks@9.4.1(react@19.2.7))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@mantine/hooks': 9.4.1(react@19.2.7) clsx: 2.1.1 react: 19.2.7 react-dom: 19.2.7(react@19.2.7) - mantine-react-table-open@9.0.3(@mantine/core@9.3.2(@mantine/hooks@9.3.2(react@19.2.7))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@mantine/dates@9.3.2(@mantine/core@9.3.2(@mantine/hooks@9.3.2(react@19.2.7))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@mantine/hooks@9.3.2(react@19.2.7))(dayjs@1.11.21)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@mantine/hooks@9.3.2(react@19.2.7))(@tabler/icons-react@3.44.0(react@19.2.7))(clsx@2.1.1)(dayjs@1.11.21)(react-dom@19.2.7(react@19.2.7))(react@19.2.7): + mantine-react-table-open@9.0.3(@mantine/core@9.4.1(@mantine/hooks@9.4.1(react@19.2.7))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@mantine/dates@9.4.1(@mantine/core@9.4.1(@mantine/hooks@9.4.1(react@19.2.7))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@mantine/hooks@9.4.1(react@19.2.7))(dayjs@1.11.21)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@mantine/hooks@9.4.1(react@19.2.7))(@tabler/icons-react@3.44.0(react@19.2.7))(clsx@2.1.1)(dayjs@1.11.21)(react-dom@19.2.7(react@19.2.7))(react@19.2.7): dependencies: - '@mantine/core': 9.3.2(@mantine/hooks@9.3.2(react@19.2.7))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@mantine/dates': 9.3.2(@mantine/core@9.3.2(@mantine/hooks@9.3.2(react@19.2.7))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@mantine/hooks@9.3.2(react@19.2.7))(dayjs@1.11.21)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@mantine/hooks': 9.3.2(react@19.2.7) + '@mantine/core': 9.4.1(@mantine/hooks@9.4.1(react@19.2.7))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@mantine/dates': 9.4.1(@mantine/core@9.4.1(@mantine/hooks@9.4.1(react@19.2.7))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@mantine/hooks@9.4.1(react@19.2.7))(dayjs@1.11.21)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@mantine/hooks': 9.4.1(react@19.2.7) '@tabler/icons-react': 3.44.0(react@19.2.7) '@tanstack/match-sorter-utils': 8.19.4 '@tanstack/react-table': 8.20.5(react-dom@19.2.7(react@19.2.7))(react@19.2.7) @@ -25325,23 +26123,23 @@ snapshots: math-intrinsics@1.1.0: {} - mcp-handler@1.1.0(@modelcontextprotocol/sdk@1.26.0(zod@4.4.3))(next@16.2.9(@opentelemetry/api@1.9.0)(@playwright/test@1.61.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(sass@1.101.0)): + mcp-handler@1.1.0(@modelcontextprotocol/sdk@1.26.0(zod@4.4.3))(next@16.2.9(@opentelemetry/api@1.9.0)(@playwright/test@1.61.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(sass@1.101.0)): dependencies: '@modelcontextprotocol/sdk': 1.26.0(zod@4.4.3) chalk: 5.6.2 commander: 11.1.0 redis: 4.7.1 optionalDependencies: - next: 16.2.9(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.61.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(sass@1.101.0) + next: 16.2.9(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.61.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(sass@1.101.0) - mcp-handler@1.1.0(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3))(next@16.2.9(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.61.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(sass@1.101.0)): + mcp-handler@1.1.0(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3))(next@16.2.9(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.61.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(sass@1.101.0)): dependencies: '@modelcontextprotocol/sdk': 1.29.0(zod@4.4.3) chalk: 5.6.2 commander: 11.1.0 redis: 4.7.1 optionalDependencies: - next: 16.2.9(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.61.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(sass@1.101.0) + next: 16.2.9(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.61.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(sass@1.101.0) mdast-util-directive@3.1.0: dependencies: @@ -25938,11 +26736,11 @@ snapshots: dependencies: dom-walk: 0.1.2 - mini-css-extract-plugin@2.10.2(webpack@5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)): + mini-css-extract-plugin@2.10.2(webpack@5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)): dependencies: schema-utils: 4.3.3 tapable: 2.3.3 - webpack: 5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.15))(esbuild@0.28.0)(html-minifier-terser@7.2.0)(postcss@8.5.15) + webpack: 5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.16))(esbuild@0.28.1)(html-minifier-terser@7.2.0)(postcss@8.5.16) minimalistic-assert@1.0.1: {} @@ -26000,7 +26798,7 @@ snapshots: statuses: 2.0.2 strict-event-emitter: 0.5.1 tough-cookie: 6.0.1 - type-fest: 5.6.0 + type-fest: 5.7.0 until-async: 3.0.2 yargs: 17.7.2 optionalDependencies: @@ -26051,9 +26849,7 @@ snapshots: nanoid@3.3.12: {} - nanoid@5.1.11: {} - - nanoid@5.1.15: {} + nanoid@5.1.16: {} nanostores@1.3.0: {} @@ -26071,22 +26867,22 @@ snapshots: neverpanic@0.0.8: {} - next-auth@5.0.0-beta.31(next@16.2.9(@opentelemetry/api@1.9.0)(@playwright/test@1.61.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(sass@1.101.0))(react@19.2.7): + next-auth@5.0.0-beta.31(next@16.2.9(@opentelemetry/api@1.9.0)(@playwright/test@1.61.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(sass@1.101.0))(react@19.2.7): dependencies: '@auth/core': 0.41.2 - next: 16.2.9(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.61.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(sass@1.101.0) + next: 16.2.9(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.61.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(sass@1.101.0) react: 19.2.7 next-intl-swc-plugin-extractor@4.13.0: {} - next-intl@4.13.0(next@16.2.9(@opentelemetry/api@1.9.0)(@playwright/test@1.61.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(sass@1.101.0))(react@19.2.7)(typescript@6.0.3): + next-intl@4.13.0(next@16.2.9(@opentelemetry/api@1.9.0)(@playwright/test@1.61.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(sass@1.101.0))(react@19.2.7)(typescript@6.0.3): dependencies: '@formatjs/intl-localematcher': 0.8.1 '@parcel/watcher': 2.4.1 '@swc/core': 1.15.3(@swc/helpers@0.5.15) icu-minify: 4.13.0 negotiator: 1.0.0 - next: 16.2.9(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.61.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(sass@1.101.0) + next: 16.2.9(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.61.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(sass@1.101.0) next-intl-swc-plugin-extractor: 4.13.0 po-parser: 2.1.1 react: 19.2.7 @@ -26096,7 +26892,7 @@ snapshots: transitivePeerDependencies: - '@swc/helpers' - next@16.2.9(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.61.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(sass@1.101.0): + next@16.2.9(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.61.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(sass@1.101.0): dependencies: '@next/env': 16.2.9 '@swc/helpers': 0.5.15 @@ -26116,7 +26912,7 @@ snapshots: '@next/swc-win32-arm64-msvc': 16.2.9 '@next/swc-win32-x64-msvc': 16.2.9 '@opentelemetry/api': 1.9.0 - '@playwright/test': 1.61.0 + '@playwright/test': 1.61.1 babel-plugin-react-compiler: 1.0.0 sass: 1.101.0 sharp: 0.34.5 @@ -26152,10 +26948,10 @@ snapshots: node-gyp-build@4.8.4: {} - node-loader@2.1.0(webpack@5.107.1(esbuild@0.28.0)(postcss@8.5.15)): + node-loader@2.1.0(webpack@5.107.1(esbuild@0.28.1)(postcss@8.5.16)): dependencies: loader-utils: 2.0.4 - webpack: 5.107.1(esbuild@0.28.0)(postcss@8.5.15) + webpack: 5.107.1(esbuild@0.28.1)(postcss@8.5.16) node-mock-http@1.0.4: {} @@ -26200,11 +26996,11 @@ snapshots: dependencies: boolbase: 1.0.0 - null-loader@4.0.1(webpack@5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)): + null-loader@4.0.1(webpack@5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)): dependencies: loader-utils: 2.0.4 schema-utils: 3.3.0 - webpack: 5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.15))(esbuild@0.28.0)(html-minifier-terser@7.2.0)(postcss@8.5.15) + webpack: 5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.16))(esbuild@0.28.1)(html-minifier-terser@7.2.0)(postcss@8.5.16) oauth4webapi@3.3.0: {} @@ -26300,51 +27096,51 @@ snapshots: outvariant@1.4.3: optional: true - oxfmt@0.55.0: + oxfmt@0.56.0: dependencies: tinypool: 2.1.0 optionalDependencies: - '@oxfmt/binding-android-arm-eabi': 0.55.0 - '@oxfmt/binding-android-arm64': 0.55.0 - '@oxfmt/binding-darwin-arm64': 0.55.0 - '@oxfmt/binding-darwin-x64': 0.55.0 - '@oxfmt/binding-freebsd-x64': 0.55.0 - '@oxfmt/binding-linux-arm-gnueabihf': 0.55.0 - '@oxfmt/binding-linux-arm-musleabihf': 0.55.0 - '@oxfmt/binding-linux-arm64-gnu': 0.55.0 - '@oxfmt/binding-linux-arm64-musl': 0.55.0 - '@oxfmt/binding-linux-ppc64-gnu': 0.55.0 - '@oxfmt/binding-linux-riscv64-gnu': 0.55.0 - '@oxfmt/binding-linux-riscv64-musl': 0.55.0 - '@oxfmt/binding-linux-s390x-gnu': 0.55.0 - '@oxfmt/binding-linux-x64-gnu': 0.55.0 - '@oxfmt/binding-linux-x64-musl': 0.55.0 - '@oxfmt/binding-openharmony-arm64': 0.55.0 - '@oxfmt/binding-win32-arm64-msvc': 0.55.0 - '@oxfmt/binding-win32-ia32-msvc': 0.55.0 - '@oxfmt/binding-win32-x64-msvc': 0.55.0 + '@oxfmt/binding-android-arm-eabi': 0.56.0 + '@oxfmt/binding-android-arm64': 0.56.0 + '@oxfmt/binding-darwin-arm64': 0.56.0 + '@oxfmt/binding-darwin-x64': 0.56.0 + '@oxfmt/binding-freebsd-x64': 0.56.0 + '@oxfmt/binding-linux-arm-gnueabihf': 0.56.0 + '@oxfmt/binding-linux-arm-musleabihf': 0.56.0 + '@oxfmt/binding-linux-arm64-gnu': 0.56.0 + '@oxfmt/binding-linux-arm64-musl': 0.56.0 + '@oxfmt/binding-linux-ppc64-gnu': 0.56.0 + '@oxfmt/binding-linux-riscv64-gnu': 0.56.0 + '@oxfmt/binding-linux-riscv64-musl': 0.56.0 + '@oxfmt/binding-linux-s390x-gnu': 0.56.0 + '@oxfmt/binding-linux-x64-gnu': 0.56.0 + '@oxfmt/binding-linux-x64-musl': 0.56.0 + '@oxfmt/binding-openharmony-arm64': 0.56.0 + '@oxfmt/binding-win32-arm64-msvc': 0.56.0 + '@oxfmt/binding-win32-ia32-msvc': 0.56.0 + '@oxfmt/binding-win32-x64-msvc': 0.56.0 - oxlint@1.70.0: + oxlint@1.71.0: optionalDependencies: - '@oxlint/binding-android-arm-eabi': 1.70.0 - '@oxlint/binding-android-arm64': 1.70.0 - '@oxlint/binding-darwin-arm64': 1.70.0 - '@oxlint/binding-darwin-x64': 1.70.0 - '@oxlint/binding-freebsd-x64': 1.70.0 - '@oxlint/binding-linux-arm-gnueabihf': 1.70.0 - '@oxlint/binding-linux-arm-musleabihf': 1.70.0 - '@oxlint/binding-linux-arm64-gnu': 1.70.0 - '@oxlint/binding-linux-arm64-musl': 1.70.0 - '@oxlint/binding-linux-ppc64-gnu': 1.70.0 - '@oxlint/binding-linux-riscv64-gnu': 1.70.0 - '@oxlint/binding-linux-riscv64-musl': 1.70.0 - '@oxlint/binding-linux-s390x-gnu': 1.70.0 - '@oxlint/binding-linux-x64-gnu': 1.70.0 - '@oxlint/binding-linux-x64-musl': 1.70.0 - '@oxlint/binding-openharmony-arm64': 1.70.0 - '@oxlint/binding-win32-arm64-msvc': 1.70.0 - '@oxlint/binding-win32-ia32-msvc': 1.70.0 - '@oxlint/binding-win32-x64-msvc': 1.70.0 + '@oxlint/binding-android-arm-eabi': 1.71.0 + '@oxlint/binding-android-arm64': 1.71.0 + '@oxlint/binding-darwin-arm64': 1.71.0 + '@oxlint/binding-darwin-x64': 1.71.0 + '@oxlint/binding-freebsd-x64': 1.71.0 + '@oxlint/binding-linux-arm-gnueabihf': 1.71.0 + '@oxlint/binding-linux-arm-musleabihf': 1.71.0 + '@oxlint/binding-linux-arm64-gnu': 1.71.0 + '@oxlint/binding-linux-arm64-musl': 1.71.0 + '@oxlint/binding-linux-ppc64-gnu': 1.71.0 + '@oxlint/binding-linux-riscv64-gnu': 1.71.0 + '@oxlint/binding-linux-riscv64-musl': 1.71.0 + '@oxlint/binding-linux-s390x-gnu': 1.71.0 + '@oxlint/binding-linux-x64-gnu': 1.71.0 + '@oxlint/binding-linux-x64-musl': 1.71.0 + '@oxlint/binding-openharmony-arm64': 1.71.0 + '@oxlint/binding-win32-arm64-msvc': 1.71.0 + '@oxlint/binding-win32-ia32-msvc': 1.71.0 + '@oxlint/binding-win32-x64-msvc': 1.71.0 p-cancelable@3.0.0: {} @@ -26573,8 +27369,6 @@ snapshots: picocolors@1.1.1: {} - picomatch-browser@2.2.6: {} - picomatch@2.3.1: {} picomatch@4.0.4: {} @@ -26625,11 +27419,11 @@ snapshots: pvutils: 1.1.5 tslib: 2.8.1 - playwright-core@1.61.0: {} + playwright-core@1.61.1: {} - playwright@1.61.0: + playwright@1.61.1: dependencies: - playwright-core: 1.61.0 + playwright-core: 1.61.1 optionalDependencies: fsevents: 2.3.2 @@ -26642,137 +27436,137 @@ snapshots: path-data-parser: 0.1.0 points-on-curve: 0.2.0 - postcss-attribute-case-insensitive@7.0.1(postcss@8.5.15): + postcss-attribute-case-insensitive@7.0.1(postcss@8.5.16): dependencies: - postcss: 8.5.15 + postcss: 8.5.16 postcss-selector-parser: 7.1.0 - postcss-calc@9.0.1(postcss@8.5.15): + postcss-calc@9.0.1(postcss@8.5.16): dependencies: - postcss: 8.5.15 + postcss: 8.5.16 postcss-selector-parser: 6.1.2 postcss-value-parser: 4.2.0 - postcss-clamp@4.1.0(postcss@8.5.15): + postcss-clamp@4.1.0(postcss@8.5.16): dependencies: - postcss: 8.5.15 + postcss: 8.5.16 postcss-value-parser: 4.2.0 - postcss-color-functional-notation@7.0.12(postcss@8.5.15): + postcss-color-functional-notation@7.0.12(postcss@8.5.16): dependencies: '@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.15) - '@csstools/utilities': 2.0.0(postcss@8.5.15) - postcss: 8.5.15 + '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.16) + '@csstools/utilities': 2.0.0(postcss@8.5.16) + postcss: 8.5.16 - postcss-color-hex-alpha@10.0.0(postcss@8.5.15): + postcss-color-hex-alpha@10.0.0(postcss@8.5.16): dependencies: - '@csstools/utilities': 2.0.0(postcss@8.5.15) - postcss: 8.5.15 + '@csstools/utilities': 2.0.0(postcss@8.5.16) + postcss: 8.5.16 postcss-value-parser: 4.2.0 - postcss-color-rebeccapurple@10.0.0(postcss@8.5.15): + postcss-color-rebeccapurple@10.0.0(postcss@8.5.16): dependencies: - '@csstools/utilities': 2.0.0(postcss@8.5.15) - postcss: 8.5.15 + '@csstools/utilities': 2.0.0(postcss@8.5.16) + postcss: 8.5.16 postcss-value-parser: 4.2.0 - postcss-colormin@6.1.0(postcss@8.5.15): + postcss-colormin@6.1.0(postcss@8.5.16): dependencies: browserslist: 4.28.2 caniuse-api: 3.0.0 colord: 2.9.3 - postcss: 8.5.15 + postcss: 8.5.16 postcss-value-parser: 4.2.0 - postcss-convert-values@6.1.0(postcss@8.5.15): + postcss-convert-values@6.1.0(postcss@8.5.16): dependencies: browserslist: 4.28.2 - postcss: 8.5.15 + postcss: 8.5.16 postcss-value-parser: 4.2.0 - postcss-custom-media@11.0.6(postcss@8.5.15): + postcss-custom-media@11.0.6(postcss@8.5.16): dependencies: '@csstools/cascade-layer-name-parser': 2.0.5(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 '@csstools/media-query-list-parser': 4.0.3(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) - postcss: 8.5.15 + postcss: 8.5.16 - postcss-custom-properties@14.0.6(postcss@8.5.15): + postcss-custom-properties@14.0.6(postcss@8.5.16): dependencies: '@csstools/cascade-layer-name-parser': 2.0.5(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - '@csstools/utilities': 2.0.0(postcss@8.5.15) - postcss: 8.5.15 + '@csstools/utilities': 2.0.0(postcss@8.5.16) + postcss: 8.5.16 postcss-value-parser: 4.2.0 - postcss-custom-selectors@8.0.5(postcss@8.5.15): + postcss-custom-selectors@8.0.5(postcss@8.5.16): dependencies: '@csstools/cascade-layer-name-parser': 2.0.5(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - postcss: 8.5.15 + postcss: 8.5.16 postcss-selector-parser: 7.1.0 - postcss-dir-pseudo-class@9.0.1(postcss@8.5.15): + postcss-dir-pseudo-class@9.0.1(postcss@8.5.16): dependencies: - postcss: 8.5.15 + postcss: 8.5.16 postcss-selector-parser: 7.1.0 - postcss-discard-comments@6.0.2(postcss@8.5.15): + postcss-discard-comments@6.0.2(postcss@8.5.16): dependencies: - postcss: 8.5.15 + postcss: 8.5.16 - postcss-discard-duplicates@6.0.3(postcss@8.5.15): + postcss-discard-duplicates@6.0.3(postcss@8.5.16): dependencies: - postcss: 8.5.15 + postcss: 8.5.16 - postcss-discard-empty@6.0.3(postcss@8.5.15): + postcss-discard-empty@6.0.3(postcss@8.5.16): dependencies: - postcss: 8.5.15 + postcss: 8.5.16 - postcss-discard-overridden@6.0.2(postcss@8.5.15): + postcss-discard-overridden@6.0.2(postcss@8.5.16): dependencies: - postcss: 8.5.15 + postcss: 8.5.16 - postcss-discard-unused@6.0.5(postcss@8.5.15): + postcss-discard-unused@6.0.5(postcss@8.5.16): dependencies: - postcss: 8.5.15 + postcss: 8.5.16 postcss-selector-parser: 6.1.2 - postcss-double-position-gradients@6.0.4(postcss@8.5.15): + postcss-double-position-gradients@6.0.4(postcss@8.5.16): dependencies: - '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.15) - '@csstools/utilities': 2.0.0(postcss@8.5.15) - postcss: 8.5.15 + '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.16) + '@csstools/utilities': 2.0.0(postcss@8.5.16) + postcss: 8.5.16 postcss-value-parser: 4.2.0 - postcss-focus-visible@10.0.1(postcss@8.5.15): + postcss-focus-visible@10.0.1(postcss@8.5.16): dependencies: - postcss: 8.5.15 + postcss: 8.5.16 postcss-selector-parser: 7.1.0 - postcss-focus-within@9.0.1(postcss@8.5.15): + postcss-focus-within@9.0.1(postcss@8.5.16): dependencies: - postcss: 8.5.15 + postcss: 8.5.16 postcss-selector-parser: 7.1.0 - postcss-font-variant@5.0.0(postcss@8.5.15): + postcss-font-variant@5.0.0(postcss@8.5.16): dependencies: - postcss: 8.5.15 + postcss: 8.5.16 - postcss-gap-properties@6.0.0(postcss@8.5.15): + postcss-gap-properties@6.0.0(postcss@8.5.16): dependencies: - postcss: 8.5.15 + postcss: 8.5.16 - postcss-image-set-function@7.0.0(postcss@8.5.15): + postcss-image-set-function@7.0.0(postcss@8.5.16): dependencies: - '@csstools/utilities': 2.0.0(postcss@8.5.15) - postcss: 8.5.15 + '@csstools/utilities': 2.0.0(postcss@8.5.16) + postcss: 8.5.16 postcss-value-parser: 4.2.0 postcss-js@4.0.1(postcss@8.5.15): @@ -26780,72 +27574,72 @@ snapshots: camelcase-css: 2.0.1 postcss: 8.5.15 - postcss-lab-function@7.0.12(postcss@8.5.15): + postcss-lab-function@7.0.12(postcss@8.5.16): dependencies: '@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.15) - '@csstools/utilities': 2.0.0(postcss@8.5.15) - postcss: 8.5.15 + '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.16) + '@csstools/utilities': 2.0.0(postcss@8.5.16) + postcss: 8.5.16 - postcss-loader@7.3.4(postcss@8.5.15)(typescript@6.0.3)(webpack@5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)): + postcss-loader@7.3.4(postcss@8.5.16)(typescript@6.0.3)(webpack@5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)): dependencies: cosmiconfig: 8.3.6(typescript@6.0.3) jiti: 1.21.7 - postcss: 8.5.15 + postcss: 8.5.16 semver: 7.7.4 - webpack: 5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.15))(esbuild@0.28.0)(html-minifier-terser@7.2.0)(postcss@8.5.15) + webpack: 5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.16))(esbuild@0.28.1)(html-minifier-terser@7.2.0)(postcss@8.5.16) transitivePeerDependencies: - typescript - postcss-logical@8.1.0(postcss@8.5.15): + postcss-logical@8.1.0(postcss@8.5.16): dependencies: - postcss: 8.5.15 + postcss: 8.5.16 postcss-value-parser: 4.2.0 - postcss-merge-idents@6.0.3(postcss@8.5.15): + postcss-merge-idents@6.0.3(postcss@8.5.16): dependencies: - cssnano-utils: 4.0.2(postcss@8.5.15) - postcss: 8.5.15 + cssnano-utils: 4.0.2(postcss@8.5.16) + postcss: 8.5.16 postcss-value-parser: 4.2.0 - postcss-merge-longhand@6.0.5(postcss@8.5.15): + postcss-merge-longhand@6.0.5(postcss@8.5.16): dependencies: - postcss: 8.5.15 + postcss: 8.5.16 postcss-value-parser: 4.2.0 - stylehacks: 6.1.1(postcss@8.5.15) + stylehacks: 6.1.1(postcss@8.5.16) - postcss-merge-rules@6.1.1(postcss@8.5.15): + postcss-merge-rules@6.1.1(postcss@8.5.16): dependencies: browserslist: 4.28.2 caniuse-api: 3.0.0 - cssnano-utils: 4.0.2(postcss@8.5.15) - postcss: 8.5.15 + cssnano-utils: 4.0.2(postcss@8.5.16) + postcss: 8.5.16 postcss-selector-parser: 6.1.2 - postcss-minify-font-values@6.1.0(postcss@8.5.15): + postcss-minify-font-values@6.1.0(postcss@8.5.16): dependencies: - postcss: 8.5.15 + postcss: 8.5.16 postcss-value-parser: 4.2.0 - postcss-minify-gradients@6.0.3(postcss@8.5.15): + postcss-minify-gradients@6.0.3(postcss@8.5.16): dependencies: colord: 2.9.3 - cssnano-utils: 4.0.2(postcss@8.5.15) - postcss: 8.5.15 + cssnano-utils: 4.0.2(postcss@8.5.16) + postcss: 8.5.16 postcss-value-parser: 4.2.0 - postcss-minify-params@6.1.0(postcss@8.5.15): + postcss-minify-params@6.1.0(postcss@8.5.16): dependencies: browserslist: 4.28.2 - cssnano-utils: 4.0.2(postcss@8.5.15) - postcss: 8.5.15 + cssnano-utils: 4.0.2(postcss@8.5.16) + postcss: 8.5.16 postcss-value-parser: 4.2.0 - postcss-minify-selectors@6.0.4(postcss@8.5.15): + postcss-minify-selectors@6.0.4(postcss@8.5.16): dependencies: - postcss: 8.5.15 + postcss: 8.5.16 postcss-selector-parser: 6.1.2 postcss-mixins@12.0.0(postcss@8.5.15): @@ -26856,182 +27650,182 @@ snapshots: sugarss: 5.0.0(postcss@8.5.15) tinyglobby: 0.2.17 - postcss-modules-extract-imports@3.1.0(postcss@8.5.15): + postcss-modules-extract-imports@3.1.0(postcss@8.5.16): dependencies: - postcss: 8.5.15 + postcss: 8.5.16 - postcss-modules-local-by-default@4.2.0(postcss@8.5.15): + postcss-modules-local-by-default@4.2.0(postcss@8.5.16): dependencies: - icss-utils: 5.1.0(postcss@8.5.15) - postcss: 8.5.15 + icss-utils: 5.1.0(postcss@8.5.16) + postcss: 8.5.16 postcss-selector-parser: 7.1.0 postcss-value-parser: 4.2.0 - postcss-modules-scope@3.2.1(postcss@8.5.15): + postcss-modules-scope@3.2.1(postcss@8.5.16): dependencies: - postcss: 8.5.15 + postcss: 8.5.16 postcss-selector-parser: 7.1.0 - postcss-modules-values@4.0.0(postcss@8.5.15): + postcss-modules-values@4.0.0(postcss@8.5.16): dependencies: - icss-utils: 5.1.0(postcss@8.5.15) - postcss: 8.5.15 + icss-utils: 5.1.0(postcss@8.5.16) + postcss: 8.5.16 postcss-nested@7.0.2(postcss@8.5.15): dependencies: postcss: 8.5.15 postcss-selector-parser: 7.1.0 - postcss-nesting@13.0.2(postcss@8.5.15): + postcss-nesting@13.0.2(postcss@8.5.16): dependencies: '@csstools/selector-resolve-nested': 3.1.0(postcss-selector-parser@7.1.0) '@csstools/selector-specificity': 5.0.0(postcss-selector-parser@7.1.0) - postcss: 8.5.15 + postcss: 8.5.16 postcss-selector-parser: 7.1.0 - postcss-normalize-charset@6.0.2(postcss@8.5.15): + postcss-normalize-charset@6.0.2(postcss@8.5.16): dependencies: - postcss: 8.5.15 + postcss: 8.5.16 - postcss-normalize-display-values@6.0.2(postcss@8.5.15): + postcss-normalize-display-values@6.0.2(postcss@8.5.16): dependencies: - postcss: 8.5.15 + postcss: 8.5.16 postcss-value-parser: 4.2.0 - postcss-normalize-positions@6.0.2(postcss@8.5.15): + postcss-normalize-positions@6.0.2(postcss@8.5.16): dependencies: - postcss: 8.5.15 + postcss: 8.5.16 postcss-value-parser: 4.2.0 - postcss-normalize-repeat-style@6.0.2(postcss@8.5.15): + postcss-normalize-repeat-style@6.0.2(postcss@8.5.16): dependencies: - postcss: 8.5.15 + postcss: 8.5.16 postcss-value-parser: 4.2.0 - postcss-normalize-string@6.0.2(postcss@8.5.15): + postcss-normalize-string@6.0.2(postcss@8.5.16): dependencies: - postcss: 8.5.15 + postcss: 8.5.16 postcss-value-parser: 4.2.0 - postcss-normalize-timing-functions@6.0.2(postcss@8.5.15): + postcss-normalize-timing-functions@6.0.2(postcss@8.5.16): dependencies: - postcss: 8.5.15 + postcss: 8.5.16 postcss-value-parser: 4.2.0 - postcss-normalize-unicode@6.1.0(postcss@8.5.15): + postcss-normalize-unicode@6.1.0(postcss@8.5.16): dependencies: browserslist: 4.28.2 - postcss: 8.5.15 + postcss: 8.5.16 postcss-value-parser: 4.2.0 - postcss-normalize-url@6.0.2(postcss@8.5.15): + postcss-normalize-url@6.0.2(postcss@8.5.16): dependencies: - postcss: 8.5.15 + postcss: 8.5.16 postcss-value-parser: 4.2.0 - postcss-normalize-whitespace@6.0.2(postcss@8.5.15): + postcss-normalize-whitespace@6.0.2(postcss@8.5.16): dependencies: - postcss: 8.5.15 + postcss: 8.5.16 postcss-value-parser: 4.2.0 - postcss-opacity-percentage@3.0.0(postcss@8.5.15): + postcss-opacity-percentage@3.0.0(postcss@8.5.16): dependencies: - postcss: 8.5.15 + postcss: 8.5.16 - postcss-ordered-values@6.0.2(postcss@8.5.15): + postcss-ordered-values@6.0.2(postcss@8.5.16): dependencies: - cssnano-utils: 4.0.2(postcss@8.5.15) - postcss: 8.5.15 + cssnano-utils: 4.0.2(postcss@8.5.16) + postcss: 8.5.16 postcss-value-parser: 4.2.0 - postcss-overflow-shorthand@6.0.0(postcss@8.5.15): + postcss-overflow-shorthand@6.0.0(postcss@8.5.16): dependencies: - postcss: 8.5.15 + postcss: 8.5.16 postcss-value-parser: 4.2.0 - postcss-page-break@3.0.4(postcss@8.5.15): + postcss-page-break@3.0.4(postcss@8.5.16): dependencies: - postcss: 8.5.15 + postcss: 8.5.16 - postcss-place@10.0.0(postcss@8.5.15): + postcss-place@10.0.0(postcss@8.5.16): dependencies: - postcss: 8.5.15 + postcss: 8.5.16 postcss-value-parser: 4.2.0 - postcss-preset-env@10.6.1(postcss@8.5.15): + postcss-preset-env@10.6.1(postcss@8.5.16): dependencies: - '@csstools/postcss-alpha-function': 1.0.1(postcss@8.5.15) - '@csstools/postcss-cascade-layers': 5.0.2(postcss@8.5.15) - '@csstools/postcss-color-function': 4.0.12(postcss@8.5.15) - '@csstools/postcss-color-function-display-p3-linear': 1.0.1(postcss@8.5.15) - '@csstools/postcss-color-mix-function': 3.0.12(postcss@8.5.15) - '@csstools/postcss-color-mix-variadic-function-arguments': 1.0.2(postcss@8.5.15) - '@csstools/postcss-content-alt-text': 2.0.8(postcss@8.5.15) - '@csstools/postcss-contrast-color-function': 2.0.12(postcss@8.5.15) - '@csstools/postcss-exponential-functions': 2.0.9(postcss@8.5.15) - '@csstools/postcss-font-format-keywords': 4.0.0(postcss@8.5.15) - '@csstools/postcss-gamut-mapping': 2.0.11(postcss@8.5.15) - '@csstools/postcss-gradients-interpolation-method': 5.0.12(postcss@8.5.15) - '@csstools/postcss-hwb-function': 4.0.12(postcss@8.5.15) - '@csstools/postcss-ic-unit': 4.0.4(postcss@8.5.15) - '@csstools/postcss-initial': 2.0.1(postcss@8.5.15) - '@csstools/postcss-is-pseudo-class': 5.0.3(postcss@8.5.15) - '@csstools/postcss-light-dark-function': 2.0.11(postcss@8.5.15) - '@csstools/postcss-logical-float-and-clear': 3.0.0(postcss@8.5.15) - '@csstools/postcss-logical-overflow': 2.0.0(postcss@8.5.15) - '@csstools/postcss-logical-overscroll-behavior': 2.0.0(postcss@8.5.15) - '@csstools/postcss-logical-resize': 3.0.0(postcss@8.5.15) - '@csstools/postcss-logical-viewport-units': 3.0.4(postcss@8.5.15) - '@csstools/postcss-media-minmax': 2.0.9(postcss@8.5.15) - '@csstools/postcss-media-queries-aspect-ratio-number-values': 3.0.5(postcss@8.5.15) - '@csstools/postcss-nested-calc': 4.0.0(postcss@8.5.15) - '@csstools/postcss-normalize-display-values': 4.0.1(postcss@8.5.15) - '@csstools/postcss-oklab-function': 4.0.12(postcss@8.5.15) - '@csstools/postcss-position-area-property': 1.0.0(postcss@8.5.15) - '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.15) - '@csstools/postcss-property-rule-prelude-list': 1.0.0(postcss@8.5.15) - '@csstools/postcss-random-function': 2.0.1(postcss@8.5.15) - '@csstools/postcss-relative-color-syntax': 3.0.12(postcss@8.5.15) - '@csstools/postcss-scope-pseudo-class': 4.0.1(postcss@8.5.15) - '@csstools/postcss-sign-functions': 1.1.4(postcss@8.5.15) - '@csstools/postcss-stepped-value-functions': 4.0.9(postcss@8.5.15) - '@csstools/postcss-syntax-descriptor-syntax-production': 1.0.1(postcss@8.5.15) - '@csstools/postcss-system-ui-font-family': 1.0.0(postcss@8.5.15) - '@csstools/postcss-text-decoration-shorthand': 4.0.3(postcss@8.5.15) - '@csstools/postcss-trigonometric-functions': 4.0.9(postcss@8.5.15) - '@csstools/postcss-unset-value': 4.0.0(postcss@8.5.15) - autoprefixer: 10.5.0(postcss@8.5.15) + '@csstools/postcss-alpha-function': 1.0.1(postcss@8.5.16) + '@csstools/postcss-cascade-layers': 5.0.2(postcss@8.5.16) + '@csstools/postcss-color-function': 4.0.12(postcss@8.5.16) + '@csstools/postcss-color-function-display-p3-linear': 1.0.1(postcss@8.5.16) + '@csstools/postcss-color-mix-function': 3.0.12(postcss@8.5.16) + '@csstools/postcss-color-mix-variadic-function-arguments': 1.0.2(postcss@8.5.16) + '@csstools/postcss-content-alt-text': 2.0.8(postcss@8.5.16) + '@csstools/postcss-contrast-color-function': 2.0.12(postcss@8.5.16) + '@csstools/postcss-exponential-functions': 2.0.9(postcss@8.5.16) + '@csstools/postcss-font-format-keywords': 4.0.0(postcss@8.5.16) + '@csstools/postcss-gamut-mapping': 2.0.11(postcss@8.5.16) + '@csstools/postcss-gradients-interpolation-method': 5.0.12(postcss@8.5.16) + '@csstools/postcss-hwb-function': 4.0.12(postcss@8.5.16) + '@csstools/postcss-ic-unit': 4.0.4(postcss@8.5.16) + '@csstools/postcss-initial': 2.0.1(postcss@8.5.16) + '@csstools/postcss-is-pseudo-class': 5.0.3(postcss@8.5.16) + '@csstools/postcss-light-dark-function': 2.0.11(postcss@8.5.16) + '@csstools/postcss-logical-float-and-clear': 3.0.0(postcss@8.5.16) + '@csstools/postcss-logical-overflow': 2.0.0(postcss@8.5.16) + '@csstools/postcss-logical-overscroll-behavior': 2.0.0(postcss@8.5.16) + '@csstools/postcss-logical-resize': 3.0.0(postcss@8.5.16) + '@csstools/postcss-logical-viewport-units': 3.0.4(postcss@8.5.16) + '@csstools/postcss-media-minmax': 2.0.9(postcss@8.5.16) + '@csstools/postcss-media-queries-aspect-ratio-number-values': 3.0.5(postcss@8.5.16) + '@csstools/postcss-nested-calc': 4.0.0(postcss@8.5.16) + '@csstools/postcss-normalize-display-values': 4.0.1(postcss@8.5.16) + '@csstools/postcss-oklab-function': 4.0.12(postcss@8.5.16) + '@csstools/postcss-position-area-property': 1.0.0(postcss@8.5.16) + '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.16) + '@csstools/postcss-property-rule-prelude-list': 1.0.0(postcss@8.5.16) + '@csstools/postcss-random-function': 2.0.1(postcss@8.5.16) + '@csstools/postcss-relative-color-syntax': 3.0.12(postcss@8.5.16) + '@csstools/postcss-scope-pseudo-class': 4.0.1(postcss@8.5.16) + '@csstools/postcss-sign-functions': 1.1.4(postcss@8.5.16) + '@csstools/postcss-stepped-value-functions': 4.0.9(postcss@8.5.16) + '@csstools/postcss-syntax-descriptor-syntax-production': 1.0.1(postcss@8.5.16) + '@csstools/postcss-system-ui-font-family': 1.0.0(postcss@8.5.16) + '@csstools/postcss-text-decoration-shorthand': 4.0.3(postcss@8.5.16) + '@csstools/postcss-trigonometric-functions': 4.0.9(postcss@8.5.16) + '@csstools/postcss-unset-value': 4.0.0(postcss@8.5.16) + autoprefixer: 10.5.0(postcss@8.5.16) browserslist: 4.28.2 - css-blank-pseudo: 7.0.1(postcss@8.5.15) - css-has-pseudo: 7.0.3(postcss@8.5.15) - css-prefers-color-scheme: 10.0.0(postcss@8.5.15) + css-blank-pseudo: 7.0.1(postcss@8.5.16) + css-has-pseudo: 7.0.3(postcss@8.5.16) + css-prefers-color-scheme: 10.0.0(postcss@8.5.16) cssdb: 8.9.0 - postcss: 8.5.15 - postcss-attribute-case-insensitive: 7.0.1(postcss@8.5.15) - postcss-clamp: 4.1.0(postcss@8.5.15) - postcss-color-functional-notation: 7.0.12(postcss@8.5.15) - postcss-color-hex-alpha: 10.0.0(postcss@8.5.15) - postcss-color-rebeccapurple: 10.0.0(postcss@8.5.15) - postcss-custom-media: 11.0.6(postcss@8.5.15) - postcss-custom-properties: 14.0.6(postcss@8.5.15) - postcss-custom-selectors: 8.0.5(postcss@8.5.15) - postcss-dir-pseudo-class: 9.0.1(postcss@8.5.15) - postcss-double-position-gradients: 6.0.4(postcss@8.5.15) - postcss-focus-visible: 10.0.1(postcss@8.5.15) - postcss-focus-within: 9.0.1(postcss@8.5.15) - postcss-font-variant: 5.0.0(postcss@8.5.15) - postcss-gap-properties: 6.0.0(postcss@8.5.15) - postcss-image-set-function: 7.0.0(postcss@8.5.15) - postcss-lab-function: 7.0.12(postcss@8.5.15) - postcss-logical: 8.1.0(postcss@8.5.15) - postcss-nesting: 13.0.2(postcss@8.5.15) - postcss-opacity-percentage: 3.0.0(postcss@8.5.15) - postcss-overflow-shorthand: 6.0.0(postcss@8.5.15) - postcss-page-break: 3.0.4(postcss@8.5.15) - postcss-place: 10.0.0(postcss@8.5.15) - postcss-pseudo-class-any-link: 10.0.1(postcss@8.5.15) - postcss-replace-overflow-wrap: 4.0.0(postcss@8.5.15) - postcss-selector-not: 8.0.1(postcss@8.5.15) + postcss: 8.5.16 + postcss-attribute-case-insensitive: 7.0.1(postcss@8.5.16) + postcss-clamp: 4.1.0(postcss@8.5.16) + postcss-color-functional-notation: 7.0.12(postcss@8.5.16) + postcss-color-hex-alpha: 10.0.0(postcss@8.5.16) + postcss-color-rebeccapurple: 10.0.0(postcss@8.5.16) + postcss-custom-media: 11.0.6(postcss@8.5.16) + postcss-custom-properties: 14.0.6(postcss@8.5.16) + postcss-custom-selectors: 8.0.5(postcss@8.5.16) + postcss-dir-pseudo-class: 9.0.1(postcss@8.5.16) + postcss-double-position-gradients: 6.0.4(postcss@8.5.16) + postcss-focus-visible: 10.0.1(postcss@8.5.16) + postcss-focus-within: 9.0.1(postcss@8.5.16) + postcss-font-variant: 5.0.0(postcss@8.5.16) + postcss-gap-properties: 6.0.0(postcss@8.5.16) + postcss-image-set-function: 7.0.0(postcss@8.5.16) + postcss-lab-function: 7.0.12(postcss@8.5.16) + postcss-logical: 8.1.0(postcss@8.5.16) + postcss-nesting: 13.0.2(postcss@8.5.16) + postcss-opacity-percentage: 3.0.0(postcss@8.5.16) + postcss-overflow-shorthand: 6.0.0(postcss@8.5.16) + postcss-page-break: 3.0.4(postcss@8.5.16) + postcss-place: 10.0.0(postcss@8.5.16) + postcss-pseudo-class-any-link: 10.0.1(postcss@8.5.16) + postcss-replace-overflow-wrap: 4.0.0(postcss@8.5.16) + postcss-selector-not: 8.0.1(postcss@8.5.16) postcss-preset-mantine@1.18.0(postcss@8.5.15): dependencies: @@ -27039,34 +27833,34 @@ snapshots: postcss-mixins: 12.0.0(postcss@8.5.15) postcss-nested: 7.0.2(postcss@8.5.15) - postcss-pseudo-class-any-link@10.0.1(postcss@8.5.15): + postcss-pseudo-class-any-link@10.0.1(postcss@8.5.16): dependencies: - postcss: 8.5.15 + postcss: 8.5.16 postcss-selector-parser: 7.1.0 - postcss-reduce-idents@6.0.3(postcss@8.5.15): + postcss-reduce-idents@6.0.3(postcss@8.5.16): dependencies: - postcss: 8.5.15 + postcss: 8.5.16 postcss-value-parser: 4.2.0 - postcss-reduce-initial@6.1.0(postcss@8.5.15): + postcss-reduce-initial@6.1.0(postcss@8.5.16): dependencies: browserslist: 4.28.2 caniuse-api: 3.0.0 - postcss: 8.5.15 + postcss: 8.5.16 - postcss-reduce-transforms@6.0.2(postcss@8.5.15): + postcss-reduce-transforms@6.0.2(postcss@8.5.16): dependencies: - postcss: 8.5.15 + postcss: 8.5.16 postcss-value-parser: 4.2.0 - postcss-replace-overflow-wrap@4.0.0(postcss@8.5.15): + postcss-replace-overflow-wrap@4.0.0(postcss@8.5.16): dependencies: - postcss: 8.5.15 + postcss: 8.5.16 - postcss-selector-not@8.0.1(postcss@8.5.15): + postcss-selector-not@8.0.1(postcss@8.5.16): dependencies: - postcss: 8.5.15 + postcss: 8.5.16 postcss-selector-parser: 7.1.0 postcss-selector-parser@6.1.2: @@ -27083,27 +27877,27 @@ snapshots: dependencies: postcss: 8.5.15 - postcss-sort-media-queries@5.2.0(postcss@8.5.15): + postcss-sort-media-queries@5.2.0(postcss@8.5.16): dependencies: - postcss: 8.5.15 + postcss: 8.5.16 sort-css-media-queries: 2.2.0 - postcss-svgo@6.0.3(postcss@8.5.15): + postcss-svgo@6.0.3(postcss@8.5.16): dependencies: - postcss: 8.5.15 + postcss: 8.5.16 postcss-value-parser: 4.2.0 svgo: 3.3.3 - postcss-unique-selectors@6.0.4(postcss@8.5.15): + postcss-unique-selectors@6.0.4(postcss@8.5.16): dependencies: - postcss: 8.5.15 + postcss: 8.5.16 postcss-selector-parser: 6.1.2 postcss-value-parser@4.2.0: {} - postcss-zindex@6.0.2(postcss@8.5.15): + postcss-zindex@6.0.2(postcss@8.5.16): dependencies: - postcss: 8.5.15 + postcss: 8.5.16 postcss@8.4.31: dependencies: @@ -27117,6 +27911,12 @@ snapshots: picocolors: 1.1.1 source-map-js: 1.2.1 + postcss@8.5.16: + dependencies: + nanoid: 3.3.12 + picocolors: 1.1.1 + source-map-js: 1.2.1 + postgres-array@2.0.0: {} postgres-bytea@1.0.0: {} @@ -27129,10 +27929,10 @@ snapshots: posthog-docusaurus@2.0.5: {} - posthog-js@1.391.2: + posthog-js@1.395.0: dependencies: - '@posthog/core': 1.35.3 - '@posthog/types': 1.390.2 + '@posthog/core': 1.38.0 + '@posthog/types': 1.391.1 core-js: 3.49.0 dompurify: 3.4.10 fflate: 0.4.8 @@ -27166,7 +27966,7 @@ snapshots: pump: 3.0.2 rc: 1.2.8 simple-get: 4.0.1 - tar-fs: 3.1.2 + tar-fs: 3.1.3 tunnel-agent: 0.6.0 transitivePeerDependencies: - bare-buffer @@ -27382,7 +28182,7 @@ snapshots: aria-hidden: 1.2.6 defu: 6.1.7 fast-deep-equal: 3.1.3 - nanoid: 5.1.15 + nanoid: 5.1.16 vue: 3.5.35(typescript@6.0.3) transitivePeerDependencies: - '@vue/composition-api' @@ -27399,8 +28199,6 @@ snapshots: range-parser@1.2.1: {} - rate-limiter-flexible@8.1.0: {} - raw-body@2.5.3: dependencies: bytes: 3.1.2 @@ -27456,11 +28254,11 @@ snapshots: '@types/react': 18.3.31 '@types/react-dom': 18.3.7(@types/react@18.3.31) - react-loadable-ssr-addon-v5-slorber@1.0.3(@docusaurus/react-loadable@6.0.0(react@19.2.7))(webpack@5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)): + react-loadable-ssr-addon-v5-slorber@1.0.3(@docusaurus/react-loadable@6.0.0(react@19.2.7))(webpack@5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)): dependencies: '@babel/runtime': 7.29.7 react-loadable: '@docusaurus/react-loadable@6.0.0(react@19.2.7)' - webpack: 5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.15))(esbuild@0.28.0)(html-minifier-terser@7.2.0)(postcss@8.5.15) + webpack: 5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.16))(esbuild@0.28.1)(html-minifier-terser@7.2.0)(postcss@8.5.16) react-markdown@10.1.0(@types/react@19.2.17)(react@19.2.7): dependencies: @@ -27644,7 +28442,7 @@ snapshots: real-require@0.2.0: {} - recharts@3.8.1(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react-is@19.2.7)(react@19.2.7)(redux@5.0.1): + recharts@3.9.0(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react-is@19.2.7)(react@19.2.7)(redux@5.0.1): dependencies: '@reduxjs/toolkit': 2.11.2(react-redux@9.2.0(@types/react@19.2.17)(react@19.2.7)(redux@5.0.1))(react@19.2.7) clsx: 2.1.1 @@ -27656,7 +28454,7 @@ snapshots: react-dom: 19.2.7(react@19.2.7) react-is: 19.2.7 react-redux: 9.2.0(@types/react@19.2.17)(react@19.2.7)(redux@5.0.1) - reselect: 5.1.1 + reselect: 5.2.0 tiny-invariant: 1.3.3 use-sync-external-store: 1.6.0(react@19.2.7) victory-vendor: 37.3.6 @@ -27894,6 +28692,8 @@ snapshots: reselect@5.1.1: {} + reselect@5.2.0: {} + reserved-identifiers@1.2.0: {} resolve-alpn@1.2.1: {} @@ -27938,26 +28738,26 @@ snapshots: robust-predicates@3.0.3: {} - rolldown@1.0.3: + rolldown@1.1.3: dependencies: - '@oxc-project/types': 0.133.0 + '@oxc-project/types': 0.137.0 '@rolldown/pluginutils': 1.0.1 optionalDependencies: - '@rolldown/binding-android-arm64': 1.0.3 - '@rolldown/binding-darwin-arm64': 1.0.3 - '@rolldown/binding-darwin-x64': 1.0.3 - '@rolldown/binding-freebsd-x64': 1.0.3 - '@rolldown/binding-linux-arm-gnueabihf': 1.0.3 - '@rolldown/binding-linux-arm64-gnu': 1.0.3 - '@rolldown/binding-linux-arm64-musl': 1.0.3 - '@rolldown/binding-linux-ppc64-gnu': 1.0.3 - '@rolldown/binding-linux-s390x-gnu': 1.0.3 - '@rolldown/binding-linux-x64-gnu': 1.0.3 - '@rolldown/binding-linux-x64-musl': 1.0.3 - '@rolldown/binding-openharmony-arm64': 1.0.3 - '@rolldown/binding-wasm32-wasi': 1.0.3 - '@rolldown/binding-win32-arm64-msvc': 1.0.3 - '@rolldown/binding-win32-x64-msvc': 1.0.3 + '@rolldown/binding-android-arm64': 1.1.3 + '@rolldown/binding-darwin-arm64': 1.1.3 + '@rolldown/binding-darwin-x64': 1.1.3 + '@rolldown/binding-freebsd-x64': 1.1.3 + '@rolldown/binding-linux-arm-gnueabihf': 1.1.3 + '@rolldown/binding-linux-arm64-gnu': 1.1.3 + '@rolldown/binding-linux-arm64-musl': 1.1.3 + '@rolldown/binding-linux-ppc64-gnu': 1.1.3 + '@rolldown/binding-linux-s390x-gnu': 1.1.3 + '@rolldown/binding-linux-x64-gnu': 1.1.3 + '@rolldown/binding-linux-x64-musl': 1.1.3 + '@rolldown/binding-openharmony-arm64': 1.1.3 + '@rolldown/binding-wasm32-wasi': 1.1.3 + '@rolldown/binding-win32-arm64-msvc': 1.1.3 + '@rolldown/binding-win32-x64-msvc': 1.1.3 rope-sequence@1.3.4: {} @@ -28559,10 +29359,10 @@ snapshots: optionalDependencies: '@babel/core': 7.29.0 - stylehacks@6.1.1(postcss@8.5.15): + stylehacks@6.1.1(postcss@8.5.16): dependencies: browserslist: 4.28.2 - postcss: 8.5.15 + postcss: 8.5.16 postcss-selector-parser: 6.1.2 stylis@4.4.0: {} @@ -28571,6 +29371,11 @@ snapshots: dependencies: postcss: 8.5.15 + sugarss@5.0.0(postcss@8.5.16): + dependencies: + postcss: 8.5.16 + optional: true + super-regex@1.0.0: dependencies: function-timeout: 1.0.2 @@ -28618,11 +29423,11 @@ snapshots: deep-rename-keys: 0.2.1 xml-reader: 2.4.3 - swc-loader@0.2.7(@swc/core@1.15.3(@swc/helpers@0.5.15))(webpack@5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)): + swc-loader@0.2.7(@swc/core@1.15.3(@swc/helpers@0.5.15))(webpack@5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)): dependencies: '@swc/core': 1.15.3(@swc/helpers@0.5.15) '@swc/counter': 0.1.3 - webpack: 5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.15))(esbuild@0.28.0)(html-minifier-terser@7.2.0)(postcss@8.5.15) + webpack: 5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.16))(esbuild@0.28.1)(html-minifier-terser@7.2.0)(postcss@8.5.16) swiper@12.2.0: {} @@ -28659,6 +29464,16 @@ snapshots: transitivePeerDependencies: - bare-buffer + tar-fs@3.1.3: + dependencies: + pump: 3.0.2 + tar-stream: 3.1.7 + optionalDependencies: + bare-fs: 4.1.2 + bare-path: 3.0.0 + transitivePeerDependencies: + - bare-buffer + tar-stream@2.2.0: dependencies: bl: 4.1.0 @@ -28682,46 +29497,73 @@ snapshots: type-fest: 2.19.0 unique-string: 3.0.0 - terser-webpack-plugin@5.6.0(@swc/core@1.15.3(@swc/helpers@0.5.15))(@swc/html@1.15.40)(esbuild@0.28.0)(lightningcss@1.32.0)(postcss@8.5.15)(webpack@5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)): + terser-webpack-plugin@5.6.0(@swc/core@1.15.3(@swc/helpers@0.5.15))(@swc/html@1.15.40)(esbuild@0.28.1)(lightningcss@1.32.0)(postcss@8.5.16)(webpack@5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)): dependencies: '@jridgewell/trace-mapping': 0.3.31 jest-worker: 27.5.1 schema-utils: 4.3.3 terser: 5.46.1 - webpack: 5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.15))(esbuild@0.28.0)(html-minifier-terser@7.2.0)(postcss@8.5.15) + webpack: 5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.16))(esbuild@0.28.1)(html-minifier-terser@7.2.0)(postcss@8.5.16) optionalDependencies: '@swc/core': 1.15.3(@swc/helpers@0.5.15) '@swc/html': 1.15.40 - esbuild: 0.28.0 + esbuild: 0.28.1 lightningcss: 1.32.0 - postcss: 8.5.15 + postcss: 8.5.16 - terser-webpack-plugin@5.6.0(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.15))(esbuild@0.28.0)(html-minifier-terser@7.2.0)(postcss@8.5.15)(webpack@5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)): + terser-webpack-plugin@5.6.0(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.16))(esbuild@0.28.0)(html-minifier-terser@7.2.0)(postcss@8.5.16)(webpack@5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)): dependencies: '@jridgewell/trace-mapping': 0.3.31 jest-worker: 27.5.1 schema-utils: 4.3.3 terser: 5.46.1 - webpack: 5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.15))(esbuild@0.28.0)(html-minifier-terser@7.2.0)(postcss@8.5.15) + webpack: 5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.16))(esbuild@0.28.1)(html-minifier-terser@7.2.0)(postcss@8.5.16) optionalDependencies: '@swc/core': 1.15.3(@swc/helpers@0.5.15) clean-css: 5.3.3 - cssnano: 6.1.2(postcss@8.5.15) + cssnano: 6.1.2(postcss@8.5.16) esbuild: 0.28.0 html-minifier-terser: 7.2.0 - postcss: 8.5.15 + postcss: 8.5.16 - terser-webpack-plugin@5.6.0(esbuild@0.28.0)(postcss@8.5.15)(webpack@5.107.1(esbuild@0.28.0)(postcss@8.5.15)): + terser-webpack-plugin@5.6.0(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.16))(esbuild@0.28.1)(html-minifier-terser@7.2.0)(postcss@8.5.16)(webpack@5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)): dependencies: '@jridgewell/trace-mapping': 0.3.31 jest-worker: 27.5.1 schema-utils: 4.3.3 terser: 5.46.1 - webpack: 5.107.1(esbuild@0.28.0)(postcss@8.5.15) + webpack: 5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.16))(esbuild@0.28.1)(html-minifier-terser@7.2.0)(postcss@8.5.16) optionalDependencies: - esbuild: 0.28.0 + '@swc/core': 1.15.3(@swc/helpers@0.5.15) + clean-css: 5.3.3 + cssnano: 6.1.2(postcss@8.5.16) + esbuild: 0.28.1 + html-minifier-terser: 7.2.0 + postcss: 8.5.16 + + terser-webpack-plugin@5.6.0(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.15)(webpack@5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)): + dependencies: + '@jridgewell/trace-mapping': 0.3.31 + jest-worker: 27.5.1 + schema-utils: 4.3.3 + terser: 5.46.1 + webpack: 5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.16))(esbuild@0.28.1)(html-minifier-terser@7.2.0)(postcss@8.5.16) + optionalDependencies: + '@swc/core': 1.15.3(@swc/helpers@0.5.15) + esbuild: 0.28.1 postcss: 8.5.15 + terser-webpack-plugin@5.6.0(esbuild@0.28.1)(postcss@8.5.16)(webpack@5.107.1(esbuild@0.28.1)(postcss@8.5.16)): + dependencies: + '@jridgewell/trace-mapping': 0.3.31 + jest-worker: 27.5.1 + schema-utils: 4.3.3 + terser: 5.46.1 + webpack: 5.107.1(esbuild@0.28.1)(postcss@8.5.16) + optionalDependencies: + esbuild: 0.28.1 + postcss: 8.5.16 + terser@5.46.1: dependencies: '@jridgewell/source-map': 0.3.11 @@ -28858,23 +29700,23 @@ snapshots: trough@2.2.0: {} - trpc-to-mcp@1.3.2(@trpc/server@11.17.0(typescript@6.0.3))(better-auth@1.6.15(@opentelemetry/api@1.9.0)(better-sqlite3@12.10.0)(drizzle-kit@0.31.10)(drizzle-orm@0.45.2(@libsql/client-wasm@0.14.0)(@opentelemetry/api@1.9.0)(@types/better-sqlite3@7.6.13)(@types/pg@8.20.0)(better-sqlite3@12.10.0)(gel@2.0.0)(kysely@0.29.2)(mysql2@3.22.5(@types/node@24.13.2))(pg@8.22.0)(sql.js@1.14.1))(mysql2@3.22.5(@types/node@24.13.2))(next@16.2.9(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.61.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(sass@1.101.0))(pg@8.22.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(vitest@4.1.9)(vue@3.5.35(typescript@6.0.3)))(mcp-handler@1.1.0(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3))(next@16.2.9(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.61.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(sass@1.101.0)))(zod@4.4.3): + trpc-to-mcp@1.3.2(@trpc/server@11.17.0(typescript@6.0.3))(better-auth@1.6.15(@opentelemetry/api@1.9.0)(better-sqlite3@12.10.0)(drizzle-kit@0.31.10)(drizzle-orm@0.45.2(@libsql/client-wasm@0.14.0)(@opentelemetry/api@1.9.0)(@types/better-sqlite3@7.6.13)(@types/pg@8.20.0)(better-sqlite3@12.10.0)(gel@2.0.0)(kysely@0.29.2)(mysql2@3.22.5(@types/node@24.13.2))(pg@8.22.0)(sql.js@1.14.1))(mysql2@3.22.5(@types/node@24.13.2))(next@16.2.9(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.61.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(sass@1.101.0))(pg@8.22.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(vitest@4.1.9)(vue@3.5.35(typescript@6.0.3)))(mcp-handler@1.1.0(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3))(next@16.2.9(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.61.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(sass@1.101.0)))(zod@4.4.3): dependencies: '@modelcontextprotocol/sdk': 1.29.0(zod@4.4.3) '@trpc/server': 11.17.0(typescript@6.0.3) - better-auth: 1.6.15(@opentelemetry/api@1.9.0)(better-sqlite3@12.10.0)(drizzle-kit@0.31.10)(drizzle-orm@0.45.2(@libsql/client-wasm@0.14.0)(@opentelemetry/api@1.9.0)(@types/better-sqlite3@7.6.13)(@types/pg@8.20.0)(better-sqlite3@12.10.0)(gel@2.0.0)(kysely@0.29.2)(mysql2@3.22.5(@types/node@24.13.2))(pg@8.22.0)(sql.js@1.14.1))(mysql2@3.22.5(@types/node@24.13.2))(next@16.2.9(@opentelemetry/api@1.9.0)(@playwright/test@1.61.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(sass@1.101.0))(pg@8.22.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(vitest@4.1.9)(vue@3.5.35(typescript@6.0.3)) - mcp-handler: 1.1.0(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3))(next@16.2.9(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.61.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(sass@1.101.0)) + better-auth: 1.6.15(@opentelemetry/api@1.9.0)(better-sqlite3@12.10.0)(drizzle-kit@0.31.10)(drizzle-orm@0.45.2(@libsql/client-wasm@0.14.0)(@opentelemetry/api@1.9.0)(@types/better-sqlite3@7.6.13)(@types/pg@8.20.0)(better-sqlite3@12.10.0)(gel@2.0.0)(kysely@0.29.2)(mysql2@3.22.5(@types/node@24.13.2))(pg@8.22.0)(sql.js@1.14.1))(mysql2@3.22.5(@types/node@24.13.2))(next@16.2.9(@opentelemetry/api@1.9.0)(@playwright/test@1.61.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(sass@1.101.0))(pg@8.22.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(vitest@4.1.9)(vue@3.5.35(typescript@6.0.3)) + mcp-handler: 1.1.0(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3))(next@16.2.9(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.61.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(sass@1.101.0)) zod: 4.4.3 transitivePeerDependencies: - '@cfworker/json-schema' - supports-color - trpc-to-mcp@1.3.2(@trpc/server@11.17.0(typescript@6.0.3))(better-auth@1.6.15(@opentelemetry/api@1.9.0)(better-sqlite3@12.10.0)(drizzle-kit@0.31.10)(drizzle-orm@0.45.2(@libsql/client-wasm@0.14.0)(@opentelemetry/api@1.9.0)(@types/better-sqlite3@7.6.13)(@types/pg@8.20.0)(better-sqlite3@12.10.0)(gel@2.0.0)(kysely@0.29.2)(mysql2@3.22.5(@types/node@24.13.2))(pg@8.22.0)(sql.js@1.14.1))(mysql2@3.22.5(@types/node@24.13.2))(next@16.2.9(@opentelemetry/api@1.9.0)(@playwright/test@1.61.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(sass@1.101.0))(pg@8.22.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(vitest@4.1.9)(vue@3.5.35(typescript@6.0.3)))(mcp-handler@1.1.0(@modelcontextprotocol/sdk@1.26.0(zod@4.4.3))(next@16.2.9(@opentelemetry/api@1.9.0)(@playwright/test@1.61.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(sass@1.101.0)))(zod@4.4.3): + trpc-to-mcp@1.3.2(@trpc/server@11.17.0(typescript@6.0.3))(better-auth@1.6.15(@opentelemetry/api@1.9.0)(better-sqlite3@12.10.0)(drizzle-kit@0.31.10)(drizzle-orm@0.45.2(@libsql/client-wasm@0.14.0)(@opentelemetry/api@1.9.0)(@types/better-sqlite3@7.6.13)(@types/pg@8.20.0)(better-sqlite3@12.10.0)(gel@2.0.0)(kysely@0.29.2)(mysql2@3.22.5(@types/node@24.13.2))(pg@8.22.0)(sql.js@1.14.1))(mysql2@3.22.5(@types/node@24.13.2))(next@16.2.9(@opentelemetry/api@1.9.0)(@playwright/test@1.61.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(sass@1.101.0))(pg@8.22.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(vitest@4.1.9)(vue@3.5.35(typescript@6.0.3)))(mcp-handler@1.1.0(@modelcontextprotocol/sdk@1.26.0(zod@4.4.3))(next@16.2.9(@opentelemetry/api@1.9.0)(@playwright/test@1.61.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(sass@1.101.0)))(zod@4.4.3): dependencies: '@modelcontextprotocol/sdk': 1.29.0(zod@4.4.3) '@trpc/server': 11.17.0(typescript@6.0.3) - better-auth: 1.6.15(@opentelemetry/api@1.9.0)(better-sqlite3@12.10.0)(drizzle-kit@0.31.10)(drizzle-orm@0.45.2(@libsql/client-wasm@0.14.0)(@opentelemetry/api@1.9.0)(@types/better-sqlite3@7.6.13)(@types/pg@8.20.0)(better-sqlite3@12.10.0)(gel@2.0.0)(kysely@0.29.2)(mysql2@3.22.5(@types/node@24.13.2))(pg@8.22.0)(sql.js@1.14.1))(mysql2@3.22.5(@types/node@24.13.2))(next@16.2.9(@opentelemetry/api@1.9.0)(@playwright/test@1.61.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(sass@1.101.0))(pg@8.22.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(vitest@4.1.9)(vue@3.5.35(typescript@6.0.3)) - mcp-handler: 1.1.0(@modelcontextprotocol/sdk@1.26.0(zod@4.4.3))(next@16.2.9(@opentelemetry/api@1.9.0)(@playwright/test@1.61.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(sass@1.101.0)) + better-auth: 1.6.15(@opentelemetry/api@1.9.0)(better-sqlite3@12.10.0)(drizzle-kit@0.31.10)(drizzle-orm@0.45.2(@libsql/client-wasm@0.14.0)(@opentelemetry/api@1.9.0)(@types/better-sqlite3@7.6.13)(@types/pg@8.20.0)(better-sqlite3@12.10.0)(gel@2.0.0)(kysely@0.29.2)(mysql2@3.22.5(@types/node@24.13.2))(pg@8.22.0)(sql.js@1.14.1))(mysql2@3.22.5(@types/node@24.13.2))(next@16.2.9(@opentelemetry/api@1.9.0)(@playwright/test@1.61.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(sass@1.101.0))(pg@8.22.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(vitest@4.1.9)(vue@3.5.35(typescript@6.0.3)) + mcp-handler: 1.1.0(@modelcontextprotocol/sdk@1.26.0(zod@4.4.3))(next@16.2.9(@opentelemetry/api@1.9.0)(@playwright/test@1.61.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(sass@1.101.0)) zod: 4.4.3 transitivePeerDependencies: - '@cfworker/json-schema' @@ -28941,14 +29783,14 @@ snapshots: tunnel@0.0.6: {} - turbo@2.9.18: + turbo@2.10.0: optionalDependencies: - '@turbo/darwin-64': 2.9.18 - '@turbo/darwin-arm64': 2.9.18 - '@turbo/linux-64': 2.9.18 - '@turbo/linux-arm64': 2.9.18 - '@turbo/windows-64': 2.9.18 - '@turbo/windows-arm64': 2.9.18 + '@turbo/darwin-64': 2.10.0 + '@turbo/darwin-arm64': 2.10.0 + '@turbo/linux-64': 2.10.0 + '@turbo/linux-arm64': 2.10.0 + '@turbo/windows-64': 2.10.0 + '@turbo/windows-arm64': 2.10.0 tweetnacl@0.14.5: {} @@ -28962,6 +29804,10 @@ snapshots: dependencies: tagged-tag: 1.0.0 + type-fest@5.7.0: + dependencies: + tagged-tag: 1.0.0 + type-is@1.6.18: dependencies: media-typer: 0.3.0 @@ -29128,14 +29974,14 @@ snapshots: url-join@5.0.0: {} - url-loader@4.1.1(file-loader@6.2.0(webpack@5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)))(webpack@5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)): + url-loader@4.1.1(file-loader@6.2.0(webpack@5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)))(webpack@5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)): dependencies: loader-utils: 2.0.4 mime-types: 2.1.35 schema-utils: 3.3.0 - webpack: 5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.15))(esbuild@0.28.0)(html-minifier-terser@7.2.0)(postcss@8.5.15) + webpack: 5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.16))(esbuild@0.28.1)(html-minifier-terser@7.2.0)(postcss@8.5.16) optionalDependencies: - file-loader: 6.2.0(webpack@5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)) + file-loader: 6.2.0(webpack@5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)) use-callback-ref@1.3.3(@types/react@19.2.17)(react@19.2.7): dependencies: @@ -29258,38 +30104,38 @@ snapshots: dependencies: global: 4.4.0 - vite-tsconfig-paths@6.1.1(typescript@6.0.3)(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.101.0)(sugarss@5.0.0(postcss@8.5.15))(terser@5.46.1)(tsx@4.21.0)(yaml@2.9.0)): + vite-tsconfig-paths@6.1.1(typescript@6.0.3)(vite@8.1.0(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(sugarss@5.0.0(postcss@8.5.16))(terser@5.46.1)(tsx@4.21.0)(yaml@2.9.0)): dependencies: debug: 4.4.3 globrex: 0.1.2 tsconfck: 3.1.3(typescript@6.0.3) - vite: 8.0.16(@types/node@24.13.2)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.101.0)(sugarss@5.0.0(postcss@8.5.15))(terser@5.46.1)(tsx@4.21.0)(yaml@2.9.0) + vite: 8.1.0(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(sugarss@5.0.0(postcss@8.5.16))(terser@5.46.1)(tsx@4.21.0)(yaml@2.9.0) transitivePeerDependencies: - supports-color - typescript - vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.101.0)(sugarss@5.0.0(postcss@8.5.15))(terser@5.46.1)(tsx@4.21.0)(yaml@2.9.0): + vite@8.1.0(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(sugarss@5.0.0(postcss@8.5.16))(terser@5.46.1)(tsx@4.21.0)(yaml@2.9.0): dependencies: lightningcss: 1.32.0 picomatch: 4.0.4 postcss: 8.5.15 - rolldown: 1.0.3 + rolldown: 1.1.3 tinyglobby: 0.2.17 optionalDependencies: '@types/node': 24.13.2 - esbuild: 0.28.0 + esbuild: 0.28.1 fsevents: 2.3.3 jiti: 2.7.0 sass: 1.101.0 - sugarss: 5.0.0(postcss@8.5.15) + sugarss: 5.0.0(postcss@8.5.16) terser: 5.46.1 tsx: 4.21.0 yaml: 2.9.0 - vitest@4.1.9(@opentelemetry/api@1.9.0)(@types/node@24.13.2)(@vitest/coverage-v8@4.1.9)(@vitest/ui@4.1.9)(jsdom@29.1.1(@noble/hashes@2.0.1))(msw@2.14.6(@types/node@24.13.2)(typescript@6.0.3))(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.101.0)(sugarss@5.0.0(postcss@8.5.15))(terser@5.46.1)(tsx@4.21.0)(yaml@2.9.0)): + vitest@4.1.9(@opentelemetry/api@1.9.0)(@types/node@24.13.2)(@vitest/coverage-v8@4.1.9)(@vitest/ui@4.1.9)(jsdom@29.1.1(@noble/hashes@2.0.1))(msw@2.14.6(@types/node@24.13.2)(typescript@6.0.3))(vite@8.1.0(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(sugarss@5.0.0(postcss@8.5.16))(terser@5.46.1)(tsx@4.21.0)(yaml@2.9.0)): dependencies: '@vitest/expect': 4.1.9 - '@vitest/mocker': 4.1.9(msw@2.14.6(@types/node@24.13.2)(typescript@6.0.3))(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.101.0)(sugarss@5.0.0(postcss@8.5.15))(terser@5.46.1)(tsx@4.21.0)(yaml@2.9.0)) + '@vitest/mocker': 4.1.9(msw@2.14.6(@types/node@24.13.2)(typescript@6.0.3))(vite@8.1.0(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(sugarss@5.0.0(postcss@8.5.16))(terser@5.46.1)(tsx@4.21.0)(yaml@2.9.0)) '@vitest/pretty-format': 4.1.9 '@vitest/runner': 4.1.9 '@vitest/snapshot': 4.1.9 @@ -29306,7 +30152,7 @@ snapshots: tinyexec: 1.0.2 tinyglobby: 0.2.17 tinyrainbow: 3.1.0 - vite: 8.0.16(@types/node@24.13.2)(esbuild@0.28.0)(jiti@2.7.0)(sass@1.101.0)(sugarss@5.0.0(postcss@8.5.15))(terser@5.46.1)(tsx@4.21.0)(yaml@2.9.0) + vite: 8.1.0(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(sugarss@5.0.0(postcss@8.5.16))(terser@5.46.1)(tsx@4.21.0)(yaml@2.9.0) why-is-node-running: 2.3.0 optionalDependencies: '@opentelemetry/api': 1.9.0 @@ -29376,7 +30222,7 @@ snapshots: - bufferutil - utf-8-validate - webpack-dev-middleware@7.4.5(tslib@2.8.1)(webpack@5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)): + webpack-dev-middleware@7.4.5(tslib@2.8.1)(webpack@5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)): dependencies: colorette: 2.0.20 memfs: 4.57.6(tslib@2.8.1) @@ -29385,11 +30231,11 @@ snapshots: range-parser: 1.2.1 schema-utils: 4.3.3 optionalDependencies: - webpack: 5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.15))(esbuild@0.28.0)(html-minifier-terser@7.2.0)(postcss@8.5.15) + webpack: 5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.16))(esbuild@0.28.1)(html-minifier-terser@7.2.0)(postcss@8.5.16) transitivePeerDependencies: - tslib - webpack-dev-server@5.2.4(tslib@2.8.1)(webpack@5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)): + webpack-dev-server@5.2.4(tslib@2.8.1)(webpack@5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)): dependencies: '@types/bonjour': 3.5.13 '@types/connect-history-api-fallback': 1.5.4 @@ -29417,10 +30263,10 @@ snapshots: serve-index: 1.9.2 sockjs: 0.3.24 spdy: 4.0.2 - webpack-dev-middleware: 7.4.5(tslib@2.8.1)(webpack@5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)) + webpack-dev-middleware: 7.4.5(tslib@2.8.1)(webpack@5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)) ws: 8.21.0 optionalDependencies: - webpack: 5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.15))(esbuild@0.28.0)(html-minifier-terser@7.2.0)(postcss@8.5.15) + webpack: 5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.16))(esbuild@0.28.1)(html-minifier-terser@7.2.0)(postcss@8.5.16) transitivePeerDependencies: - bufferutil - debug @@ -29442,7 +30288,7 @@ snapshots: webpack-sources@3.5.0: {} - webpack@5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(@swc/html@1.15.40)(esbuild@0.28.0)(lightningcss@1.32.0)(postcss@8.5.15): + webpack@5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(@swc/html@1.15.40)(esbuild@0.28.1)(lightningcss@1.32.0)(postcss@8.5.16): dependencies: '@types/estree': 1.0.8 '@types/json-schema': 7.0.15 @@ -29464,7 +30310,7 @@ snapshots: neo-async: 2.6.2 schema-utils: 4.3.3 tapable: 2.3.3 - terser-webpack-plugin: 5.6.0(@swc/core@1.15.3(@swc/helpers@0.5.15))(@swc/html@1.15.40)(esbuild@0.28.0)(lightningcss@1.32.0)(postcss@8.5.15)(webpack@5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)) + terser-webpack-plugin: 5.6.0(@swc/core@1.15.3(@swc/helpers@0.5.15))(@swc/html@1.15.40)(esbuild@0.28.1)(lightningcss@1.32.0)(postcss@8.5.16)(webpack@5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)) watchpack: 2.5.1 webpack-sources: 3.5.0 transitivePeerDependencies: @@ -29481,7 +30327,7 @@ snapshots: - postcss - uglify-js - webpack@5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.15))(esbuild@0.28.0)(html-minifier-terser@7.2.0)(postcss@8.5.15): + webpack@5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.16))(esbuild@0.28.1)(html-minifier-terser@7.2.0)(postcss@8.5.16): dependencies: '@types/estree': 1.0.8 '@types/json-schema': 7.0.15 @@ -29503,7 +30349,7 @@ snapshots: neo-async: 2.6.2 schema-utils: 4.3.3 tapable: 2.3.3 - terser-webpack-plugin: 5.6.0(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.15))(esbuild@0.28.0)(html-minifier-terser@7.2.0)(postcss@8.5.15)(webpack@5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)) + terser-webpack-plugin: 5.6.0(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.16))(esbuild@0.28.1)(html-minifier-terser@7.2.0)(postcss@8.5.16)(webpack@5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)) watchpack: 2.5.1 webpack-sources: 3.5.0 transitivePeerDependencies: @@ -29520,7 +30366,7 @@ snapshots: - postcss - uglify-js - webpack@5.107.1(esbuild@0.28.0)(postcss@8.5.15): + webpack@5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.15): dependencies: '@types/estree': 1.0.8 '@types/json-schema': 7.0.15 @@ -29542,7 +30388,7 @@ snapshots: neo-async: 2.6.2 schema-utils: 4.3.3 tapable: 2.3.3 - terser-webpack-plugin: 5.6.0(esbuild@0.28.0)(postcss@8.5.15)(webpack@5.107.1(esbuild@0.28.0)(postcss@8.5.15)) + terser-webpack-plugin: 5.6.0(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.15)(webpack@5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)) watchpack: 2.5.1 webpack-sources: 3.5.0 transitivePeerDependencies: @@ -29559,7 +30405,46 @@ snapshots: - postcss - uglify-js - webpackbar@7.0.0(@rspack/core@1.7.11(@swc/helpers@0.5.15))(webpack@5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.0)(postcss@8.5.15)): + webpack@5.107.1(esbuild@0.28.1)(postcss@8.5.16): + dependencies: + '@types/estree': 1.0.8 + '@types/json-schema': 7.0.15 + '@webassemblyjs/ast': 1.14.1 + '@webassemblyjs/wasm-edit': 1.14.1 + '@webassemblyjs/wasm-parser': 1.14.1 + acorn: 8.16.0 + acorn-import-phases: 1.0.4(acorn@8.16.0) + browserslist: 4.28.2 + chrome-trace-event: 1.0.4 + enhanced-resolve: 5.22.0 + es-module-lexer: 2.1.0 + eslint-scope: 5.1.1 + events: 3.3.0 + glob-to-regexp: 0.4.1 + graceful-fs: 4.2.11 + loader-runner: 4.3.2 + mime-db: 1.54.0 + neo-async: 2.6.2 + schema-utils: 4.3.3 + tapable: 2.3.3 + terser-webpack-plugin: 5.6.0(esbuild@0.28.1)(postcss@8.5.16)(webpack@5.107.1(esbuild@0.28.1)(postcss@8.5.16)) + watchpack: 2.5.1 + webpack-sources: 3.5.0 + transitivePeerDependencies: + - '@minify-html/node' + - '@swc/core' + - '@swc/css' + - '@swc/html' + - clean-css + - cssnano + - csso + - esbuild + - html-minifier-terser + - lightningcss + - postcss + - uglify-js + + webpackbar@7.0.0(@rspack/core@1.7.11(@swc/helpers@0.5.15))(webpack@5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.16)): dependencies: ansis: 3.17.0 consola: 3.4.2 @@ -29567,7 +30452,7 @@ snapshots: std-env: 3.10.0 optionalDependencies: '@rspack/core': 1.7.11(@swc/helpers@0.5.15) - webpack: 5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.15))(esbuild@0.28.0)(html-minifier-terser@7.2.0)(postcss@8.5.15) + webpack: 5.107.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.16))(esbuild@0.28.1)(html-minifier-terser@7.2.0)(postcss@8.5.16) websocket-driver@0.7.5: dependencies: @@ -29681,8 +30566,6 @@ snapshots: dependencies: is-wsl: 3.1.1 - xcase@2.0.1: {} - xdg-basedir@5.1.0: {} xml-js@1.6.11: diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 24ff4891f..53147b2e0 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -14,29 +14,29 @@ catalog: "@dnd-kit/utilities": ^3.2.2 "@drizzle-team/brocli": ^0.12.0 "@extractus/feed-extractor": 7.2.1 - '@gfazioli/mantine-onboarding-tour': ^4.0.5 + '@gfazioli/mantine-onboarding-tour': ^4.0.6 "@gitbeaker/rest": ^43.8.0 "@homarr/gridstack": ^1.12.0 "@homarr/node-unifi": ^2.6.0 - "@immich/sdk": ^2.7.5 + "@immich/sdk": ^3.0.0 "@jellyfin/sdk": ^0.13.0 "@kubernetes/client-node": ^1.4.0 "@modelcontextprotocol/sdk": 1.29.0 - "@mantine/charts": ^9.3.2 - "@mantine/colors-generator": ^9.3.2 - "@mantine/core": ^9.3.2 - "@mantine/dates": ^9.3.2 - "@mantine/dropzone": ^9.3.2 - "@mantine/form": ^9.3.2 - "@mantine/hooks": ^9.3.2 - "@mantine/modals": ^9.3.2 - "@mantine/notifications": ^9.3.2 - "@mantine/spotlight": ^9.3.2 - "@mantine/tiptap": ^9.3.2 + "@mantine/charts": ^9.4.0 + "@mantine/colors-generator": ^9.4.0 + "@mantine/core": ^9.4.0 + "@mantine/dates": ^9.4.0 + "@mantine/dropzone": ^9.4.0 + "@mantine/form": ^9.4.0 + "@mantine/hooks": ^9.4.0 + "@mantine/modals": ^9.4.0 + "@mantine/notifications": ^9.4.0 + "@mantine/spotlight": ^9.4.0 + "@mantine/tiptap": ^9.4.0 "@next/eslint-plugin-next": 16.2.9 "@octokit/auth-app": ^8.2.0 "@paralleldrive/cuid2": ^3.3.0 - "@playwright/test": ^1.61.0 + "@playwright/test": ^1.61.1 "@semantic-release/changelog": ^6.0.3 "@semantic-release/commit-analyzer": ^13.0.1 "@semantic-release/git": ^10.0.1 @@ -45,10 +45,11 @@ catalog: "@semantic-release/release-notes-generator": ^14.1.1 "@t3-oss/env-nextjs": ^0.13.11 "@tabler/icons-react": ^3.44.0 - "@tanstack/query-persist-client-core": ^5.101.0 + "@tanstack/query-async-storage-persister": ^5.101.0 "@tanstack/react-query": ^5.101.0 "@tanstack/react-query-devtools": ^5.101.0 "@tanstack/react-query-next-experimental": ^5.101.0 + "@tanstack/react-query-persist-client": ^5.101.0 "@testcontainers/mysql": ^12.0.3 "@testcontainers/postgresql": ^12.0.3 "@testcontainers/redis": ^12.0.3 @@ -73,7 +74,7 @@ catalog: "@trpc/react-query": ^11.17.0 "@trpc/server": ^11.17.0 "@trpc/tanstack-react-query": ^11.17.0 - "@turbo/gen": ^2.9.18 + "@turbo/gen": ^2.10.0 "@types/adm-zip": 0.5.8 "@types/bcrypt": 6.0.0 "@types/better-sqlite3": 7.6.13 @@ -88,13 +89,13 @@ catalog: "@types/prismjs": ^1.26.6 "@types/react": 19.2.17 "@types/react-dom": 19.2.3 - "@scalar/api-reference-react": ^0.9.47 + "@scalar/api-reference-react": ^0.9.49 "@types/video.js": ^7.3.58 "@types/ws": ^8.18.1 "@types/xml2js": ^0.4.14 - posthog-js: ^1.391.2 + posthog-js: ^1.395.0 posthog-node: ^4.18.0 - "@vitejs/plugin-react": ^6.0.2 + "@vitejs/plugin-react": ^6.0.3 "@vitest/coverage-v8": ^4.1.9 "@vitest/ui": ^4.1.9 "@xterm/addon-canvas": ^0.7.0 @@ -118,7 +119,7 @@ catalog: dayjs: ^1.11.21 deepmerge: 4.3.1 dns-caching: ^0.2.9 - dockerode: ^5.0.0 + dockerode: ^5.0.1 dotenv: ^17.4.2 dotenv-cli: ^11.0.0 drizzle-kit: ^0.31.10 @@ -150,8 +151,8 @@ catalog: next-intl: 4.13.0 node-loader: ^2.1.0 octokit: ^5.0.5 - oxfmt: ^0.55.0 - oxlint: ^1.70.0 + oxfmt: ^0.56.0 + oxlint: ^1.71.0 pg: ^8.22.0 postcss-preset-mantine: ^1.18.0 prismjs: ^1.30.0 @@ -163,7 +164,7 @@ catalog: react-markdown: ^10.1.0 react-quill-new: ^3.8.3 react-simple-code-editor: ^0.14.1 - recharts: ^3.8.1 + recharts: ^3.9.0 sass: ^1.101.0 semantic-release: ^25.0.5 semver-parser: ^4.2.1 @@ -175,9 +176,9 @@ catalog: trpc-to-openapi: ^3.3.0 tsdav: ^2.2.2 tsx: 4.20.4 - turbo: ^2.9.18 + turbo: ^2.10.0 typescript: ^6.0.3 - vite: ^8.0.16 + vite: ^8.1.0 undici: 7.27.2 use-deep-compare-effect: ^1.8.1 video.js: ^8.23.9 @@ -199,6 +200,7 @@ minimumReleaseAge: 4320 minimumReleaseAgeExclude: - next - "@next/*" + - "@immich/sdk@3.0.0" # Renovate security update: turbo@2.9.14 - turbo@2.9.14 # Renovate security update: form-data@4.0.6 @@ -206,23 +208,23 @@ minimumReleaseAgeExclude: overrides: "@babel/helpers@<7.26.10": ">=7.29.7" "@babel/runtime@<7.26.10": ">=7.29.7" - "axios@>=1.0.0 <1.8.2": ">=1.18.0" + "axios@>=1.0.0 <1.8.2": ">=1.18.1" "brace-expansion@>=2.0.0 <=2.0.1": ">=5.0.6" "brace-expansion@>=1.0.0 <=1.1.11": ">=5.0.6" "esbuild@<=0.24.2": ">=0.28.0" "form-data@>=4.0.0 <4.0.4": ">=4.0.6" - "hono@<4.6.5": ">=4.12.26" + "hono@<4.6.5": ">=4.12.27" "linkifyjs@<4.3.2": ">=4.3.3" - "nanoid@>=4.0.0 <5.0.9": ">=5.1.15" + "nanoid@>=4.0.0 <5.0.9": ">=5.1.16" "prismjs@<1.30.0": ">=1.30.0" "proxmox-api>undici": "7.27.1" "react-is": "^19.2.7" "rollup@>=4.0.0 <4.22.4": ">=4.62.2" "sha.js@<=2.4.11": ">=2.4.12" - "tar-fs@>=3.0.0 <3.0.9": ">=3.1.2" - "tar-fs@>=2.0.0 <2.1.3": ">=3.1.2" + "tar-fs@>=3.0.0 <3.0.9": ">=3.1.3" + "tar-fs@>=2.0.0 <2.1.3": ">=3.1.3" "tmp@<=0.2.3": ">=0.2.7" - "vite@>=5.0.0 <=5.4.18": ">=8.0.16" + "vite@>=5.0.0 <=5.4.18": ">=8.1.0" patchedDependencies: "@types/node-unifi": "patches/@types__node-unifi.patch" "trpc-to-openapi": "patches/trpc-to-openapi.patch" diff --git a/static-data/contributors.json b/static-data/contributors.json index 07d22132c..3613cc905 100644 --- a/static-data/contributors.json +++ b/static-data/contributors.json @@ -1 +1 @@ -[{"login":"ajnart","avatar_url":"https://avatars.githubusercontent.com/u/49837342?v=4"},{"login":"manuel-rw","avatar_url":"https://avatars.githubusercontent.com/u/30572287?v=4"},{"login":"Meierschlumpf","avatar_url":"https://avatars.githubusercontent.com/u/63781622?v=4"},{"login":"SeDemal","avatar_url":"https://avatars.githubusercontent.com/u/26098587?v=4"},{"login":"lammersbjorn","avatar_url":"https://avatars.githubusercontent.com/u/71191962?v=4"},{"login":"LarveyOfficial","avatar_url":"https://avatars.githubusercontent.com/u/39219859?v=4"},{"login":"c00ldude1oo","avatar_url":"https://avatars.githubusercontent.com/u/15677803?v=4"},{"login":"InterN0te","avatar_url":"https://avatars.githubusercontent.com/u/10882916?v=4"},{"login":"Aimsucks","avatar_url":"https://avatars.githubusercontent.com/u/1828486?v=4"},{"login":"JannesV","avatar_url":"https://avatars.githubusercontent.com/u/5984244?v=4"},{"login":"MauriceNino","avatar_url":"https://avatars.githubusercontent.com/u/18658092?v=4"},{"login":"hillaliy","avatar_url":"https://avatars.githubusercontent.com/u/950010?v=4"},{"login":"Skeeww","avatar_url":"https://avatars.githubusercontent.com/u/9639992?v=4"},{"login":"RichyHBM","avatar_url":"https://avatars.githubusercontent.com/u/5472275?v=4"},{"login":"Bonfire","avatar_url":"https://avatars.githubusercontent.com/u/5704760?v=4"},{"login":"wieli99","avatar_url":"https://avatars.githubusercontent.com/u/46794825?v=4"},{"login":"ishaanparlikar","avatar_url":"https://avatars.githubusercontent.com/u/43013132?v=4"},{"login":"Zareix","avatar_url":"https://avatars.githubusercontent.com/u/29818713?v=4"},{"login":"tuggan","avatar_url":"https://avatars.githubusercontent.com/u/4325608?v=4"},{"login":"Spillebulle","avatar_url":"https://avatars.githubusercontent.com/u/46653946?v=4"},{"login":"marektancak0106","avatar_url":"https://avatars.githubusercontent.com/u/25422667?v=4"},{"login":"spkesDE","avatar_url":"https://avatars.githubusercontent.com/u/1682314?v=4"},{"login":"j3lte","avatar_url":"https://avatars.githubusercontent.com/u/2557568?v=4"},{"login":"dslatt","avatar_url":"https://avatars.githubusercontent.com/u/5211001?v=4"},{"login":"Malong11-007","avatar_url":"https://avatars.githubusercontent.com/u/40298510?v=4"},{"login":"StefanB7","avatar_url":"https://avatars.githubusercontent.com/u/22056509?v=4"},{"login":"souravghosh01","avatar_url":"https://avatars.githubusercontent.com/u/37548139?v=4"},{"login":"SR3u","avatar_url":"https://avatars.githubusercontent.com/u/2380522?v=4"},{"login":"Rikpat","avatar_url":"https://avatars.githubusercontent.com/u/33869814?v=4"},{"login":"NormanJS","avatar_url":"https://avatars.githubusercontent.com/u/83409549?v=4"},{"login":"No-Maines-Land","avatar_url":"https://avatars.githubusercontent.com/u/20687725?v=4"},{"login":"stark1tty","avatar_url":"https://avatars.githubusercontent.com/u/69326376?v=4"},{"login":"Momcilo42","avatar_url":"https://avatars.githubusercontent.com/u/10118137?v=4"},{"login":"marius-arch","avatar_url":"https://avatars.githubusercontent.com/u/72494359?v=4"},{"login":"Lumilias","avatar_url":"https://avatars.githubusercontent.com/u/10852161?v=4"},{"login":"Havock94","avatar_url":"https://avatars.githubusercontent.com/u/7635248?v=4"},{"login":"Lollyde","avatar_url":"https://avatars.githubusercontent.com/u/11347524?v=4"},{"login":"krishnamuppaneni","avatar_url":"https://avatars.githubusercontent.com/u/4703366?v=4"},{"login":"devtobi","avatar_url":"https://avatars.githubusercontent.com/u/28538704?v=4"},{"login":"TyxTang","avatar_url":"https://avatars.githubusercontent.com/u/90897955?v=4"},{"login":"beanbag420","avatar_url":"https://avatars.githubusercontent.com/u/19630511?v=4"},{"login":"WillyJL","avatar_url":"https://avatars.githubusercontent.com/u/49810075?v=4"},{"login":"anonysoul","avatar_url":"https://avatars.githubusercontent.com/u/105715150?v=4"},{"login":"cospeedster","avatar_url":"https://avatars.githubusercontent.com/u/52749645?v=4"},{"login":"diederbert","avatar_url":"https://avatars.githubusercontent.com/u/162878798?v=4"},{"login":"digdilem","avatar_url":"https://avatars.githubusercontent.com/u/4052275?v=4"},{"login":"dinghino","avatar_url":"https://avatars.githubusercontent.com/u/14729459?v=4"},{"login":"gfomichev","avatar_url":"https://avatars.githubusercontent.com/u/1596146?v=4"},{"login":"gnattu","avatar_url":"https://avatars.githubusercontent.com/u/12995396?v=4"},{"login":"tuxpizza","avatar_url":"https://avatars.githubusercontent.com/u/84710786?v=4"},{"login":"xmarkurion","avatar_url":"https://avatars.githubusercontent.com/u/46654687?v=4"},{"login":"huanga","avatar_url":"https://avatars.githubusercontent.com/u/732970?v=4"},{"login":"taos15","avatar_url":"https://avatars.githubusercontent.com/u/122130728?v=4"},{"login":"arghyadipchak","avatar_url":"https://avatars.githubusercontent.com/u/32430374?v=4"},{"login":"AuthorShin","avatar_url":"https://avatars.githubusercontent.com/u/4959043?v=4"},{"login":"benniblot","avatar_url":"https://avatars.githubusercontent.com/u/47495914?v=4"},{"login":"bebsworthy","avatar_url":"https://avatars.githubusercontent.com/u/5266759?v=4"},{"login":"catrielmuller","avatar_url":"https://avatars.githubusercontent.com/u/2272323?v=4"},{"login":"Vysp3r","avatar_url":"https://avatars.githubusercontent.com/u/62019735?v=4"},{"login":"DR-Blogs","avatar_url":"https://avatars.githubusercontent.com/u/96393186?v=4"},{"login":"singular0","avatar_url":"https://avatars.githubusercontent.com/u/1925883?v=4"},{"login":"diogovalentte","avatar_url":"https://avatars.githubusercontent.com/u/49578155?v=4"},{"login":"codezninja","avatar_url":"https://avatars.githubusercontent.com/u/4400773?v=4"},{"login":"fabricionaweb","avatar_url":"https://avatars.githubusercontent.com/u/15933?v=4"},{"login":"nyok1912","avatar_url":"https://avatars.githubusercontent.com/u/32163949?v=4"},{"login":"bo3bdo","avatar_url":"https://avatars.githubusercontent.com/u/5271380?v=4"},{"login":"hskrtich","avatar_url":"https://avatars.githubusercontent.com/u/1214484?v=4"},{"login":"ishults","avatar_url":"https://avatars.githubusercontent.com/u/7275442?v=4"},{"login":"jelliuk","avatar_url":"https://avatars.githubusercontent.com/u/4790959?v=4"},{"login":"Moohan","avatar_url":"https://avatars.githubusercontent.com/u/5982260?v=4"},{"login":"tesar-tech","avatar_url":"https://avatars.githubusercontent.com/u/33880579?v=4"},{"login":"jbruell","avatar_url":"https://avatars.githubusercontent.com/u/31685172?v=4"},{"login":"JasonLeeB06","avatar_url":"https://avatars.githubusercontent.com/u/82266978?v=4"},{"login":"jeffersonraimon","avatar_url":"https://avatars.githubusercontent.com/u/80064475?v=4"},{"login":"jo-chemla","avatar_url":"https://avatars.githubusercontent.com/u/16822841?v=4"},{"login":"joser93","avatar_url":"https://avatars.githubusercontent.com/u/6912089?v=4"},{"login":"justijndepover","avatar_url":"https://avatars.githubusercontent.com/u/9008623?v=4"},{"login":"Aandree5","avatar_url":"https://avatars.githubusercontent.com/u/32734153?v=4"},{"login":"afonsojramos","avatar_url":"https://avatars.githubusercontent.com/u/19473034?v=4"},{"login":"mvanhorn","avatar_url":"https://avatars.githubusercontent.com/u/455140?v=4"},{"login":"jackwellerreal","avatar_url":"https://avatars.githubusercontent.com/u/97435840?v=4"},{"login":"Copilot","avatar_url":"https://avatars.githubusercontent.com/in/1143301?v=4"},{"login":"SergioChan","avatar_url":"https://avatars.githubusercontent.com/u/10103766?v=4"},{"login":"toineenzo","avatar_url":"https://avatars.githubusercontent.com/u/4921397?v=4"},{"login":"oben01","avatar_url":"https://avatars.githubusercontent.com/u/22369027?v=4"},{"login":"rezstje","avatar_url":"https://avatars.githubusercontent.com/u/45420315?v=4"},{"login":"castielwaverly","avatar_url":"https://avatars.githubusercontent.com/u/137005689?v=4"},{"login":"xoxyuxu","avatar_url":"https://avatars.githubusercontent.com/u/3543416?v=4"},{"login":"StrandedTurtle","avatar_url":"https://avatars.githubusercontent.com/u/132835255?v=4"},{"login":"MaienM","avatar_url":"https://avatars.githubusercontent.com/u/583129?v=4"},{"login":"kylegrieder","avatar_url":"https://avatars.githubusercontent.com/u/25424229?v=4"},{"login":"HeapReaper","avatar_url":"https://avatars.githubusercontent.com/u/31496522?v=4"},{"login":"lopsch","avatar_url":"https://avatars.githubusercontent.com/u/4073114?v=4"},{"login":"Gilmoursa","avatar_url":"https://avatars.githubusercontent.com/u/10321421?v=4"},{"login":"Lok3sh-kumar","avatar_url":"https://avatars.githubusercontent.com/u/110459168?v=4"},{"login":"Bricklou","avatar_url":"https://avatars.githubusercontent.com/u/15181236?v=4"},{"login":"mairas","avatar_url":"https://avatars.githubusercontent.com/u/407010?v=4"},{"login":"itzTheMeow","avatar_url":"https://avatars.githubusercontent.com/u/50887230?v=4"},{"login":"Nerdy-Technician","avatar_url":"https://avatars.githubusercontent.com/u/45691205?v=4"},{"login":"NicolasNewman","avatar_url":"https://avatars.githubusercontent.com/u/16135852?v=4"},{"login":"NikeLaosClericus","avatar_url":"https://avatars.githubusercontent.com/u/39810627?v=4"},{"login":"noahcroghan","avatar_url":"https://avatars.githubusercontent.com/u/27422774?v=4"},{"login":"ohmzi","avatar_url":"https://avatars.githubusercontent.com/u/6551272?v=4"},{"login":"Rhiz3K","avatar_url":"https://avatars.githubusercontent.com/u/33246262?v=4"},{"login":"saschabuehrle","avatar_url":"https://avatars.githubusercontent.com/u/47737812?v=4"},{"login":"CrazyWolf13","avatar_url":"https://avatars.githubusercontent.com/u/96661824?v=4"},{"login":"VIVAAN-DHAWAN","avatar_url":"https://avatars.githubusercontent.com/u/150339722?v=4"},{"login":"generalmat82","avatar_url":"https://avatars.githubusercontent.com/u/110256073?v=4"},{"login":"lthomsen","avatar_url":"https://avatars.githubusercontent.com/u/28560325?v=4"},{"login":"pitschi","avatar_url":"https://avatars.githubusercontent.com/u/6841565?v=4"},{"login":"sharpsalat","avatar_url":"https://avatars.githubusercontent.com/u/15666932?v=4"},{"login":"tech62","avatar_url":"https://avatars.githubusercontent.com/u/16237642?v=4"},{"login":"AartSchinkel","avatar_url":"https://avatars.githubusercontent.com/u/189675191?v=4"},{"login":"m0s4ik","avatar_url":"https://avatars.githubusercontent.com/u/26578395?v=4"},{"login":"ashtonbatty","avatar_url":"https://avatars.githubusercontent.com/u/230635?v=4"},{"login":"Oupsman","avatar_url":"https://avatars.githubusercontent.com/u/11260343?v=4"},{"login":"Bergasha","avatar_url":"https://avatars.githubusercontent.com/u/25044200?v=4"},{"login":"cethien","avatar_url":"https://avatars.githubusercontent.com/u/60463918?v=4"},{"login":"DZ945brH","avatar_url":"https://avatars.githubusercontent.com/u/42635243?v=4"},{"login":"dgshue","avatar_url":"https://avatars.githubusercontent.com/u/12952156?v=4"},{"login":"d-lindahl","avatar_url":"https://avatars.githubusercontent.com/u/690410?v=4"},{"login":"KartulUdus","avatar_url":"https://avatars.githubusercontent.com/u/23346150?v=4"},{"login":"ieugen","avatar_url":"https://avatars.githubusercontent.com/u/1050920?v=4"},{"login":"faugusto-oliveira","avatar_url":"https://avatars.githubusercontent.com/u/37384074?v=4"},{"login":"frankvHoof93","avatar_url":"https://avatars.githubusercontent.com/u/7346516?v=4"},{"login":"darbyjack","avatar_url":"https://avatars.githubusercontent.com/u/21014720?v=4"},{"login":"febrile42","avatar_url":"https://avatars.githubusercontent.com/u/14597122?v=4"},{"login":"Kaotic3","avatar_url":"https://avatars.githubusercontent.com/u/80522225?v=4"},{"login":"KelianB","avatar_url":"https://avatars.githubusercontent.com/u/11216155?v=4"},{"login":"hotrungnhan","avatar_url":"https://avatars.githubusercontent.com/u/40029631?v=4"},{"login":"leandroscardua","avatar_url":"https://avatars.githubusercontent.com/u/24698541?v=4"}] \ No newline at end of file +[{"login":"ajnart","avatar_url":"https://avatars.githubusercontent.com/u/49837342?v=4"},{"login":"manuel-rw","avatar_url":"https://avatars.githubusercontent.com/u/30572287?v=4"},{"login":"Meierschlumpf","avatar_url":"https://avatars.githubusercontent.com/u/63781622?v=4"},{"login":"SeDemal","avatar_url":"https://avatars.githubusercontent.com/u/26098587?v=4"},{"login":"lammersbjorn","avatar_url":"https://avatars.githubusercontent.com/u/71191962?v=4"},{"login":"LarveyOfficial","avatar_url":"https://avatars.githubusercontent.com/u/39219859?v=4"},{"login":"c00ldude1oo","avatar_url":"https://avatars.githubusercontent.com/u/15677803?v=4"},{"login":"InterN0te","avatar_url":"https://avatars.githubusercontent.com/u/10882916?v=4"},{"login":"Aimsucks","avatar_url":"https://avatars.githubusercontent.com/u/1828486?v=4"},{"login":"JannesV","avatar_url":"https://avatars.githubusercontent.com/u/5984244?v=4"},{"login":"MauriceNino","avatar_url":"https://avatars.githubusercontent.com/u/18658092?v=4"},{"login":"hillaliy","avatar_url":"https://avatars.githubusercontent.com/u/950010?v=4"},{"login":"Skeeww","avatar_url":"https://avatars.githubusercontent.com/u/9639992?v=4"},{"login":"RichyHBM","avatar_url":"https://avatars.githubusercontent.com/u/5472275?v=4"},{"login":"Bonfire","avatar_url":"https://avatars.githubusercontent.com/u/5704760?v=4"},{"login":"wieli99","avatar_url":"https://avatars.githubusercontent.com/u/46794825?v=4"},{"login":"ishaanparlikar","avatar_url":"https://avatars.githubusercontent.com/u/43013132?v=4"},{"login":"Zareix","avatar_url":"https://avatars.githubusercontent.com/u/29818713?v=4"},{"login":"tuggan","avatar_url":"https://avatars.githubusercontent.com/u/4325608?v=4"},{"login":"Spillebulle","avatar_url":"https://avatars.githubusercontent.com/u/46653946?v=4"},{"login":"marektancak0106","avatar_url":"https://avatars.githubusercontent.com/u/25422667?v=4"},{"login":"spkesDE","avatar_url":"https://avatars.githubusercontent.com/u/1682314?v=4"},{"login":"j3lte","avatar_url":"https://avatars.githubusercontent.com/u/2557568?v=4"},{"login":"dslatt","avatar_url":"https://avatars.githubusercontent.com/u/5211001?v=4"},{"login":"Malong11-007","avatar_url":"https://avatars.githubusercontent.com/u/40298510?v=4"},{"login":"StefanB7","avatar_url":"https://avatars.githubusercontent.com/u/22056509?v=4"},{"login":"souravghosh01","avatar_url":"https://avatars.githubusercontent.com/u/37548139?v=4"},{"login":"SR3u","avatar_url":"https://avatars.githubusercontent.com/u/2380522?v=4"},{"login":"Rikpat","avatar_url":"https://avatars.githubusercontent.com/u/33869814?v=4"},{"login":"NormanJS","avatar_url":"https://avatars.githubusercontent.com/u/83409549?v=4"},{"login":"No-Maines-Land","avatar_url":"https://avatars.githubusercontent.com/u/20687725?v=4"},{"login":"stark1tty","avatar_url":"https://avatars.githubusercontent.com/u/69326376?v=4"},{"login":"Momcilo42","avatar_url":"https://avatars.githubusercontent.com/u/10118137?v=4"},{"login":"marius-arch","avatar_url":"https://avatars.githubusercontent.com/u/72494359?v=4"},{"login":"Lumilias","avatar_url":"https://avatars.githubusercontent.com/u/10852161?v=4"},{"login":"Havock94","avatar_url":"https://avatars.githubusercontent.com/u/7635248?v=4"},{"login":"Lollyde","avatar_url":"https://avatars.githubusercontent.com/u/11347524?v=4"},{"login":"krishnamuppaneni","avatar_url":"https://avatars.githubusercontent.com/u/4703366?v=4"},{"login":"devtobi","avatar_url":"https://avatars.githubusercontent.com/u/28538704?v=4"},{"login":"TyxTang","avatar_url":"https://avatars.githubusercontent.com/u/90897955?v=4"},{"login":"beanbag420","avatar_url":"https://avatars.githubusercontent.com/u/19630511?v=4"},{"login":"WillyJL","avatar_url":"https://avatars.githubusercontent.com/u/49810075?v=4"},{"login":"anonysoul","avatar_url":"https://avatars.githubusercontent.com/u/105715150?v=4"},{"login":"cospeedster","avatar_url":"https://avatars.githubusercontent.com/u/52749645?v=4"},{"login":"diederbert","avatar_url":"https://avatars.githubusercontent.com/u/162878798?v=4"},{"login":"digdilem","avatar_url":"https://avatars.githubusercontent.com/u/4052275?v=4"},{"login":"dinghino","avatar_url":"https://avatars.githubusercontent.com/u/14729459?v=4"},{"login":"gfomichev","avatar_url":"https://avatars.githubusercontent.com/u/1596146?v=4"},{"login":"gnattu","avatar_url":"https://avatars.githubusercontent.com/u/12995396?v=4"},{"login":"tuxpizza","avatar_url":"https://avatars.githubusercontent.com/u/84710786?v=4"},{"login":"xmarkurion","avatar_url":"https://avatars.githubusercontent.com/u/46654687?v=4"},{"login":"huanga","avatar_url":"https://avatars.githubusercontent.com/u/732970?v=4"},{"login":"taos15","avatar_url":"https://avatars.githubusercontent.com/u/122130728?v=4"},{"login":"arghyadipchak","avatar_url":"https://avatars.githubusercontent.com/u/32430374?v=4"},{"login":"AuthorShin","avatar_url":"https://avatars.githubusercontent.com/u/4959043?v=4"},{"login":"benniblot","avatar_url":"https://avatars.githubusercontent.com/u/47495914?v=4"},{"login":"bebsworthy","avatar_url":"https://avatars.githubusercontent.com/u/5266759?v=4"},{"login":"catrielmuller","avatar_url":"https://avatars.githubusercontent.com/u/2272323?v=4"},{"login":"Vysp3r","avatar_url":"https://avatars.githubusercontent.com/u/62019735?v=4"},{"login":"DR-Blogs","avatar_url":"https://avatars.githubusercontent.com/u/96393186?v=4"},{"login":"singular0","avatar_url":"https://avatars.githubusercontent.com/u/1925883?v=4"},{"login":"diogovalentte","avatar_url":"https://avatars.githubusercontent.com/u/49578155?v=4"},{"login":"codezninja","avatar_url":"https://avatars.githubusercontent.com/u/4400773?v=4"},{"login":"fabricionaweb","avatar_url":"https://avatars.githubusercontent.com/u/15933?v=4"},{"login":"nyok1912","avatar_url":"https://avatars.githubusercontent.com/u/32163949?v=4"},{"login":"bo3bdo","avatar_url":"https://avatars.githubusercontent.com/u/5271380?v=4"},{"login":"hskrtich","avatar_url":"https://avatars.githubusercontent.com/u/1214484?v=4"},{"login":"ishults","avatar_url":"https://avatars.githubusercontent.com/u/7275442?v=4"},{"login":"jelliuk","avatar_url":"https://avatars.githubusercontent.com/u/4790959?v=4"},{"login":"Moohan","avatar_url":"https://avatars.githubusercontent.com/u/5982260?v=4"},{"login":"tesar-tech","avatar_url":"https://avatars.githubusercontent.com/u/33880579?v=4"},{"login":"jbruell","avatar_url":"https://avatars.githubusercontent.com/u/31685172?v=4"},{"login":"JasonLeeB06","avatar_url":"https://avatars.githubusercontent.com/u/82266978?v=4"},{"login":"jeffersonraimon","avatar_url":"https://avatars.githubusercontent.com/u/80064475?v=4"},{"login":"jo-chemla","avatar_url":"https://avatars.githubusercontent.com/u/16822841?v=4"},{"login":"joser93","avatar_url":"https://avatars.githubusercontent.com/u/6912089?v=4"},{"login":"justijndepover","avatar_url":"https://avatars.githubusercontent.com/u/9008623?v=4"},{"login":"Aandree5","avatar_url":"https://avatars.githubusercontent.com/u/32734153?v=4"},{"login":"afonsojramos","avatar_url":"https://avatars.githubusercontent.com/u/19473034?v=4"},{"login":"mvanhorn","avatar_url":"https://avatars.githubusercontent.com/u/455140?v=4"},{"login":"jackwellerreal","avatar_url":"https://avatars.githubusercontent.com/u/97435840?v=4"},{"login":"Copilot","avatar_url":"https://avatars.githubusercontent.com/in/1143301?v=4"},{"login":"SergioChan","avatar_url":"https://avatars.githubusercontent.com/u/10103766?v=4"},{"login":"toineenzo","avatar_url":"https://avatars.githubusercontent.com/u/4921397?v=4"},{"login":"oben01","avatar_url":"https://avatars.githubusercontent.com/u/22369027?v=4"},{"login":"rezstje","avatar_url":"https://avatars.githubusercontent.com/u/45420315?v=4"},{"login":"darbyjack","avatar_url":"https://avatars.githubusercontent.com/u/21014720?v=4"},{"login":"castielwaverly","avatar_url":"https://avatars.githubusercontent.com/u/137005689?v=4"},{"login":"xoxyuxu","avatar_url":"https://avatars.githubusercontent.com/u/3543416?v=4"},{"login":"StrandedTurtle","avatar_url":"https://avatars.githubusercontent.com/u/132835255?v=4"},{"login":"MaienM","avatar_url":"https://avatars.githubusercontent.com/u/583129?v=4"},{"login":"kylegrieder","avatar_url":"https://avatars.githubusercontent.com/u/25424229?v=4"},{"login":"HeapReaper","avatar_url":"https://avatars.githubusercontent.com/u/31496522?v=4"},{"login":"lopsch","avatar_url":"https://avatars.githubusercontent.com/u/4073114?v=4"},{"login":"Bergasha","avatar_url":"https://avatars.githubusercontent.com/u/25044200?v=4"},{"login":"Gilmoursa","avatar_url":"https://avatars.githubusercontent.com/u/10321421?v=4"},{"login":"AartSchinkel","avatar_url":"https://avatars.githubusercontent.com/u/189675191?v=4"},{"login":"mairas","avatar_url":"https://avatars.githubusercontent.com/u/407010?v=4"},{"login":"itzTheMeow","avatar_url":"https://avatars.githubusercontent.com/u/50887230?v=4"},{"login":"Nerdy-Technician","avatar_url":"https://avatars.githubusercontent.com/u/45691205?v=4"},{"login":"NicolasNewman","avatar_url":"https://avatars.githubusercontent.com/u/16135852?v=4"},{"login":"NikeLaosClericus","avatar_url":"https://avatars.githubusercontent.com/u/39810627?v=4"},{"login":"noahcroghan","avatar_url":"https://avatars.githubusercontent.com/u/27422774?v=4"},{"login":"ohmzi","avatar_url":"https://avatars.githubusercontent.com/u/6551272?v=4"},{"login":"Rhiz3K","avatar_url":"https://avatars.githubusercontent.com/u/33246262?v=4"},{"login":"saschabuehrle","avatar_url":"https://avatars.githubusercontent.com/u/47737812?v=4"},{"login":"CrazyWolf13","avatar_url":"https://avatars.githubusercontent.com/u/96661824?v=4"},{"login":"VIVAAN-DHAWAN","avatar_url":"https://avatars.githubusercontent.com/u/150339722?v=4"},{"login":"euduardo","avatar_url":"https://avatars.githubusercontent.com/u/22860571?v=4"},{"login":"generalmat82","avatar_url":"https://avatars.githubusercontent.com/u/110256073?v=4"},{"login":"lthomsen","avatar_url":"https://avatars.githubusercontent.com/u/28560325?v=4"},{"login":"pitschi","avatar_url":"https://avatars.githubusercontent.com/u/6841565?v=4"},{"login":"sharpsalat","avatar_url":"https://avatars.githubusercontent.com/u/15666932?v=4"},{"login":"tech62","avatar_url":"https://avatars.githubusercontent.com/u/16237642?v=4"},{"login":"m0s4ik","avatar_url":"https://avatars.githubusercontent.com/u/26578395?v=4"},{"login":"ashtonbatty","avatar_url":"https://avatars.githubusercontent.com/u/230635?v=4"},{"login":"Oupsman","avatar_url":"https://avatars.githubusercontent.com/u/11260343?v=4"},{"login":"cethien","avatar_url":"https://avatars.githubusercontent.com/u/60463918?v=4"},{"login":"DZ945brH","avatar_url":"https://avatars.githubusercontent.com/u/42635243?v=4"},{"login":"dgshue","avatar_url":"https://avatars.githubusercontent.com/u/12952156?v=4"},{"login":"d-lindahl","avatar_url":"https://avatars.githubusercontent.com/u/690410?v=4"},{"login":"KartulUdus","avatar_url":"https://avatars.githubusercontent.com/u/23346150?v=4"},{"login":"ieugen","avatar_url":"https://avatars.githubusercontent.com/u/1050920?v=4"},{"login":"faugusto-oliveira","avatar_url":"https://avatars.githubusercontent.com/u/37384074?v=4"},{"login":"frankvHoof93","avatar_url":"https://avatars.githubusercontent.com/u/7346516?v=4"},{"login":"febrile42","avatar_url":"https://avatars.githubusercontent.com/u/14597122?v=4"},{"login":"Kaotic3","avatar_url":"https://avatars.githubusercontent.com/u/80522225?v=4"},{"login":"KelianB","avatar_url":"https://avatars.githubusercontent.com/u/11216155?v=4"},{"login":"hotrungnhan","avatar_url":"https://avatars.githubusercontent.com/u/40029631?v=4"},{"login":"leandroscardua","avatar_url":"https://avatars.githubusercontent.com/u/24698541?v=4"},{"login":"Lok3sh-kumar","avatar_url":"https://avatars.githubusercontent.com/u/110459168?v=4"},{"login":"Bricklou","avatar_url":"https://avatars.githubusercontent.com/u/15181236?v=4"}] \ No newline at end of file diff --git a/tooling/github/setup/action.yml b/tooling/github/setup/action.yml index 77ff8d742..4f8330014 100644 --- a/tooling/github/setup/action.yml +++ b/tooling/github/setup/action.yml @@ -7,7 +7,7 @@ runs: - uses: pnpm/action-setup@v6 - uses: actions/setup-node@v6 with: - node-version: 24.17.0 + node-version: 24.18.0 cache: "pnpm" - shell: bash